export default {
async fetch(request, env, ctx) {
const bucket = env.MY_BUCKET;
const rootDirectory = 'guest/';
const url = new URL(request.url);
// Helper function to get the current directory
const getCurrentDirectory = (path) => {
const cleanPath = path.replace(/^\/+|\/+$/g, '');
return rootDirectory + cleanPath + (cleanPath ? '/' : '');
};
// Helper function to get parent directory
const getParentDirectory = (path) => {
const parts = path.split('/').filter(p => p);
parts.pop();
return '/' + parts.join('/');
};
// List files and folders in the current directory
async function listDirectory(directory) {
const objects = await bucket.list({ prefix: directory, delimiter: '/' });
const items = new Set();
for (const obj of objects.objects) {
const relativePath = obj.key.slice(directory.length);
if (relativePath) {
items.add(relativePath.split('/')[0]);
}
}
for (const prefix of objects.delimitedPrefixes) {
const relativePath = prefix.slice(directory.length);
if (relativePath) {
items.add(relativePath.split('/')[0]);
}
}
return Array.from(items).sort();
}
// Handle directory listing
if (!url.pathname.startsWith('/download/')) {
const currentDirectory = getCurrentDirectory(url.pathname);
try {
const items = await listDirectory(currentDirectory);
let html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AcoFork R2 Files</title>
<style>
body, html {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
line-height: 1.6;
height: 100%;
}
body {
background-image: url('https://hrandom.onani.cn');
background-size: cover;
background-position: center;
background-attachment: fixed;
display: flex;
justify-content: center;
align-items: center;
}
.container {
width: 80%;
max-width: 800px;
margin: 40px;
padding: 30px;
background-color: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(10px);
border-radius: 15px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
ul {
list-style-type: none;
padding: 0;
margin: 0;
}
li {
margin-bottom: 10px;
border: 1px solid rgba(0, 0, 0, 0.1);
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
}
li:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
a {
display: block;
padding: 12px 15px;
text-decoration: none;
color: #333;
transition: background-color 0.3s ease;
}
a:hover {
background-color: rgba(255, 255, 255, 0.5);
}
.folder::before {
content: "📁 ";
color: #FFA500;
}
.file::before {
content: "📄 ";
color: #008000;
}
.parent-link {
display: inline-block;
margin-bottom: 20px;
padding: 8px 15px;
background-color: rgba(0, 102, 204, 0.8);
color: white;
text-decoration: none;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.parent-link:hover {
background-color: rgba(0, 82, 163, 0.9);
}
@keyframes clickEffect {
0% { transform: scale(1); }
50% { transform: scale(0.95); }
100% { transform: scale(1); }
}
.click-effect {
animation: clickEffect 0.3s;
}
</style>
</head>
<body>
<div class="container">
<h1>我的 R2 Files</h1>
`;
// Add parent directory link if not in root
if (currentDirectory !== rootDirectory) {
const parentDir = getParentDirectory(url.pathname);
html += `<a href="${parentDir}" class="parent-link">← 上級目錄</a>`;
}
html += '<ul>';
for (const item of items) {
const itemPath = currentDirectory.slice(rootDirectory.length) + item;
const isFolder = !item.includes('.');
const className = isFolder ? 'folder' : 'file';
// Update href for files to use the external download URL
const href = isFolder
? `/${encodeURIComponent(itemPath)}`
: `https://r2-dl.afo.im/guest/${encodeURIComponent(itemPath)}`;
html += `<li class="${className}"><a href="${href}" onclick="this.classList.add('click-effect'); setTimeout(() => this.classList.remove('click-effect'), 300);">${item}${isFolder ? '/' : ''}</a></li>`;
}
html += `
</ul>
</div>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
document.querySelectorAll('a').forEach(link => {
link.addEventListener('click', function(e) {
if (!this.classList.contains('parent-link')) {
e.preventDefault();
this.classList.add('click-effect');
setTimeout(() => {
window.location = this.href;
}, 300);
}
});
});
});
</script>
</body>
</html>`;
return new Response(html, {
headers: { 'Content-Type': 'text/html; charset=utf-8' },
});
} catch (error) {
console.error('Error listing objects:', error);
return new Response('列出對象時出錯', { status: 500 });
}
}
// Default response for any other invalid requests
return new Response('無效請求', { status: 400 });
},
};