/** * 配置区域 */ const CONFIG = { // Notion 配置 NOTION_DATABASE_ID: "修改为对应的NOTION_DATABASE_ID", NOTION_API_KEY: "修改为对应的NOTION_API_KEY", // Telegram 配置 TELEGRAM_BOT_TOKEN: "修改为对应的TELEGRAM_BOT_TOKEN", ALLOWED_USER_IDS: ["修改为对应的ALLOWED_USER_IDS"], // 基础配置 CITY: "Portland", STATE_FULL: "Oregon", ZIP_CODE: "97212" }; export default { async fetch(request, env, ctx) { if (request.method === "GET") { const data = await handleRedfinAddress(null, true); return new Response(JSON.stringify(data, null, 2), { headers: { "Content-Type": "application/json; charset=utf-8" } }); } if (request.method !== "POST") return new Response("Bot is running!", { status: 200 }); try { const update = await request.json(); // --- 1. 鉴权 --- const userId = getUserId(update); if (userId && !CONFIG.ALLOWED_USER_IDS.includes(String(userId))) { return new Response("Unauthorized", { status: 200 }); } // --- 2. 消息处理 --- if (update.message && update.message.text) { const text = update.message.text.trim(); const chatId = update.message.chat.id; if (text === "/start") { await sendWelcomeMenu(chatId); } else if (text === "📦 立即取号" || text === "取号" || text === "/get") { await handleGetAccount(chatId); } else if (text === "📊 查询库存" || text === "库存" || text === "/stock") { await handleCheckStock(chatId); } else if (text === "🏠 随机地址" || text === "地址" || text === "/address") { await handleRedfinAddress(chatId); } } // --- 3. 按钮回调处理 --- else if (update.callback_query) { await handleCallback(update.callback_query); } } catch (e) { console.error(e); } return new Response("OK", { status: 200 }); } }; // --- 辅助函数 --- function getUserId(update) { if (update.message) return update.message.from.id; if (update.callback_query) return update.callback_query.from.id; return null; } // 菜单 async function sendWelcomeMenu(chatId) { const text = "👋 欢迎回来!\n\n请点击下方按钮开始操作:"; const keyboard = { keyboard: [ [{ text: "📦 立即取号" }, { text: "📊 查询库存" }], [{ text: "🏠 随机地址" }] ], resize_keyboard: true, is_persistent: true }; await sendMessage(chatId, text, keyboard); } // --- 核心业务逻辑 --- // 1. 处理地址抓取 (👑 终极降维打击版:Overpass API 数据库直连) async function handleRedfinAddress(chatId, isJsonOnly = false) { try { // 💡 放弃搜索,改为“数据库框选” // (45.530,-122.665,45.555,-122.615) 是完美覆盖 Portland 97212 的地理矩形坐标 // 查询指令:在这个矩形内,找出所有带有 addr:housenumber (门牌号) 和 addr:street (街道) 的真实房屋节点,一次提取 300 个 const bbox = "45.530,-122.665,45.555,-122.615"; const overpassQuery = `[out:json];node(${bbox})["addr:housenumber"]["addr:street"];out 300;`; // 使用官方与备用节点,防止单点故障 const endpoints = [ "https://overpass-api.de/api/interpreter", "https://lz4.overpass-api.de/api/interpreter" ]; const apiUrl = `${endpoints[Math.floor(Math.random() * endpoints.length)]}?data=${encodeURIComponent(overpassQuery)}`; const response = await fetch(apiUrl, { headers: { // 生成随机 User-Agent 防止被识别为同一来源限制 "User-Agent": `TelegramAddressBot_CF_${Math.floor(Math.random()*100000)}`, "Accept": "application/json" } }); if (!response.ok) { throw new Error(`API 拒绝响应: ${response.status}`); } const data = await response.json(); const houses = data.elements; if (!houses || houses.length === 0) { throw new Error("区域内未提取到有效建筑数据"); } // --- 成功获取!--- // 从一次性拿到的 300 套真实房屋中,随机挑一套 const randomHouse = houses[Math.floor(Math.random() * houses.length)]; const tags = randomHouse.tags; // 精准组装地址 const street = `${tags["addr:housenumber"]} ${tags["addr:street"]}`; const city = tags["addr:city"] || CONFIG.CITY; const state = "OR"; const zip = tags["addr:postcode"] || CONFIG.ZIP_CODE; const fullAddr = `${street}, ${city}, ${state} ${zip}`; const result = { address: street, city: city, state: state, zip: zip, fullAddress: fullAddr }; if (!isJsonOnly && chatId) { const msg = `🏠 随机房源地址\n\n📍 ${result.fullAddress}`; await sendMessage(chatId, msg); } return result; } catch (e) { if (chatId) { await sendMessage(chatId, `❌ 获取失败: ${e.message} (请稍后再试)`); } return { error: e.message }; } } // 2. 处理取号 async function handleGetAccount(chatId) { const [page, stockCount] = await Promise.all([ queryNotionUnused(), getStockCount() ]); if (!page) { return sendMessage(chatId, "⚠️ 库存不足:当前没有“未使用”的账号。"); } const props = page.properties; const pageId = page.id; const account = props["账号"]?.formula?.string || props["账号"]?.rich_text?.[0]?.plain_text || props["账号"]?.title?.[0]?.plain_text || "无账号"; const password = props["密码"]?.formula?.string || props["密码"]?.rich_text?.[0]?.plain_text || "无密码"; // 读取创建时间 let createTimeRaw = props["创建时间"]?.created_time || props["创建时间"]?.date?.start || "未知"; let createTimeStr = "未知"; if (createTimeRaw !== "未知") { const dateObj = new Date(createTimeRaw); createTimeStr = dateObj.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }).replace(/\//g, '-'); } const messageText = `📦 提取成功! (📊 库存: ${stockCount})\n\n` + `账号:${account}\n` + `密码:${password}\n` + `组合:${account}----${password}\n` + `Xbox创建时间:${createTimeStr}\n\n` + `👇 请选择操作:`; const inlineKeyboard = { inline_keyboard: [ [ { text: "✅ 兑换码", callback_data: `redeem|${pageId}` }, { text: "🔘 其他", callback_data: `other|${pageId}` } ], [ { text: "❌ 取消", callback_data: `cancel|${pageId}` } ] ] }; await sendMessage(chatId, messageText, inlineKeyboard); } // 3. 处理库存查询 async function handleCheckStock(chatId) { const count = await getStockCount(); const text = `📊 库存查询结果\n\n` + `当前剩余可用白号:${count} 个`; await sendMessage(chatId, text); } // 4. 处理按钮回调 async function handleCallback(query) { const [action, pageId] = query.data.split("|"); const chatId = query.message.chat.id; const messageId = query.message.message_id; if (action === "cancel") { await deleteMessage(chatId, messageId); return; } let originalText = query.message.text || ""; originalText = originalText.replace(/👇.*/s, "").trim(); // 🔄 重新给账号、密码、组合、地址加上 标签 originalText = originalText.replace(/(账号:)(.+)/, '$1$2'); originalText = originalText.replace(/(密码:)(.+)/, '$1$2'); originalText = originalText.replace(/(组合:)(.+)/, '$1$2'); originalText = originalText.replace(/(📍)(.+)/, '$1$2'); let newStatus = action === "redeem" ? "兑换码" : "其他"; let statusText = action === "redeem" ? "✅ 已标记为:兑换码" : "🔘 已标记为:其他"; const nowStr = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false }); const success = await updateNotionPage(pageId, newStatus); if (success) { const finalText = `${originalText}\n\n${statusText}\n🕒 使用时间:${nowStr}`; await editMessage(chatId, messageId, finalText); await answerCallback(query.id, "操作成功!"); } else { await answerCallback(query.id, "❌ Notion 更新失败"); } } // --- Notion API --- async function queryNotionUnused() { const url = `https://api.notion.com/v1/databases/${CONFIG.NOTION_DATABASE_ID}/query`; const response = await fetch(url, { method: "POST", headers: { "Authorization": `Bearer ${CONFIG.NOTION_API_KEY}`, "Notion-Version": "2022-06-28", "Content-Type": "application/json" }, body: JSON.stringify({ "filter": { "property": "状态", "select": { "is_empty": true } }, // 仅筛选空状态 "sorts": [{ "property": "创建时间", "direction": "ascending" }], "page_size": 1 }) }); const data = await response.json(); if (data.results && data.results.length > 0) return data.results[0]; return null; } async function getStockCount() { const url = `https://api.notion.com/v1/databases/${CONFIG.NOTION_DATABASE_ID}/query`; try { const response = await fetch(url, { method: "POST", headers: { "Authorization": `Bearer ${CONFIG.NOTION_API_KEY}`, "Notion-Version": "2022-06-28", "Content-Type": "application/json" }, body: JSON.stringify({ "filter": { "property": "状态", "select": { "is_empty": true } }, // 仅筛选空状态 "page_size": 100 }) }); const data = await response.json(); return data.results ? data.results.length : 0; } catch (e) { return "未知"; } } async function updateNotionPage(pageId, statusName) { const url = `https://api.notion.com/v1/pages/${pageId}`; const now = new Date().toISOString(); const response = await fetch(url, { method: "PATCH", headers: { "Authorization": `Bearer ${CONFIG.NOTION_API_KEY}`, "Notion-Version": "2022-06-28", "Content-Type": "application/json" }, body: JSON.stringify({ "properties": { "状态": { "select": { "name": statusName } }, "使用日期": { "date": { "start": now } } } }) }); return response.ok; } // --- Telegram API --- async function sendMessage(chatId, text, replyMarkup = null) { const url = `https://api.telegram.org/bot${CONFIG.TELEGRAM_BOT_TOKEN}/sendMessage`; const body = { chat_id: chatId, text: text, parse_mode: "HTML" }; if (replyMarkup) body.reply_markup = replyMarkup; await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); } async function deleteMessage(chatId, messageId) { const url = `https://api.telegram.org/bot${CONFIG.TELEGRAM_BOT_TOKEN}/deleteMessage`; await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ chat_id: chatId, message_id: messageId }) }); } async function editMessage(chatId, messageId, text) { const url = `https://api.telegram.org/bot${CONFIG.TELEGRAM_BOT_TOKEN}/editMessageText`; await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ chat_id: chatId, message_id: messageId, text: text, parse_mode: "HTML" }) }); } async function answerCallback(id, text) { const url = `https://api.telegram.org/bot${CONFIG.TELEGRAM_BOT_TOKEN}/answerCallbackQuery`; await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ callback_query_id: id, text: text }) }); }