diff --git a/.next/trace b/.next/trace deleted file mode 100644 index de78b322..00000000 --- a/.next/trace +++ /dev/null @@ -1 +0,0 @@ -[{"name":"generate-buildid","duration":145,"timestamp":29782277938,"id":4,"parentId":1,"tags":{},"startTime":1779266763571,"traceId":"3a8c4178be1354d5"},{"name":"load-custom-routes","duration":254,"timestamp":29782278150,"id":5,"parentId":1,"tags":{},"startTime":1779266763572,"traceId":"3a8c4178be1354d5"},{"name":"create-dist-dir","duration":423,"timestamp":29782278422,"id":6,"parentId":1,"tags":{},"startTime":1779266763572,"traceId":"3a8c4178be1354d5"},{"name":"clean","duration":708,"timestamp":29782279361,"id":7,"parentId":1,"tags":{},"startTime":1779266763573,"traceId":"3a8c4178be1354d5"},{"name":"next-build","duration":25156,"timestamp":29782255096,"id":1,"tags":{"buildMode":"default","version":"16.2.6","bundler":"turbopack","failed":true},"startTime":1779266763549,"traceId":"3a8c4178be1354d5"}] diff --git a/.next/trace-build b/.next/trace-build deleted file mode 100644 index 33aa9265..00000000 --- a/.next/trace-build +++ /dev/null @@ -1 +0,0 @@ -[{"name":"next-build","duration":25156,"timestamp":29782255096,"id":1,"tags":{"buildMode":"default","version":"16.2.6","bundler":"turbopack","failed":true},"startTime":1779266763549,"traceId":"3a8c4178be1354d5"}] diff --git a/bot/.gitignore b/bot/.gitignore deleted file mode 100644 index 2d7ec5ce..00000000 --- a/bot/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -.env -node_modules/ diff --git a/bot/README.md b/bot/README.md deleted file mode 100644 index 9d56cf62..00000000 --- a/bot/README.md +++ /dev/null @@ -1,46 +0,0 @@ -# WriteBot - -纯 Node.js,零 npm 依赖。后台管理 `write` 开发服务,提供 Web 控制面板 + Telegram 机器人。 - -## 快速开始 - -### 1. 配置 - -```powershell -cd bot -copy .env.example .env -``` - -### 2. 启动 - -```powershell -node index.js -``` - -或双击 `start.bat`。 - -### 3. 控制面板 - -浏览器打开:`http://127.0.0.1:8017/` - -一键启动/停止 Write、查看状态、复制局域网地址。 - -### 4. Telegram 机器人 - -1. 联系 [@BotFather](https://t.me/BotFather),`/newbot` 获得 token。 -2. 联系 [@userinfobot](https://t.me/userinfobot) 获得 chat id。 -3. 填入 `.env`。 - -命令:`/start` `/stop` `/status` `/ip` `/help` - -## 开机自启 - -已配好启动文件夹的快捷方式(WriteBot.lnk)。开机登录后自动启动。 - -## 防火墙 - -首次手机访问不了要放行 8016: - -```powershell -New-NetFirewallRule -DisplayName "Write Dev 8016" -Direction Inbound -Protocol TCP -LocalPort 8016 -Action Allow -``` diff --git a/bot/index.js b/bot/index.js deleted file mode 100644 index e5222783..00000000 --- a/bot/index.js +++ /dev/null @@ -1,75 +0,0 @@ -// writebot - Telegram bot + HTTP API + Web Admin -// Zero npm dependencies. Node 18+ built-in everything. - -const config = require("./src/config"); -const { getLanIP } = require("./src/helpers"); -const { startWriteServer, stopWriteServer } = require("./src/write-server"); -const { createControlServer } = require("./src/admin-panel"); -const { tg, allowed } = require("./src/tg-core"); -const { tgPoll } = require("./src/tg-poll"); - -async function main() { - const ip = getLanIP(); - console.log(""); - console.log(" \\u250C\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510"); - console.log(" \\u2502 WriteBot \\u2502"); - console.log(" \\u251C\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2524"); - console.log(" \\u2502 Admin: http://127.0.0.1:" + config.CONTROL_PORT + "/ \\u2502"); - console.log(" \\u2502 Write: http://" + ip + ":" + config.WRITE_PORT + " \\u2502"); - console.log(" \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518"); - console.log(""); - - // Start HTTP control server - const controlServer = createControlServer(); - - // Connect Telegram - if (config.TG_TOKEN) { - try { - const me = await tg("getMe", {}); - if (me.ok) { - console.log("[bot] Telegram: @" + me.result.username); - - try { - await tg("setMyCommands", { - commands: [ - { command: "start", description: "\\u542F\\u52A8\\u5199\\u4F5C\\u540E\\u53F0" }, - { command: "new", description: "\\u5F00\\u59CB\\u5199\\u4E00\\u7BC7" }, - { command: "edit", description: "\\u7F16\\u8F91\\u5DF2\\u6709\\u6587\\u7AE0" }, - { command: "list", description: "\\u6587\\u7AE0\\u5217\\u8868" }, - { command: "stats", description: "\\u7F51\\u7AD9\\u6570\\u636E\\u7EDF\\u8BA1" }, - { command: "publish", description: "\\u53D1\\u5E03\\u5230\\u7F51\\u7AD9" }, - { command: "draft", description: "\\u4FDD\\u5B58\\u4E3A\\u8349\\u7A3F" }, - { command: "preview", description: "\\u9884\\u89C8\\u5168\\u6587" }, - { command: "links", description: "\\u53CB\\u94FE\\u5217\\u8868" }, - { command: "feeds", description: "\\u8BA2\\u9605\\u6E90\\u5217\\u8868" }, - { command: "help", description: "\\u5168\\u90E8\\u547D\\u4EE4" }, - ], - }); - console.log("[bot] commands registered"); - } catch (e) { - console.error("[bot] failed to register commands:", e.message); - } - - tgPoll(); - } else { - console.log("[bot] TG token invalid - HTTP-only mode"); - } - } catch { - console.log("[bot] TG connection failed - HTTP-only mode"); - } - } else { - console.log("[bot] no TG_TOKEN - HTTP-only mode"); - } - - // Graceful shutdown - for (const sig of ["SIGINT", "SIGTERM"]) { - process.on(sig, () => { - console.log("\\n[bot] " + sig + ", stopping..."); - stopWriteServer(); - controlServer.close(); - process.exit(0); - }); - } -} - -main(); diff --git a/bot/package-lock.json b/bot/package-lock.json deleted file mode 100644 index 8df4b821..00000000 --- a/bot/package-lock.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "writebot", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "writebot", - "version": "1.0.0" - } - } -} diff --git a/bot/package.json b/bot/package.json deleted file mode 100644 index d3beb48e..00000000 --- a/bot/package.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "writebot", - "version": "1.0.0", - "private": true, - "description": "Telegram bot to start the write dev server on LAN", - "main": "index.js", - "scripts": { - "start": "node index.js" - } -} diff --git a/bot/src/admin-panel.js b/bot/src/admin-panel.js deleted file mode 100644 index 2443621f..00000000 --- a/bot/src/admin-panel.js +++ /dev/null @@ -1,125 +0,0 @@ -// admin-panel.js - Minimal web admin panel + HTTP control API -const http = require("http"); -const config = require("./config"); -const { getLanIP } = require("./helpers"); -const { startWriteServer, stopWriteServer, getWriteStatus } = require("./write-server"); - -const ADMIN_HTML = ` - - - - -WriteBot - - - -
-

