addEventListener("fetch", event => { event.respondWith(handleRequest(event)); }); const INDEX_KEY = "__index__"; const FIXED_PASSWORD = "****"; // 固定密码方式 const SESSION_KEY_PREFIX = "note_session_"; async function handleRequest(event) { const request = event.request; let url; try { url = new URL(request.url); } catch(e){ return new Response("Invalid URL", {status:400}); } let noteName; try { noteName = decodeURIComponent(url.pathname.slice(1)) || generateRandomNote(); } catch(e){ noteName = generateRandomNote(); } function isValidNoteName(name){ if(!name || name.length > 50) return false; if(/[-\u001F\u007F\/\\]/.test(name)) return false; return true; } if(!isValidNoteName(noteName) && url.pathname !== "/"){ return new Response(`<script>alert("笔记名非法");history.back();</script>`, { headers:{ "Content-Type":"text/html;charset=UTF-8" } }); } const method = request.method; const isRaw = url.searchParams.has("raw"); // 处理密码验证请求 if(method === "POST" && url.searchParams.has("password")) { const formData = await request.formData(); const password = formData.get("password"); if(password === FIXED_PASSWORD) { // 设置会话 Cookie,有效期 1 小时 const sessionKey = `${SESSION_KEY_PREFIX}${encodeURIComponent(noteName)}`; const headers = new Headers({ "Content-Type": "application/json", "Set-Cookie": `${sessionKey}=1; Path=/; Max-Age=3600; HttpOnly; SameSite=Strict` }); return new Response(JSON.stringify({ success: true }), { headers }); } else { return new Response(JSON.stringify({ success: false, error: "密码错误" }), { status: 401, headers: { "Content-Type": "application/json" } }); } } // POST 保存逻辑 if(method === "POST" && !url.searchParams.has("password")) { const text = await request.text(); let encryptedFlag = url.searchParams.get("encrypt") === "1"; // 删除空文件 if(!text.trim()){ try { await NOTES_KV.delete(noteName); } catch(e){ console.error("删除 KV 失败:", e); } await updateIndex(noteName, null); return new Response(JSON.stringify({ deleted:true }), { headers:{ "Content-Type":"application/json" } }); } let existingObj; try { const existingNote = await NOTES_KV.get(noteName); existingObj = existingNote ? JSON.parse(existingNote) : null; } catch(e){ existingObj=null; } const createdAt = existingObj?.created_at || new Date().toISOString(); const updatedAt = new Date().toISOString(); try { await NOTES_KV.put(noteName, JSON.stringify({ content:text, created_at:createdAt, updated_at:updatedAt, encrypted: encryptedFlag })); await updateIndex(noteName, { created_at: createdAt, updated_at: updatedAt, encrypted: encryptedFlag }); } catch(e){ console.error("保存 KV 失败:", e); return new Response("KV 保存失败",{status:500}); } return new Response(JSON.stringify({ created_at:createdAt, updated_at:updatedAt, encrypted: encryptedFlag }), { headers:{ "Content-Type":"application/json" } }); } // 检查是否为加密笔记 let note; try { note = await NOTES_KV.get(noteName); } catch(e){ note=null; } let noteObj; if(note){ try { noteObj = JSON.parse(note); } catch(e){ noteObj={ content: note, created_at:null, updated_at:null, encrypted:false }; } } else noteObj={ content:"", created_at:null, updated_at:null, encrypted:false }; const encryptedFlag = noteObj.encrypted || false; // RAW 请求 if(isRaw){ if(encryptedFlag) { const sessionKey = `${SESSION_KEY_PREFIX}${encodeURIComponent(noteName)}`; const cookies = request.headers.get("Cookie") || ""; const isUnlocked = cookies.includes(`${sessionKey}=1`); if(!isUnlocked) { return new Response("需要密码验证", { status: 401, headers: { "Content-Type": "text/plain;charset=UTF-8" } }); } } try{ if(note){ try { note = JSON.parse(note).content; } catch(e) {} return new Response(note,{ headers:{ "Content-Type":"text/plain;charset=UTF-8" } }); } else return new Response("Not found",{status:404}); } catch(e){ return new Response("KV 获取失败",{status:500}); } } // 目录 JSON if(url.pathname === "/" && url.searchParams.get("list") === "1"){ try { let indexData = await NOTES_KV.get(INDEX_KEY); let arr = indexData ? JSON.parse(indexData) : []; arr.sort((a,b)=>{ let ta = a.updated_at || a.created_at || ""; let tb = b.updated_at || b.created_at || ""; return new Date(tb) - new Date(ta); }); return new Response(JSON.stringify(arr), { headers:{ "Content-Type":"application/json" } }); } catch(e){ return new Response("索引读取失败",{status:500}); } } // 目录页 if(url.pathname === "/"){ let html = `<html><head><meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>📒 Notes Directory</title> <style> body { font-family: sans-serif; background:#f0f0f0; padding:20px; } h1 { color:#333; } ul { list-style:none; padding:0; } li { margin:10px 0; } a { text-decoration:none; color:#0077cc; font-size:1.1em; } a:hover { text-decoration:underline; } .time-info { display: flex; justify-content: space-between; font-size: 12px; color:#555; margin-top:2px;} @media (prefers-color-scheme: dark) { body { background:#121212; color:#f0f0f0; } h1 { color:#ddd; } a { color:#80b3ff; } .time-info { color:#f0f0f0; } } </style> </head> <body> <h1>📒 Notes</h1><ul id="notesList"></ul> <script> function displayTime(t){return t?new Date(t).toLocaleString(undefined,{hour12:false}):"未知";} async function loadList(){ try{ const resp = await fetch("/?list=1"); const arr = await resp.json(); const ul = document.getElementById("notesList"); ul.innerHTML=""; arr.forEach(item=>{ const li=document.createElement("li"); let icon = ''; if(item.encrypted) { if(sessionStorage.getItem("${SESSION_KEY_PREFIX}"+encodeURIComponent(item.name))==="1") icon='🔓'; else icon='🔐'; } li.innerHTML = icon+'<a href="/'+encodeURIComponent(item.name)+'">'+item.name+'</a>' + '<div class="time-info">创建: '+displayTime(item.created_at)+' | 更新: '+displayTime(item.updated_at)+'</div>'; ul.appendChild(li); }); }catch(e){console.error("加载目录失败",e);} } loadList(); setInterval(loadList,1000); </script> </body></html>`; return new Response(html,{ headers:{ "Content-Type":"text/html;charset=UTF-8" } }); } // 如果是加密笔记,检查会话状态 if(encryptedFlag && !url.searchParams.has("password")) { const sessionKey = `${SESSION_KEY_PREFIX}${encodeURIComponent(noteName)}`; const cookies = request.headers.get("Cookie") || ""; const isUnlocked = cookies.includes(`${sessionKey}=1`); if(!isUnlocked) { // 返回密码输入页面 return new Response(` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>📒 输入密码 - ${noteName}</title> <style> body { font-family: sans-serif; background:#f0f0f0; padding:20px; display:flex; justify-content:center; align-items:center; height:100vh; margin:0; } .container { background:#fff; padding:20px; border-radius:5px; box-shadow:0 0 10px rgba(0,0,0,0.1); text-align:center; } input { padding:10px; margin:10px 0; width:200px; } button { padding:10px 20px; cursor:pointer; } .error { color:red; display:none; } @media (prefers-color-scheme: dark) { body { background:#121212; } .container { background:#24262b; color:#fff; } input, button { background:#333b4d; color:#fff; border:1px solid #495265; } } </style> </head> <body> <div class="container"> <h2>请输入密码访问笔记: ${noteName}</h2> <input type="password" id="password" placeholder="输入密码" onkeydown="if(event.key === 'Enter') submitPassword();"> <div class="error" id="errorMsg"></div> <button onclick="submitPassword()">提交</button> </div> <script> async function submitPassword() { const password = document.getElementById('password').value; const errorMsg = document.getElementById('errorMsg'); const formData = new FormData(); formData.append('password', password); try { const resp = await fetch(window.location.href + '?password=1', { method: 'POST', body: formData }); const data = await resp.json(); if(data.success) { sessionStorage.setItem("${SESSION_KEY_PREFIX}${encodeURIComponent(noteName)}", "1"); window.location.href = window.location.pathname; // 跳转到笔记页面 } else { errorMsg.textContent = data.error || '密码错误'; errorMsg.style.display = 'block'; } } catch(e) { errorMsg.textContent = '验证失败'; errorMsg.style.display = 'block'; } } </script> </body> </html>`, { headers: { "Content-Type": "text/html;charset=UTF-8" } }); } } // 编辑页 const content = noteObj.content || ""; const createdAtISO = noteObj.created_at || ""; const updatedAtISO = noteObj.updated_at || ""; return new Response(`<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>📒 ${noteName}</title> <style> body{margin:0;background:#ebeef1;} .container{position:absolute;top:20px;right:20px;bottom:20px;left:20px;display:flex;flex-direction:column;} #content{flex:1;margin:0;padding:20px;overflow-y:auto;resize:none;width:100%;box-sizing:border-box;border:1px solid #ddd;outline:none;font-size:1em;} .controls{display:flex;justify-content:space-between;align-items:center;margin-top:5px;font-size:12px;} .actions{display:flex;gap:10px;} #saveBtn{padding:2px 6px;font-size:12px;} @media (prefers-color-scheme: dark){ body{background:#333b4d;} #content{background:#24262b;color:#fff;border-color:#495265;} #status{color:#ccc;} .controls div, .controls span { color:#ccc; } } </style> </head> <body> <div class="container"> <textarea id="content">${content}</textarea> <div class="controls"> <div>创建: <span class="created" data-time="${createdAtISO}"></span><br> 更新: <span class="updated" data-time="${updatedAtISO}"></span></div> <div class="actions"> <label><input type="checkbox" id="encryptToggle"${encryptedFlag?' checked':''}/> 加密</label> <button id="saveBtn">💾 保存</button> </div> </div> <div id="status"></div> </div> <script> const textarea=document.getElementById('content'); const saveBtn=document.getElementById('saveBtn'); const encryptToggle=document.getElementById('encryptToggle'); const status=document.getElementById('status'); let previousContent=textarea.value; // session 解锁标记 let sessionKey = "${SESSION_KEY_PREFIX}${encodeURIComponent(noteName)}"; let unlocked = sessionStorage.getItem(sessionKey) === "1" || ${!encryptedFlag}; // 加密控制 if(encryptToggle.checked && !unlocked){ let pwd = prompt("请输入密码访问此笔记:"); if(pwd !== "${FIXED_PASSWORD}"){ alert("密码错误"); window.location.href = window.location.pathname + '?password=1'; // 重定向到密码输入页面 } else { unlocked=true; sessionStorage.setItem(sessionKey,"1"); // 同步设置 Cookie fetch(window.location.href + '?password=1', { method: 'POST', body: new FormData().append('password', "${FIXED_PASSWORD}") }).then(() => window.location.reload()); // 重新加载以确保服务器端验证 } } else if(!encryptToggle.checked){ unlocked=true; } textarea.disabled = encryptToggle.checked && !unlocked; function displayTime(t){return t?new Date(t).toLocaleString(undefined,{hour12:false}):"未知";} function updateTimeDisplays(){ document.querySelectorAll('.created').forEach(el=>el.textContent=displayTime(el.dataset.time)); document.querySelectorAll('.updated').forEach(el=>el.textContent=displayTime(el.dataset.time)); } updateTimeDisplays(); async function save(auto=false){ const temp = textarea.value; if(previousContent!==temp || !auto){ try{ const resp=await fetch(window.location.href+'?encrypt='+ (encryptToggle.checked?"1":"0"),{method:'POST',body:temp}); const data=await resp.json(); previousContent=temp; if(data.deleted){ textarea.value=""; if(!auto) status.textContent='笔记已删除'; setTimeout(()=>status.textContent='',3000); document.querySelector('.created').dataset.time = ""; document.querySelector('.updated').dataset.time = ""; updateTimeDisplays(); } else { if(!auto) status.textContent='已保存: '+new Date().toLocaleString(undefined,{hour12:false}); setTimeout(()=>status.textContent='',3000); if(data.updated_at){ document.querySelector('.updated').dataset.time = data.updated_at; } if(data.created_at && !document.querySelector('.created').dataset.time){ document.querySelector('.created').dataset.time = data.created_at; } updateTimeDisplays(); } } catch(e){ console.error("保存请求失败", e); } } } saveBtn.addEventListener('click',()=>save(false)); setInterval(()=>save(true),1000); </script> </body> </html>`,{ headers:{ "Content-Type":"text/html;charset=UTF-8" } }); } // 更新索引函数 async function updateIndex(name, timesObj){ let indexData = await NOTES_KV.get(INDEX_KEY); let arr = indexData ? JSON.parse(indexData) : []; arr = arr.filter(item=>item.name!==name); if(timesObj){ arr.push({ name, created_at: timesObj.created_at, updated_at: timesObj.updated_at, encrypted: timesObj.encrypted }); } await NOTES_KV.put(INDEX_KEY, JSON.stringify(arr)); } function generateRandomNote(){ const chars='234579abcdefghjkmnpqrstwxyz'; return Array.from({length:5},()=>chars[Math.floor(Math.random()*chars.length)]).join(''); }