WriteBot

-
Ctrl+D bookmark this page
- -
Loading...
-
-
-
-
-
-
-
-
-
- - - -
- - -
- - -`.replace("var port=8016", "var port=${config.WRITE_PORT}"); - -function createControlServer() { - const server = http.createServer((req, res) => { - const url = new URL(req.url, "http://127.0.0.1:" + config.CONTROL_PORT); - const path = url.pathname; - res.setHeader("Access-Control-Allow-Origin", "*"); - - if (path === "/" && req.method === "GET") { - res.setHeader("Content-Type", "text/html; charset=utf-8"); - res.end(ADMIN_HTML); - } else if (path === "/status" && req.method === "GET") { - res.setHeader("Content-Type", "application/json"); - const s = getWriteStatus(); - s.ip = getLanIP(); - s.port = config.WRITE_PORT; - res.end(JSON.stringify(s)); - } else if (path === "/start" && req.method === "POST") { - res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify(startWriteServer())); - } else if (path === "/stop" && req.method === "POST") { - res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify({ ok: stopWriteServer() })); - } else if (path === "/ip" && req.method === "GET") { - res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify({ ip: getLanIP(), port: config.WRITE_PORT })); - } else if (path === "/health" && req.method === "GET") { - res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify({ ok: true })); - } else { - res.statusCode = 404; - res.setHeader("Content-Type", "application/json"); - res.end(JSON.stringify({ error: "not found" })); - } - }); - - server.listen(config.CONTROL_PORT, "127.0.0.1", () => { - console.log("[control] http://127.0.0.1:" + config.CONTROL_PORT); - }); - - return server; -} - -module.exports = { createControlServer }; diff --git a/bot/src/config.js b/bot/src/config.js deleted file mode 100644 index a96bf358..00000000 --- a/bot/src/config.js +++ /dev/null @@ -1,21 +0,0 @@ -// config.js - Environment and constants -const { existsSync, readFileSync } = require("fs"); -const { resolve } = require("path"); - -const envPath = resolve(__dirname, ".env"); -if (existsSync(envPath)) { - for (const line of readFileSync(envPath, "utf-8").split("\n")) { - const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.+?)\s*$/); - if (m && !process.env[m[1]]) process.env[m[1]] = m[2]; - } -} - -module.exports = { - TG_TOKEN: process.env.TG_BOT_TOKEN || "", - ALLOWED_IDS: (process.env.TG_ALLOWED_CHAT_IDS || "") - .split(",").map((s) => s.trim()).filter(Boolean).map(Number), - WRITE_PORT: parseInt(process.env.WRITE_PORT || "8016", 10), - CONTROL_PORT: parseInt(process.env.BOT_PORT || "8017", 10), - PREFER_IFACE: process.env.PREFER_IFACE || "WLAN", - WRITE_DIR: resolve(__dirname, "..", "write"), -}; diff --git a/bot/src/helpers.js b/bot/src/helpers.js deleted file mode 100644 index 44a25541..00000000 --- a/bot/src/helpers.js +++ /dev/null @@ -1,76 +0,0 @@ -// helpers.js - Utility functions -const os = require("os"); -const net = require("net"); -const config = require("./config"); - -function getLanIP() { - const now = Date.now(); - if (getLanIP._cache && getLanIP._cache.ip && now - getLanIP._cache.ts < 30000) - return getLanIP._cache.ip; - let ip = "0.0.0.0"; - try { - const ifs = os.networkInterfaces(); - const candidates = []; - for (const [name, addrs] of Object.entries(ifs)) { - if (!addrs) continue; - for (const a of addrs) { - if (a.family !== "IPv4" || a.internal) continue; - if (a.address.startsWith("169.254.")) continue; - candidates.push({ name, address: a.address }); - } - } - const pref = candidates.find((c) => - c.name.toLowerCase().includes(config.PREFER_IFACE.toLowerCase()) - ); - if (pref) ip = pref.address; - else { - const real = candidates.find((c) => { - const n = c.name.toLowerCase(); - return !n.includes("vmware") && !n.includes("virtual") && !n.includes("vethernet"); - }); - if (real) ip = real.address; - else if (candidates[0]) ip = candidates[0].address; - } - } catch {} - getLanIP._cache = { ip, ts: now }; - return ip; -} - -function checkPort(port) { - return new Promise((resolve) => { - const s = net.createConnection(port, "127.0.0.1", () => { s.destroy(); resolve(true); }); - s.on("error", () => { s.destroy(); resolve(false); }); - s.setTimeout(1500, () => { s.destroy(); resolve(false); }); - }); -} - -async function waitForReady(port, timeoutMs) { - const start = Date.now(); - while (Date.now() - start < (timeoutMs || 30000)) { - if (await checkPort(port)) return true; - await new Promise((r) => setTimeout(r, 1500)); - } - return false; -} - -function makeSlug(title) { - const dateStr = new Date().toISOString().slice(0, 10); - const slugPart = (title || "untitled") - .replace(/[^\w\u4e00-\u9fff]+/g, "-") - .replace(/^-+|-+$/g, "") - .toLowerCase() || "post"; - return dateStr + "-" + slugPart; -} - -function sessionSummary(s) { - const lines = s.content ? s.content.split("\\n").filter(Boolean).length : 0; - const chars = s.content.length; - return [ - "\\uD83D\\uDCDD " + (s.title || "\\u672A\\u547D\\u540D") + "", - s.categories.length ? "\\uD83D\\uDCC2 " + s.categories.join("\\u3001") : "", - s.tags.length ? "\\uD83C\\uDFF7 " + s.tags.join("\\u3001") : "", - chars ? "\\u270D " + lines + " \\u884C " + chars + " \\u5B57" : "", - ].filter(Boolean).join(" \\u00B7 "); -} - -module.exports = { getLanIP, checkPort, waitForReady, makeSlug, sessionSummary }; diff --git a/bot/src/sessions.js b/bot/src/sessions.js deleted file mode 100644 index c5a2f99c..00000000 --- a/bot/src/sessions.js +++ /dev/null @@ -1,59 +0,0 @@ -// sessions.js - Per-chat writing sessions -const config = require("./config"); -const { makeSlug } = require("./helpers"); - -const sessions = new Map(); - -function getSession(chatId) { - if (!sessions.has(chatId)) { - sessions.set(chatId, { - title: "", - slug: "", - content: "", - categories: [], - tags: [], - author: "qunlin", - draft: true, - }); - } - return sessions.get(chatId); -} - -function clearSession(chatId) { - sessions.delete(chatId); -} - -// Save session via Write API -async function saveViaAPI(s, isDraft) { - const body = JSON.stringify({ - frontMatter: { - title: s.title, - slug: s.slug, - date: new Date().toISOString().slice(0, 10), - draft: isDraft, - categories: s.categories, - tags: s.tags, - author: s.author, - }, - content: s.content, - }); - - const checkRes = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/posts/" + s.slug); - if (checkRes.ok) { - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/posts/" + s.slug, { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body, - }); - if (!res.ok) throw new Error((await res.json()).error || "API error (PUT)"); - } else { - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/posts", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body, - }); - if (!res.ok) throw new Error((await res.json()).error || "API error (POST)"); - } -} - -module.exports = { sessions, getSession, clearSession, saveViaAPI }; diff --git a/bot/src/tg-core.js b/bot/src/tg-core.js deleted file mode 100644 index 4151ee22..00000000 --- a/bot/src/tg-core.js +++ /dev/null @@ -1,54 +0,0 @@ -// tg-core.js - Telegram API helpers -const config = require("./config"); - -const API = config.TG_TOKEN ? "https://api.telegram.org/bot" + config.TG_TOKEN : ""; - -async function tg(method, body) { - const res = await fetch(API + "/" + method, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(body), - }); - return res.json(); -} - -function allowed(chatId) { - if (config.ALLOWED_IDS.length === 0) return true; - return config.ALLOWED_IDS.includes(chatId); -} - -async function sendMessage(chatId, text, opts) { - const safeText = String(text || ""); - try { - return await tg("sendMessage", Object.assign( - { chat_id: chatId, text: safeText, parse_mode: "HTML", disable_web_page_preview: true }, - opts || {} - )); - } catch (e) { - if (e.message && (e.message.includes("parse") || e.message.includes("HTML"))) { - return await tg("sendMessage", Object.assign( - { chat_id: chatId, text: safeText }, - opts || {} - )); - } - throw e; - } -} - -async function handleCallback(chatId, queryId, data, handlers) { - if (!data) return; - const parts = data.split("_"); - const cmd = parts[0]; - const page = parseInt(parts[1]) || 1; - - if (cmd === "list" && handlers.list) { - await handlers.list(chatId, String(page)); - } else if (cmd === "links" && handlers.links) { - await handlers.links(chatId, String(page)); - } else if (cmd === "feeds" && handlers.feeds) { - await handlers.feeds(chatId, String(page)); - } - try { await tg("answerCallbackQuery", { callback_query_id: queryId }); } catch {} -} - -module.exports = { tg, allowed, sendMessage, handleCallback }; diff --git a/bot/src/tg-handlers.js b/bot/src/tg-handlers.js deleted file mode 100644 index 5a838a75..00000000 --- a/bot/src/tg-handlers.js +++ /dev/null @@ -1,413 +0,0 @@ -// tg-handlers.js - All Telegram /command handlers -const config = require("./config"); -const { getLanIP, makeSlug, sessionSummary } = require("./helpers"); -const { getSession, clearSession, sessions, saveViaAPI } = require("./sessions"); -const { getWriteStatus, ensureWriteReady } = require("./write-server"); -const { sendMessage } = require("./tg-core"); - -const CMD_HANDLERS = {}; - -// ---------- System ---------- -CMD_HANDLERS.start = async function (chatId) { - const ip = getLanIP(); - const ready = await ensureWriteReady(chatId, sendMessage); - if (ready) { - return sendMessage(chatId, - "\\u2705 Write \\u5DF2\\u542F\\u52A8\\n\\n" + - "\\uD83D\\uDCF1 \\u624B\\u673A: http://" + ip + ":8016/\\n" + - "\\uD83D\\uDCDD \\u6587\\u7AE0: http://" + ip + ":1313/posts/\\n" + - "\\uD83D\\uDCBB \\u7535\\u8111: http://localhost:8016/\\n\\n" + - "/help \\u67E5\\u770B\\u5168\\u90E8\\u547D\\u4EE4" - ); - } - return sendMessage(chatId, "\\u274C \\u542F\\u52A8\\u8D85\\u65F6\\uFF0C\\u67E5\\u770B\\u65E5\\u5FD7 http://127.0.0.1:" + config.CONTROL_PORT + "/log"); -}; - -CMD_HANDLERS.stop = async function (chatId) { - const { stopWriteServer } = require("./write-server"); - if (stopWriteServer()) { - return sendMessage(chatId, "\\u26A1 Write stopped."); - } - return sendMessage(chatId, "\\u23F8 Write is not running."); -}; - -CMD_HANDLERS.status = async function (chatId) { - const s = getWriteStatus(); - const status = s.running ? "\\u2705 Running" : s.starting ? "\\u23F3 Starting..." : "\\u23F8 Stopped"; - const lines = ["Status: " + status]; - if (s.running) { - const ip = getLanIP(); - lines.push("Local: http://localhost:" + config.WRITE_PORT); - lines.push("Phone: http://" + ip + ":" + config.WRITE_PORT); - } - return sendMessage(chatId, lines.join("\\n")); -}; - -CMD_HANDLERS.ip = async function (chatId) { - const ip = getLanIP(); - return sendMessage(chatId, "Phone: http://" + ip + ":" + config.WRITE_PORT + " (" + config.PREFER_IFACE + ")"); -}; - -CMD_HANDLERS.help = async function (chatId) { - if (sessions.has(chatId)) { - return sendMessage(chatId, - "\\u270D \\u6B63\\u5728\\u5199\\u4F5C\\u4E2D\\n\\n" + - "\\u26A1 /publish \\u53D1\\u5E03 \\uD83D\\uDCC4 /draft \\u8349\\u7A3F \\uD83D\\uDD0D /preview \\u9884\\u89C8\\n" + - "\\u270F /title /categories /tags /author\\n" + - "\\u274C /cancel \\u4E22\\u5F03" - ); - } - return sendMessage(chatId, - "\\uD83E\\uDD16 WriteBot \\u547D\\u4EE4\\n\\n" + - "\\uD83D\\uDCDD \\u5199\\u4F5C:\\n" + - " /new \\u6807\\u9898 - \\u5F00\\u59CB\\u5199\\u4E00\\u7BC7\\n" + - " /edit slug - \\u7F16\\u8F91\\u5DF2\\u6709\\u6587\\u7AE0\\n" + - " /delete slug - \\u5220\\u9664\\u6587\\u7AE0\\n" + - " /list - \\u6587\\u7AE0\\u5217\\u8868\\n\\n" + - "\\uD83D\\uDD17 \\u53CB\\u94FE:\\n" + - " /links - \\u67E5\\u770B\\u53CB\\u94FE\\n" + - " /link_add url name - \\u6DFB\\u52A0\\u53CB\\u94FE\\n" + - " /link_del url - \\u5220\\u9664\\u53CB\\u94FE\\n" + - " /link_toggle url - \\u663E\\u793A/\\u9690\\u85CF\\n\\n" + - "\\uD83D\\uDCE1 \\u8BA2\\u9605:\\n" + - " /feeds - \\u67E5\\u770B\\u8BA2\\u9605\\u6E90\\n" + - " /feed_add url - \\u6DFB\\u52A0\\u8BA2\\u9605\\u6E90\\n" + - " /feed_del url - \\u5220\\u9664\\u8BA2\\u9605\\u6E90\\n\\n" + - "\\uD83D\\uDCCA /stats \\u7F51\\u7AD9\\u6570\\u636E" - ); -}; - -// ---------- Articles ---------- -CMD_HANDLERS.new = async function (chatId, title) { - if (!title) return sendMessage(chatId, "\\u683C\\u5F0F\\uFF1A\\u5199\\u4E00\\u7BC7 \\u6807\\u9898"); - const s = getSession(chatId); - s.title = title; - s.slug = makeSlug(title); - s.content = ""; - s.categories = []; - s.tags = []; - s.draft = true; - s.author = "qunlin"; - return sendMessage(chatId, - "\\uD83D\\uDCDD \\u5F00\\u59CB\\u5199\\uFF1A" + title + "\\n\\n" + - "\\u76F4\\u63A5\\u53D1\\u6D88\\u606F\\u5199\\u4F5C\\uFF0C\\u5B8C\\u6210\\u540E /publish\\n" + - "\\u968F\\u65F6 /preview \\u67E5\\u770B\\uFF0C/help \\u5168\\u90E8\\u547D\\u4EE4" - ); -}; - -CMD_HANDLERS.edit = async function (chatId, slug) { - if (!slug) return sendMessage(chatId, "\\u683C\\u5F0F\\uFF1A/edit slug\\n\\u5148\\u7528 /list \\u67E5\\u770B\\u6587\\u7AE0 slug\\u3002"); - try { - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/posts/" + slug); - if (!res.ok) throw new Error("\\u672A\\u627E\\u5230\\u8BE5\\u6587\\u7AE0"); - const post = await res.json(); - const s = getSession(chatId); - s.title = post.frontMatter.title; - s.slug = post.frontMatter.slug; - s.content = post.content || ""; - s.categories = post.frontMatter.categories || []; - s.tags = post.frontMatter.tags || []; - s.author = post.frontMatter.author || "qunlin"; - s.draft = post.frontMatter.draft; - return sendMessage(chatId, "\\u270F \\u7F16\\u8F91\\uFF1A" + s.title + "\\n\\n" + sessionSummary(s) + "\\n\\n\\u76F4\\u63A5\\u53D1\\u6D88\\u606F\\u8FFD\\u52A0\\u5185\\u5BB9\\uFF0C\\u5199\\u5B8C\\u53D1\\u300C\\u53D1\\u5E03\\u300D\\u3002"); - } catch (e) { - return sendMessage(chatId, "\\u274C " + e.message); - } -}; - -CMD_HANDLERS.delete = async function (chatId, slug) { - if (!slug) return sendMessage(chatId, "Usage: /delete \\nFind slugs with /list\\n\\nArticles are moved to recycle bin (30-day retention)."); - try { - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/posts/" + slug, { method: "DELETE" }); - if (!res.ok) throw new Error((await res.json()).error || "Delete failed"); - return sendMessage(chatId, "\\u2702 Moved to recycle bin: " + slug + "\\nRecoverable within 30 days."); - } catch (e) { - return sendMessage(chatId, "\\u274C " + e.message); - } -}; - -CMD_HANDLERS.view = async function (chatId, slug) { - if (!slug) return sendMessage(chatId, "Usage: /view \\nFind slugs with /list"); - const ip = getLanIP(); - const url = "http://" + ip + ":1313/" + slug + ".html"; - return sendMessage(chatId, "\\uD83D\\uDCC9 Preview\\n\\n" + url + "\\n\\n\\uD83D\\uDCBB http://localhost:1313/" + slug + ".html"); -}; - -CMD_HANDLERS.title = async function (chatId, newTitle) { - const s = getSession(chatId); - if (!newTitle) return sendMessage(chatId, "\\u683C\\u5F0F\\uFF1A\\u6807\\u9898 \\u65B0\\u6807\\u9898"); - s.title = newTitle; - s.slug = makeSlug(newTitle); - return sendMessage(chatId, "\\u270F \\u6807\\u9898\\u5DF2\\u66F4\\u65B0\\uFF1A" + newTitle + ""); -}; - -CMD_HANDLERS.categories = async function (chatId, cats) { - const s = getSession(chatId); - s.categories = cats ? cats.split(/[,;\"\\",\\"\\uFF0C\\u3001]+/).map(function(c){return c.trim();}).filter(Boolean) : []; - return sendMessage(chatId, "\\uD83D\\uDCC2 \\u5206\\u7C7B\\uFF1A" + (s.categories.join("\\u3001") || "\\u5DF2\\u6E05\\u7A7A")); -}; - -CMD_HANDLERS.tags = async function (chatId, tags) { - const s = getSession(chatId); - s.tags = tags ? tags.split(/[,;\"\\",\\"\\uFF0C\\u3001]+/).map(function(t){return t.trim();}).filter(Boolean) : []; - return sendMessage(chatId, "\\uD83C\\uDFF7 \\u6807\\u7B7E\\uFF1A" + (s.tags.join("\\u3001") || "\\u5DF2\\u6E05\\u7A7A")); -}; - -CMD_HANDLERS.author = async function (chatId, author) { - const s = getSession(chatId); - s.author = author || "qunlin"; - return sendMessage(chatId, "\\u270D \\u4F5C\\u8005\\uFF1A" + s.author); -}; - -CMD_HANDLERS.preview = async function (chatId) { - const s = sessions.get(chatId); - if (!s || !s.title) return sendMessage(chatId, "\\u8FD8\\u6CA1\\u5F00\\u59CB\\u5199\\u3002\\u53D1\\u300C\\u5199\\u4E00\\u7BC7\\u300D\\u5F00\\u59CB\\u3002"); - if (!s.content) return sendMessage(chatId, "\\u6B63\\u6587\\u4E3A\\u7A7A\\u3002"); - const maxLen = 3800; - const preview = s.content.slice(0, maxLen); - return sendMessage(chatId, "\\uD83D\\uDCDD " + s.title + "\\n\\n" + preview + (s.content.length > maxLen ? "\\n\\n... \\u524D " + maxLen + " \\u5B57" : "")); -}; - -CMD_HANDLERS.draft = async function (chatId) { - const s = sessions.get(chatId); - if (!s || !s.title) return sendMessage(chatId, "\\u8FD8\\u6CA1\\u5F00\\u59CB\\u5199\\u3002\\u53D1\\u300C\\u5199\\u4E00\\u7BC7\\u300D\\u5F00\\u59CB\\u3002"); - if (!s.content) return sendMessage(chatId, "\\u6B63\\u6587\\u4E3A\\u7A7A\\uFF0C\\u5148\\u5199\\u70B9\\u5185\\u5BB9\\u518D\\u4FDD\\u5B58\\u3002"); - try { - await saveViaAPI(s, true); - clearSession(chatId); - return sendMessage(chatId, "\\uD83D\\uDCC4 \\u8349\\u7A3F\\u5DF2\\u4FDD\\u5B58\\uFF1A" + s.title + ""); - } catch (e) { - return sendMessage(chatId, "\\u274C \\u4FDD\\u5B58\\u5931\\u8D25\\uFF1A" + e.message + "\\n\\n\\u8BF7\\u5148 /start \\u542F\\u52A8\\u540E\\u53F0\\u670D\\u52A1\\u3002"); - } -}; - -CMD_HANDLERS.publish = async function (chatId) { - const s = sessions.get(chatId); - if (!s || !s.title) return sendMessage(chatId, "\\u8FD8\\u6CA1\\u5F00\\u59CB\\u5199\\u3002\\u53D1\\u300C\\u5199\\u4E00\\u7BC7\\u300D\\u5F00\\u59CB\\u3002"); - if (!s.content) return sendMessage(chatId, "\\u6B63\\u6587\\u4E3A\\u7A7A\\uFF0C\\u5148\\u5199\\u70B9\\u5185\\u5BB9\\u518D\\u53D1\\u5E03\\u3002"); - try { - await saveViaAPI(s, false); - const slug = s.slug; - clearSession(chatId); - const ip = getLanIP(); - return sendMessage(chatId, "\\u2705 \\u5DF2\\u53D1\\u5E03\\uFF01\\n\\n\\uD83D\\uDCDD " + s.title + "\\n\\u9884\\u89C8\\uFF1Ahttp://localhost:1313/" + slug + ".html\\n\\u7EBF\\u4E0A\\uFF1Ahttps://usj.cc/" + slug + ".html"); - } catch (e) { - return sendMessage(chatId, "\\u274C \\u53D1\\u5E03\\u5931\\u8D25\\uFF1A" + e.message + "\\n\\n\\u8BF7\\u5148 /start \\u542F\\u52A8\\u540E\\u53F0\\u670D\\u52A1\\u3002"); - } -}; - -CMD_HANDLERS.cancel = async function (chatId) { - const s = sessions.get(chatId); - if (!s || !s.title) return sendMessage(chatId, "\\u6CA1\\u6709\\u6D3B\\u52A8\\u7684\\u8349\\u7A3F\\u3002"); - clearSession(chatId); - return sendMessage(chatId, "\\u2702 \\u8349\\u7A3F\\u5DF2\\u4E22\\u5F03\\u3002"); -}; - -CMD_HANDLERS.list = async function (chatId, pageStr) { - const ip = getLanIP(); - return sendMessage(chatId, - "\\uD83D\\uDCD1 \\u6587\\u7AE0\\u5217\\u8868\\n\\n" + - "\\uD83D\\uDCF1 \\u624B\\u673A\\u6253\\u5F00: http://" + ip + ":1313/posts/\\n" + - "\\uD83D\\uDCBB \\u7535\\u8111\\u6253\\u5F00: http://localhost:1313/posts/\\n\\n" + - "\\u2705 \\u65B0\\u5EFA: /new \\u300A\\u6807\\u9898\\u300B\\n" + - "\\uD83D\\uDCD1 \\u7F16\\u8F91: /edit slug\\n" + - "\\uD83D\\uDCC4 \\u8349\\u7A3F: /draft\\n" + - "\\uD83D\\uDE80 \\u53D1\\u5E03: /publish" - ); -}; - -// ---------- Links (友链) ---------- -CMD_HANDLERS.links = async function (chatId, pageStr) { - const page = parseInt(pageStr) || 1; - const perPage = 10; - try { - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/rss/links?all=1"); - const data = await res.json(); - const allLinks = data.links || []; - if (allLinks.length === 0) return sendMessage(chatId, "No links found."); - - const totalPages = Math.ceil(allLinks.length / perPage); - const p = page > totalPages ? totalPages : page; - const start = (p - 1) * perPage; - const pageLinks = allLinks.slice(start, start + perPage); - - const lines = ["\\uD83D\\uDD17 \\u53CB\\u94FE \\u5171" + allLinks.length + " \\u4E2A \\u00B7 \\u7B2C" + p + "/" + totalPages + " \\u9875"]; - pageLinks.forEach(function(l) { - const icon = l.hidden ? "\\uD83D\\uDD12" : "\\uD83C\\uDF10"; - const name = (l.name || l.url).slice(0, 40); - lines.push(icon + " " + name + " " + l.url); - }); - - const keyboard = { inline_keyboard: [] }; - const navRow = []; - if (p > 1) navRow.push({ text: "\\u25C0 \\u4E0A\\u4E00\\u9875", callback_data: "links_" + (p - 1) }); - if (p < totalPages) navRow.push({ text: "\\u4E0B\\u4E00\\u9875 \\u25B6", callback_data: "links_" + (p + 1) }); - if (navRow.length) keyboard.inline_keyboard.push(navRow); - - const payload = { - chat_id: chatId, - text: lines.join("\\n"), - parse_mode: "HTML", - reply_markup: navRow.length ? keyboard : undefined, - disable_web_page_preview: true, - }; - const { tg } = require("./tg-core"); - return tg("sendMessage", payload); - } catch (e) { - return sendMessage(chatId, "Please /start Write first."); - } -}; - -CMD_HANDLERS.link_add = async function (chatId, arg) { - if (!arg) return sendMessage(chatId, "Usage: /link_add \\nExample: /link_add https://example.com My Blog"); - const parts = arg.split(/\\s+/); - let url = parts[0]; - const name = parts.slice(1).join(" ") || url; - if (!url.startsWith("http")) url = "https://" + url; - try { - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/rss/links", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ url, name }), - }); - if (!res.ok) throw new Error((await res.json()).error || "Add failed"); - return sendMessage(chatId, "\\u2705 Link added: " + name + "\\n" + url); - } catch (e) { - return sendMessage(chatId, "\\u274C " + e.message); - } -}; - -CMD_HANDLERS.link_del = async function (chatId, url) { - if (!url) return sendMessage(chatId, "Usage: /link_del \\nUse /links to find the URL."); - try { - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/rss/links?url=" + encodeURIComponent(url), { - method: "DELETE", - }); - if (!res.ok) throw new Error((await res.json()).error || "Delete failed"); - return sendMessage(chatId, "\\u2705 Link deleted: " + url); - } catch (e) { - return sendMessage(chatId, "\\u274C " + e.message); - } -}; - -CMD_HANDLERS.link_toggle = async function (chatId, url) { - if (!url) return sendMessage(chatId, "Usage: /link_toggle \\nToggle visibility of a link."); - try { - const listRes = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/rss/links?all=1"); - const listData = await listRes.json(); - const link = (listData.links || []).find(function(l) { return l.url === url; }); - if (!link) return sendMessage(chatId, "\\u274C Link not found: " + url); - const newHidden = !link.hidden; - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/rss/links", { - method: "PUT", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ oldUrl: url, hidden: newHidden }), - }); - if (!res.ok) throw new Error((await res.json()).error || "Toggle failed"); - const label = newHidden ? "\\uD83D\\uDD12 Hidden" : "\\uD83C\\uDF10 Visible"; - return sendMessage(chatId, label + ": " + url); - } catch (e) { - return sendMessage(chatId, "\\u274C " + e.message); - } -}; - -// ---------- Feeds (订阅) ---------- -CMD_HANDLERS.feeds = async function (chatId, pageStr) { - const page = parseInt(pageStr) || 1; - const perPage = 10; - try { - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/rss/feeds"); - const data = await res.json(); - const allFeeds = data.feeds || []; - if (allFeeds.length === 0) return sendMessage(chatId, "No feeds configured."); - - const totalPages = Math.ceil(allFeeds.length / perPage); - const p = page > totalPages ? totalPages : page; - const start = (p - 1) * perPage; - const pageFeeds = allFeeds.slice(start, start + perPage); - - const lines = ["\\uD83D\\uDCE1 \\u8BA2\\u9605\\u6E90 \\u5171" + allFeeds.length + " \\u4E2A \\u00B7 \\u7B2C" + p + "/" + totalPages + " \\u9875"]; - pageFeeds.forEach(function(f) { - const title = (f.feedTitle || f.url).slice(0, 50); - lines.push("\\uD83D\\uDCDC " + title + "\\n " + f.url); - }); - - const keyboard = { inline_keyboard: [] }; - const navRow = []; - if (p > 1) navRow.push({ text: "\\u25C0 \\u4E0A\\u4E00\\u9875", callback_data: "feeds_" + (p - 1) }); - if (p < totalPages) navRow.push({ text: "\\u4E0B\\u4E00\\u9875 \\u25B6", callback_data: "feeds_" + (p + 1) }); - if (navRow.length) keyboard.inline_keyboard.push(navRow); - - const { tg } = require("./tg-core"); - return tg("sendMessage", { - chat_id: chatId, - text: lines.join("\\n"), - parse_mode: "HTML", - reply_markup: navRow.length ? keyboard : undefined, - disable_web_page_preview: true, - }); - } catch (e) { - return sendMessage(chatId, "Please /start Write first."); - } -}; - -CMD_HANDLERS.feed_add = async function (chatId, arg) { - if (!arg) return sendMessage(chatId, "Usage: /feed_add \\nExample: /feed_add https://example.com/rss.xml"); - const parts = arg.split(/\\s+/); - const url = parts[0]; - const feedTitle = parts.slice(1).join(" ") || ""; - try { - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/rss/feeds", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ url, feedTitle }), - }); - if (!res.ok) throw new Error((await res.json()).error || "Add failed"); - return sendMessage(chatId, "\\u2705 Feed added: " + url); - } catch (e) { - return sendMessage(chatId, "\\u274C " + e.message); - } -}; - -CMD_HANDLERS.feed_del = async function (chatId, url) { - if (!url) return sendMessage(chatId, "Usage: /feed_del \\nUse /feeds to find the URL."); - try { - const res = await fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/rss/feeds?url=" + encodeURIComponent(url), { - method: "DELETE", - }); - if (!res.ok) throw new Error((await res.json()).error || "Delete failed"); - return sendMessage(chatId, "\\u2705 Feed deleted: " + url); - } catch (e) { - return sendMessage(chatId, "\\u274C " + e.message); - } -}; - -// ---------- Stats ---------- -CMD_HANDLERS.stats = async function (chatId) { - try { - const [resPosts, resLinks, resFeeds] = await Promise.all([ - fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/posts?perPage=1"), - fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/rss/links?all=1"), - fetch("http://127.0.0.1:" + config.WRITE_PORT + "/api/rss/feeds"), - ]); - const postsData = await resPosts.json(); - const linksData = await resLinks.json(); - const feedsData = await resFeeds.json(); - - const totalPosts = postsData.totalCount || postsData.total || 0; - const totalLinks = (linksData.links || []).filter(function(l) { return !l.hidden; }).length; - const totalFeeds = (feedsData.feeds || []).length; - - return sendMessage(chatId, - "\\uD83D\\uDCCA \\u7F51\\u7AD9\\u6570\\u636E\\n\\n" + - "\\uD83D\\uDCDD \\u6587\\u7AE0\\uFF1A" + totalPosts + " \\u7BC7\\n" + - "\\uD83D\\uDD17 \\u53CB\\u94FE\\uFF1A" + totalLinks + " \\u4E2A\\n" + - "\\uD83D\\uDCE1 \\u8BA2\\u9605\\u6E90\\uFF1A" + totalFeeds + " \\u4E2A\\n\\n" + - "\\uD83C\\uDF10 usj.cc" - ); - } catch (e) { - return sendMessage(chatId, "\\u8BF7\\u5148 /start \\u542F\\u52A8\\u540E\\u53F0\\u670D\\u52A1\\u3002"); - } -}; - -module.exports = CMD_HANDLERS; diff --git a/bot/src/tg-poll.js b/bot/src/tg-poll.js deleted file mode 100644 index 2989b339..00000000 --- a/bot/src/tg-poll.js +++ /dev/null @@ -1,103 +0,0 @@ -// tg-poll.js - Telegram polling loop and message dispatch -const config = require("./config"); -const { tg, allowed, sendMessage, handleCallback } = require("./tg-core"); -const { sessions, getSession } = require("./sessions"); -const CMD_HANDLERS = require("./tg-handlers"); - -let lastOffset = 0; - -// Commands that accept an argument -const ARG_COMMANDS = new Set([ - "new", "title", "categories", "tags", "author", - "edit", "view", "delete", - "link_add", "link_del", "link_toggle", - "feed_add", "feed_del", - "list", "links", "feeds", -]); - -async function tgPoll() { - if (!config.TG_TOKEN) return; - try { - const data = await tg("getUpdates", { - offset: lastOffset + 1, - timeout: 30, - allowed_updates: ["message", "callback_query"], - }); - if (data.ok && data.result) { - for (const update of data.result) { - lastOffset = update.update_id; - - // Handle callback queries (inline buttons) - if (update.callback_query) { - const cb = update.callback_query; - const cbChatId = cb.message ? cb.message.chat.id : (cb.from ? cb.from.id : 0); - handleCallback(cbChatId, cb.id, cb.data, CMD_HANDLERS); - continue; - } - - const msg = update.message; - const chatId = msg.chat.id; - if (!allowed(chatId)) continue; - - // Handle photo in writing session - append as Markdown image - if (msg.photo && sessions.has(chatId)) { - try { - const s = getSession(chatId); - const largestPhoto = msg.photo[msg.photo.length - 1]; - const fileRes = await tg("getFile", { file_id: largestPhoto.file_id }); - if (fileRes.ok) { - const filePath = fileRes.result.file_path; - const caption = msg.caption || ""; - const imgUrl = "https://api.telegram.org/file/bot" + config.TG_TOKEN + "/" + filePath; - s.content += "\\n![" + (caption || "image") + "](" + imgUrl + ")\\n"; - } - } catch (e) { - console.error("[photo]", e.message); - } - continue; - } - - if (!msg?.text) continue; - - const fullText = msg.text ? msg.text.trim() : (msg.caption || "").trim(); - const parts = fullText.split("@")[0].split(/\\s+/); - const slashCmd = parts[0].toLowerCase().slice(1); - const slashArg = parts.slice(1).join(" "); - const hasSession = sessions.has(chatId); - - // 1) If in writing session and NOT a slash command, append text silently - if (hasSession && !fullText.startsWith("/")) { - try { - const s = getSession(chatId); - s.content += fullText + "\\n"; - } catch (e) { - console.error("[append]", e.message); - } - continue; - } - - // 2) Slash commands - each wrapped in try/catch - try { - if (ARG_COMMANDS.has(slashCmd)) { - const handler = CMD_HANDLERS[slashCmd]; - if (handler) await handler(chatId, slashArg); - else if (slashCmd) await sendMessage(chatId, "\\u274C Unknown: /" + slashCmd); - } else { - const handler = CMD_HANDLERS[slashCmd]; - if (handler) await handler(chatId); - else if (slashCmd) await sendMessage(chatId, "\\u274C Unknown: /" + slashCmd); - } - } catch (e) { - console.error("[handler " + slashCmd + "]", e.message); - try { await sendMessage(chatId, "\\u274C " + (e.message || "error").slice(0, 200)); } catch {} - } - } - } - } catch (e) { - console.error("[tg poll]", e.message); - await new Promise((r) => setTimeout(r, 3000)); - } - setImmediate(tgPoll); -} - -module.exports = { tgPoll }; diff --git a/bot/src/write-server.js b/bot/src/write-server.js deleted file mode 100644 index 1e02401e..00000000 --- a/bot/src/write-server.js +++ /dev/null @@ -1,118 +0,0 @@ -// write-server.js - Manage the Write (Next.js) and Hugo processes -const { spawn, execSync } = require("child_process"); -const config = require("./config"); -const { waitForReady } = require("./helpers"); - -let writeProcess = null; -let writeReady = false; -let hugoProcess = null; - -function startWriteServer() { - if (writeProcess) { - return { started: false, reason: writeReady ? "already_running" : "starting" }; - } - writeReady = false; - const isWin = process.platform === "win32"; - writeProcess = spawn( - isWin ? "npm.cmd" : "npm", - ["run", "dev", "--", "-p", String(config.WRITE_PORT), "-H", "0.0.0.0"], - { cwd: config.WRITE_DIR, shell: isWin, stdio: "pipe", windowsHide: true } - ); - writeProcess.stdout?.on("data", (d) => process.stdout.write(d)); - writeProcess.stderr?.on("data", (d) => process.stderr.write(d)); - writeProcess.on("exit", (code) => { - console.log("[write] exited " + code); - writeProcess = null; - writeReady = false; - }); - return { started: true }; -} - -function stopWriteServer() { - if (hugoProcess) { - try { - const hpid = hugoProcess.pid; - if (process.platform === "win32" && hpid) { - execSync("taskkill /PID " + hpid + " /T /F", { stdio: "ignore" }); - } else { - hugoProcess.kill(); - } - } catch {} - hugoProcess = null; - } - if (!writeProcess) return false; - const pid = writeProcess.pid; - try { - if (process.platform === "win32" && pid) { - execSync("taskkill /PID " + pid + " /T /F", { stdio: "ignore" }); - } else { - writeProcess.kill(); - } - } catch {} - writeProcess = null; - writeReady = false; - return true; -} - -function startHugoIfNeeded() { - if (hugoProcess) return; - try { - hugoProcess = spawn("hugo", ["server", "--bind", "0.0.0.0", "--port", "1313", "--disableFastRender"], { - cwd: config.WRITE_DIR + "/..", - shell: true, - stdio: "pipe", - windowsHide: true, - }); - hugoProcess.on("exit", (code) => { - console.log("[hugo] exited " + code); - hugoProcess = null; - }); - console.log("[hugo] started on 0.0.0.0:1313"); - } catch (e) { - console.error("[hugo]", e.message); - } -} - -function getWriteStatus() { - return { - running: !!(writeProcess && writeReady), - starting: !!(writeProcess && !writeReady), - }; -} - -async function ensureWriteReady(chatId, sendMessage) { - if (writeProcess && writeReady) { - startHugoIfNeeded(); - return true; - } - if (writeProcess && !writeReady) { - await sendMessage(chatId, "\\u231B Write \\u6B63\\u5728\\u542F\\u52A8\\u4E2D..."); - const ready = await waitForReady(config.WRITE_PORT); - if (ready) { - writeReady = true; - startHugoIfNeeded(); - return true; - } - return false; - } - const result = startWriteServer(); - if (!result.started) return false; - await sendMessage(chatId, "\\u231B \\u6B63\\u5728\\u542F\\u52A8 Write..."); - const ready = await waitForReady(config.WRITE_PORT); - if (ready) { - writeReady = true; - startHugoIfNeeded(); - return true; - } - return false; -} - -module.exports = { - get writeReady() { return writeReady; }, - get writePort() { return config.WRITE_PORT; }, - startWriteServer, - stopWriteServer, - startHugoIfNeeded, - getWriteStatus, - ensureWriteReady, -}; diff --git a/bot/start.bat b/bot/start.bat deleted file mode 100644 index 5ed68ea4..00000000 --- a/bot/start.bat +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -chcp 65001 >nul -cd /d "%~dp0" -node index.js -pause diff --git a/bot/writebot.ico b/bot/writebot.ico deleted file mode 100644 index 8a595e74..00000000 Binary files a/bot/writebot.ico and /dev/null differ