├── img ├── logo.png ├── mobile1.png ├── mobile2.png └── background.png ├── LICENSE ├── README.md └── _worker@1.0.js /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o2sky/CF-Domain-AutoCheck/HEAD/img/logo.png -------------------------------------------------------------------------------- /img/mobile1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o2sky/CF-Domain-AutoCheck/HEAD/img/mobile1.png -------------------------------------------------------------------------------- /img/mobile2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o2sky/CF-Domain-AutoCheck/HEAD/img/mobile2.png -------------------------------------------------------------------------------- /img/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/o2sky/CF-Domain-AutoCheck/HEAD/img/background.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Faiz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🌍CF-Domain-Autocheck 2 | 3 |
4 | 修复日志 5 | 2025-08-29
6 | 1.添加了whoisjson的api,支持一级域名自动查询信息并填写
7 | 2.优化分类功能,单独设立分类管理功能
8 | 3.增添统计功能,可以显示分类数量以及分类下的域名数量
9 | 4.整合telegram多条信息为一条 10 |
11 | 2025-08-13
12 | 1.修复到期日期会根据用户输入的注册时间和续期周期自动计算
13 | 2.修复清除续期后,到期日期自动回调
14 |
15 | 2025-08-06
16 | 1.修复手机端背景图会依页面内容自动放大或缩小
17 | 2.新增可自定义手机端背景图 18 |
19 | 20 | ## 🚨本项目主要是和Ai沟通创作而成,小伙伴可自行进行完善或魔改🚨 21 | 22 | * 项目是部署在Cloudflare平台的,作用只有监控域名的到期情况。 23 | * 主要是针对那些白嫖的域名,例如`dpdns.org`之类的,可能有人注册了好几个,需要定期点击续期,或者有些白嫖的一年的域名,可以帮助进行到期监控,避免忘记到期时间。 24 | * 主要功能:日期监控、价格记录、注册商记录、自定义标签、自定义续费链接、telegram提前通知。 25 | > ⚠️ 想要vps监控功能的小伙伴请看这里:https://github.com/kamanfaiz/CF-Server-AutoCheck ⚠️ 26 | 27 | ## 💻界面展示 28 | | 登录界面 | 监控界面 | 29 | |:-------------------------------------------------------------------------:|:----------------------------------------------------------------:| 30 | | ![](https://imgr2.952536.xyz/Hexo/Article/PixPin_2025-07-26_23-05-27.png) | ![](https://imgr2.952536.xyz/Hexo/Article/20250829173210152.png) | 31 | 32 | ## 📌显示逻辑 33 | ### 一、卡头标签显示逻辑 34 | | 判定条件 | 标签状态 | 35 | |:-----------------|:-----------| 36 | | 剩余天数小于1天 | ❌已过期 | 37 | | 剩余天数为1-20天 | 📢即将过期 | 38 | | 剩余天数大于20天 | ✅正常 | 39 | 40 | ### 二、卡片进度条显示逻辑 41 | | 判定条件 | 进度条状态 | 42 | |:--------------------------|:-----------| 43 | | 剩余天数小于周期的10% | 🔴已过期 | 44 | | 剩余天数是周期的10%-30% | 🟡即将过期 | 45 | | 剩余天数大于等于周期的30% | 🟢正常 | 46 | 47 | ## 🚀简易部署流程,若需要详细流程,请移步👉[Faiz博客](https://blog.faiz.hidns.co/2025/07/26/Domain-AutoCheck%E5%9F%9F%E5%90%8D%E5%88%B0%E6%9C%9F%E7%9B%91%E6%8E%A7/) 48 | 1. 创建workers,粘贴代码 49 | > 1.0版本和2.0版本都行,2.0版本支持一级域名自动查询,且细化单独分类;1.0版本为纯手动,且分类是以注册商进行简单分类 50 | 2. 创建一个KV,名字可以随便取 51 | 3. 绑定KV,变量名称:`DOMAIN_MONITOR`,注意大写,怕填错就复制粘贴,KV命名空间就下拉菜单选择刚才创建的KV名 52 | 4. 绑定自定义域名 53 | 5. 设定环境变量,cloudflare环境变量名如下: 54 | 55 | > 先级都是:Cloudflare环境变量>代码中的变量>默认值🚨 56 | 57 | | 名称 | 示例 | 必填 | 备注 | 58 | |:------------------|:-------------------------------------------------------------------------------|:----:|:-----------------------------------------| 59 | | TOKEN | 默认是domain | ✅️ | 登录密码,最好自定义,不填则默认是domain | 60 | | TG_TOKEN | telegram找[@BotFather](https://t.me/BotFather)获取 | ❌️ | 可在界面后端配置 | 61 | | TG_ID | telegram找[@userinfobot](https://t.me/userinfobot)获取,或者群机器人也行 | ❌️ | 可在界面后端配置 | 62 | | SITE_NAME | 默认为域名到期监控 | ❌️ | 不填,默认就是域名到期监控 | 63 | | LOGO_URL | https://123abc.com/logo.svg | ❌️ | 网站logo,有需要可自行设置 | 64 | | BACKGROUND_URL | https://123abc.com/img.jpg | ❌️ | 背景图,有需要的可以自己设置 | 65 | | WHOISJSON_API_KEY | 去[WHOISJSON](https://whoisjson.com/)平台免费注册获取API,每个月免费1000次查询 | ❌️ | 仅支持一级域名的自动查询,部署2.0的可以获取 | 66 | 67 | 6. 按照上述变量名添加完telegram变量后,点击`设置`——点击`触发事件`——点击`添加`——选择`cron触发器`——选择`一周中的某一天`——自定义时间 68 | > 🚨这里面的时间不是北京时间,是UTC时间,与北京时间相差8小时,例如设置为00:00,那么会在北京时间08:00进行通知🚨 69 | 70 | ## ♻️代码更新方式 71 | 功能基本没有问题,一般不太会大更新功能方面的内容,只可能修复一些用户体验类的bug,这些都不影响整体功能的使用,只有强迫症患者才需要更新。如果代码更新了,只需要重新复制粘贴代码可,因为域名数据都是储存在KV里面的,只要不动KV空间,就不会出现数据丢失。 72 | > 🚨如果是喜欢在代码中填写变量的小伙伴,记得重新复制代码前保存好自己设置的变量。 73 | -------------------------------------------------------------------------------- /_worker@1.0.js: -------------------------------------------------------------------------------- 1 | /*域名监控系统 - Cloudflare Workers*/ 2 | /*使用KV存储域名信息*/ 3 | 4 | // iconfont阿里巴巴图标库 5 | const ICONFONT_CSS = '//at.alicdn.com/t/c/font_4973034_1qunj5fctpb.css'; 6 | const ICONFONT_JS = '//at.alicdn.com/t/c/font_4973034_1qunj5fctpb.js'; 7 | 8 | // 网站图标和背景图片,可在环境变量中设置 9 | const DEFAULT_LOGO = 'https://cdn.jsdelivr.net/gh/kamanfaiz/CF-Domain-AutoCheck@main/img/logo.png'; // 默认Logo图片,外置变量名为LOGO_URL 10 | const DEFAULT_BACKGROUND = 'https://cdn.jsdelivr.net/gh/kamanfaiz/CF-Domain-AutoCheck@main/img/background.png'; // 默认背景图片,外置变量名为BACKGROUND_URL 11 | const DEFAULT_MOBILE_BACKGROUND = 'https://cdn.jsdelivr.net/gh/kamanfaiz/CF-Domain-AutoCheck@main/img/mobile2.png'; // 默认手机端背景图片,留空则使用桌面端背景图片,外置变量名为MOBILE_BACKGROUND_URL 12 | 13 | // 登录密码设置 14 | const DEFAULT_TOKEN = ''; // 在此处设置默认密码,留空则使用'domain',外置变量名为TOKEN 15 | 16 | // Telegram通知配置 17 | const DEFAULT_TG_TOKEN = ''; // 你的Telegram机器人Token,留空则尝试读取环境变量中TG_TOKEN的值 18 | const DEFAULT_TG_ID = ''; // 你的Telegram聊天ID,留空则尝试读取环境变量中TG_ID的值 19 | 20 | // 网站标题配置 21 | const DEFAULT_SITE_NAME = ''; // 默认网站标题,外置环境变量名为SITE_NAME 22 | 23 | // 登录页HTML模板 24 | const getLoginHTML = (title) => ` 25 | 26 | 27 | 28 | 29 | 30 | ${title} 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 190 | 191 | 192 | 193 | 194 | 195 |
196 |
197 | 201 |
202 |
203 | 204 |
205 | 206 |
密码错误,请重试
207 |
208 |
209 |
210 | 211 | 239 | 240 | 241 | `; 242 | 243 | // HTML模板 244 | const getHTMLContent = (title) => ` 245 | 246 | 247 | 248 | 249 | 250 | ${title} 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 266 | 1539 | 1540 | 1541 |
1542 | 1543 | 1560 | 1561 | 1562 | 1596 | 1597 |
1598 | 1599 |
1600 |
1601 |
1602 |
1603 |
1604 | 1605 | 1606 | 1737 | 1738 | 1739 | 1783 | 1784 | 1785 | 1802 | 1803 | 1804 | 1836 | 1837 | 1838 | 1839 | 3317 | 3318 | 3319 | `; 3320 | 3321 | // 处理请求 3322 | async function handleRequest(request) { 3323 | const url = new URL(request.url); 3324 | const path = url.pathname; 3325 | 3326 | // 检查是否已配置KV空间 3327 | if (!isKVConfigured()) { 3328 | // 如果请求是"完成设置"按钮的操作 3329 | if (path === '/setup-complete') { 3330 | return Response.redirect(url.origin, 302); 3331 | } 3332 | 3333 | // 显示设置向导页面 3334 | return new Response(getSetupHTML(), { 3335 | headers: { 3336 | 'Content-Type': 'text/html;charset=UTF-8', 3337 | }, 3338 | }); 3339 | } 3340 | 3341 | // 获取标题 3342 | // 优先级:环境变量 > 代码变量 > 默认值'域名到期监控' 3343 | let siteTitle = '域名到期监控'; 3344 | if (typeof SITE_NAME !== 'undefined' && SITE_NAME) { 3345 | siteTitle = SITE_NAME; 3346 | } else if (DEFAULT_SITE_NAME) { 3347 | siteTitle = DEFAULT_SITE_NAME; 3348 | } 3349 | 3350 | // 获取正确的密码 3351 | // 优先级:环境变量 > 代码变量 > 默认密码'domain' 3352 | let correctPassword = 'domain'; 3353 | if (typeof TOKEN !== 'undefined' && TOKEN) { 3354 | correctPassword = TOKEN; 3355 | } else if (DEFAULT_TOKEN) { 3356 | correctPassword = DEFAULT_TOKEN; 3357 | } 3358 | 3359 | // 检查是否已经登录(通过cookie) 3360 | const cookieHeader = request.headers.get('Cookie') || ''; 3361 | const isAuthenticated = cookieHeader.includes('auth=true'); 3362 | 3363 | // 处理登录POST请求 3364 | if (path === '/login' && request.method === 'POST') { 3365 | try { 3366 | const requestData = await request.json(); 3367 | const submittedPassword = requestData.password; 3368 | 3369 | if (submittedPassword === correctPassword) { 3370 | // 密码正确,设置cookie并重定向到dashboard 3371 | return new Response(JSON.stringify({ success: true }), { 3372 | status: 200, 3373 | headers: { 3374 | 'Content-Type': 'application/json', 3375 | 'Set-Cookie': 'auth=true; Path=/; HttpOnly; SameSite=Strict; Max-Age=86400', // 24小时过期 3376 | }, 3377 | }); 3378 | } else { 3379 | // 密码错误 3380 | return new Response(JSON.stringify({ success: false, error: '密码错误' }), { 3381 | status: 401, 3382 | headers: { 3383 | 'Content-Type': 'application/json', 3384 | }, 3385 | }); 3386 | } 3387 | } catch (error) { 3388 | return new Response(JSON.stringify({ success: false, error: '请求格式错误' }), { 3389 | status: 400, 3390 | headers: { 3391 | 'Content-Type': 'application/json', 3392 | }, 3393 | }); 3394 | } 3395 | } 3396 | 3397 | // 处理dashboard页面请求 3398 | if (path === '/dashboard') { 3399 | if (isAuthenticated) { 3400 | // 已登录,显示主页面 3401 | const htmlContent = getHTMLContent(siteTitle); 3402 | const response = new Response(htmlContent, { 3403 | headers: { 3404 | 'Content-Type': 'text/html;charset=UTF-8', 3405 | }, 3406 | }); 3407 | 3408 | return await addFooterToResponse(response); 3409 | } else { 3410 | // 未登录,重定向到登录页面 3411 | return Response.redirect(url.origin, 302); 3412 | } 3413 | } 3414 | 3415 | // 登出功能 3416 | if (path === '/logout') { 3417 | return new Response('登出成功', { 3418 | status: 302, 3419 | headers: { 3420 | 'Location': '/', 3421 | 'Set-Cookie': 'auth=; Path=/; HttpOnly; SameSite=Strict; Max-Age=0', // 清除cookie 3422 | }, 3423 | }); 3424 | } 3425 | 3426 | // 根路径或任何其他路径(除了/api和/dashboard)都显示登录页面 3427 | if (path === '/' || (!path.startsWith('/api/') && path !== '/dashboard')) { 3428 | // 如果已登录,重定向到dashboard 3429 | if (isAuthenticated) { 3430 | return Response.redirect(`${url.origin}/dashboard`, 302); 3431 | } 3432 | 3433 | const loginHtml = getLoginHTML(siteTitle); 3434 | return new Response(loginHtml, { 3435 | headers: { 3436 | 'Content-Type': 'text/html;charset=UTF-8', 3437 | }, 3438 | }); 3439 | } 3440 | 3441 | // API 路由处理 3442 | if (path.startsWith('/api/')) { 3443 | // 检查是否已登录 3444 | if (!isAuthenticated) { 3445 | return jsonResponse({ error: '未授权访问', success: false }, 401); 3446 | } 3447 | 3448 | return await handleApiRequest(request); 3449 | } 3450 | 3451 | // 如果都不匹配,返回登录页面 3452 | const loginHtml = getLoginHTML(siteTitle); 3453 | return new Response(loginHtml, { 3454 | headers: { 3455 | 'Content-Type': 'text/html;charset=UTF-8', 3456 | }, 3457 | }); 3458 | } 3459 | 3460 | 3461 | 3462 | // 处理API请求 3463 | async function handleApiRequest(request) { 3464 | const url = new URL(request.url); 3465 | const path = url.pathname; 3466 | 3467 | // 获取所有域名 3468 | if (path === '/api/domains' && request.method === 'GET') { 3469 | try { 3470 | const domains = await getDomains(); 3471 | return jsonResponse(domains); 3472 | } catch (error) { 3473 | return jsonResponse({ error: '获取域名列表失败' }, 500); 3474 | } 3475 | } 3476 | 3477 | // 添加新域名 3478 | if (path === '/api/domains' && request.method === 'POST') { 3479 | try { 3480 | const domainData = await request.json(); 3481 | const domain = await addDomain(domainData); 3482 | return jsonResponse(domain, 201); 3483 | } catch (error) { 3484 | return jsonResponse({ error: '添加域名失败' }, 400); 3485 | } 3486 | } 3487 | 3488 | // 更新域名 3489 | if (path.match(/^\/api\/domains\/[^\/]+$/) && request.method === 'PUT') { 3490 | const id = path.split('/').pop(); 3491 | try { 3492 | const domainData = await request.json(); 3493 | const domain = await updateDomain(id, domainData); 3494 | return jsonResponse(domain); 3495 | } catch (error) { 3496 | return jsonResponse({ error: '更新域名失败' }, 400); 3497 | } 3498 | } 3499 | 3500 | // 删除域名 3501 | if (path.match(/^\/api\/domains\/[^\/]+$/) && request.method === 'DELETE') { 3502 | const id = path.split('/').pop(); 3503 | try { 3504 | await deleteDomain(id); 3505 | return jsonResponse({ success: true }); 3506 | } catch (error) { 3507 | return jsonResponse({ error: '删除域名失败' }, 400); 3508 | } 3509 | } 3510 | 3511 | // 域名续期 3512 | if (path.match(/^\/api\/domains\/[^\/]+\/renew$/) && request.method === 'POST') { 3513 | const id = path.split('/')[3]; 3514 | try { 3515 | const renewData = await request.json(); 3516 | const domain = await renewDomain(id, renewData); 3517 | return jsonResponse(domain); 3518 | } catch (error) { 3519 | return jsonResponse({ error: '域名续期失败' }, 400); 3520 | } 3521 | } 3522 | 3523 | // 获取Telegram配置 3524 | if (path === '/api/telegram/config' && request.method === 'GET') { 3525 | try { 3526 | const config = await getTelegramConfig(); 3527 | return jsonResponse(config); 3528 | } catch (error) { 3529 | return jsonResponse({ error: '获取Telegram配置失败' }, 500); 3530 | } 3531 | } 3532 | 3533 | // 保存Telegram配置 3534 | if (path === '/api/telegram/config' && request.method === 'POST') { 3535 | try { 3536 | const configData = await request.json(); 3537 | const config = await saveTelegramConfig(configData); 3538 | return jsonResponse(config); 3539 | } catch (error) { 3540 | console.error('保存Telegram配置失败:', error); 3541 | return jsonResponse({ error: '保存Telegram配置失败: ' + error.message }, 400); 3542 | } 3543 | } 3544 | 3545 | // 测试Telegram通知 3546 | if (path === '/api/telegram/test' && request.method === 'POST') { 3547 | try { 3548 | const result = await testTelegramNotification(); 3549 | return jsonResponse(result); 3550 | } catch (error) { 3551 | return jsonResponse({ error: '测试Telegram通知失败: ' + error.message }, 400); 3552 | } 3553 | } 3554 | 3555 | // 测试单个域名的通知 3556 | if (path.match(/^\/api\/domains\/[^\/]+\/test-notify$/) && request.method === 'POST') { 3557 | const id = path.split('/')[3]; 3558 | try { 3559 | const result = await testSingleDomainNotification(id); 3560 | return jsonResponse(result); 3561 | } catch (error) { 3562 | return jsonResponse({ error: '测试通知失败: ' + error.message }, 400); 3563 | } 3564 | } 3565 | 3566 | // 404 - 路由不存在 3567 | return jsonResponse({ error: '未找到请求的资源' }, 404); 3568 | } 3569 | 3570 | // 获取所有域名 3571 | async function getDomains() { 3572 | const domainsStr = await DOMAIN_MONITOR.get('domains') || '[]'; 3573 | return JSON.parse(domainsStr); 3574 | } 3575 | 3576 | // 添加新域名 3577 | async function addDomain(domainData) { 3578 | const domains = await getDomains(); 3579 | 3580 | // 验证域名数据 3581 | if (!domainData.name || !domainData.registrationDate || !domainData.expiryDate) { 3582 | throw new Error('域名、注册时间和到期日期为必填项'); 3583 | } 3584 | 3585 | // 生成唯一ID 3586 | domainData.id = crypto.randomUUID(); 3587 | 3588 | // 添加创建时间 3589 | domainData.createdAt = new Date().toISOString(); 3590 | 3591 | // 处理通知设置 3592 | if (!domainData.notifySettings) { 3593 | // 添加默认通知设置 3594 | domainData.notifySettings = { 3595 | useGlobalSettings: true, 3596 | notifyDays: 30, 3597 | enabled: true 3598 | }; 3599 | } 3600 | 3601 | // 确保有lastRenewed字段 3602 | if (!domainData.lastRenewed) { 3603 | domainData.lastRenewed = null; 3604 | } 3605 | 3606 | // 添加到列表 3607 | domains.push(domainData); 3608 | 3609 | // 保存到KV 3610 | await DOMAIN_MONITOR.put('domains', JSON.stringify(domains)); 3611 | 3612 | return domainData; 3613 | } 3614 | 3615 | // 更新域名 3616 | async function updateDomain(id, domainData) { 3617 | const domains = await getDomains(); 3618 | 3619 | // 查找域名索引 3620 | const index = domains.findIndex(d => d.id === id); 3621 | if (index === -1) { 3622 | throw new Error('域名不存在'); 3623 | } 3624 | 3625 | // 验证域名数据 3626 | if (!domainData.name || !domainData.registrationDate || !domainData.expiryDate) { 3627 | throw new Error('域名、注册时间和到期日期为必填项'); 3628 | } 3629 | 3630 | // 确保通知设置正确 3631 | let notifySettings; 3632 | if (domainData.notifySettings) { 3633 | // 使用提交的通知设置 3634 | notifySettings = domainData.notifySettings; 3635 | } else if (domains[index].notifySettings) { 3636 | // 使用现有的通知设置 3637 | notifySettings = domains[index].notifySettings; 3638 | } else { 3639 | // 创建默认通知设置 3640 | notifySettings = { 3641 | useGlobalSettings: true, 3642 | notifyDays: 30, 3643 | enabled: true 3644 | }; 3645 | } 3646 | 3647 | // 更新域名 - 确保正确处理空值 3648 | domains[index] = { 3649 | ...domains[index], 3650 | name: domainData.name, 3651 | expiryDate: domainData.expiryDate, 3652 | registrationDate: domainData.registrationDate !== undefined ? domainData.registrationDate : domains[index].registrationDate, 3653 | registrar: domainData.registrar !== undefined ? domainData.registrar : domains[index].registrar, 3654 | customNote: domainData.customNote !== undefined ? domainData.customNote : domains[index].customNote, // 正确处理空字符串 3655 | noteColor: domainData.noteColor !== undefined ? domainData.noteColor : domains[index].noteColor, // 添加备注颜色处理 3656 | renewLink: domainData.renewLink !== undefined ? domainData.renewLink : domains[index].renewLink, // 正确处理空字符串 3657 | renewCycle: domainData.renewCycle || domains[index].renewCycle, 3658 | price: domainData.price !== undefined ? domainData.price : domains[index].price, // 添加价格信息,保留现有价格如果未提供 3659 | lastRenewed: domainData.lastRenewed !== undefined ? domainData.lastRenewed : domains[index].lastRenewed, // 根据用户选择更新续期时间 3660 | notifySettings: notifySettings, 3661 | updatedAt: new Date().toISOString() 3662 | }; 3663 | 3664 | // 保存到KV 3665 | await DOMAIN_MONITOR.put('domains', JSON.stringify(domains)); 3666 | 3667 | return domains[index]; 3668 | } 3669 | 3670 | // 删除域名 3671 | async function deleteDomain(id) { 3672 | const domains = await getDomains(); 3673 | 3674 | // 过滤掉要删除的域名 3675 | const newDomains = domains.filter(d => d.id !== id); 3676 | 3677 | // 如果长度相同,说明没有找到要删除的域名 3678 | if (newDomains.length === domains.length) { 3679 | throw new Error('域名不存在'); 3680 | } 3681 | 3682 | // 保存到KV 3683 | await DOMAIN_MONITOR.put('domains', JSON.stringify(newDomains)); 3684 | 3685 | return true; 3686 | } 3687 | 3688 | // 域名续期 3689 | async function renewDomain(id, renewData) { 3690 | const domains = await getDomains(); 3691 | 3692 | // 查找域名索引 3693 | const index = domains.findIndex(d => d.id === id); 3694 | if (index === -1) { 3695 | throw new Error('域名不存在'); 3696 | } 3697 | 3698 | const now = new Date(); 3699 | 3700 | // 更新域名信息中的续期数据 3701 | if (!domains[index].renewCycle) { 3702 | domains[index].renewCycle = { 3703 | value: renewData.value || 1, 3704 | unit: renewData.unit || 'year' 3705 | }; 3706 | } 3707 | 3708 | // 如果域名是已过期状态,标记为从当前时间开始的全新计算 3709 | if (new Date(domains[index].expiryDate) < new Date()) { 3710 | domains[index].renewedFromExpired = true; 3711 | domains[index].renewStartDate = now.toISOString(); // 记录续期开始时间(当前时间) 3712 | } 3713 | 3714 | // 更新到期日期和续期记录 3715 | domains[index] = { 3716 | ...domains[index], 3717 | expiryDate: renewData.newExpiryDate, 3718 | updatedAt: now.toISOString(), 3719 | lastRenewed: now.toISOString(), // 记录本次续期时间 3720 | lastRenewPeriod: { 3721 | value: renewData.value, 3722 | unit: renewData.unit 3723 | } // 记录本次续期周期,用于进度条计算 3724 | }; 3725 | 3726 | // 保存到KV 3727 | await DOMAIN_MONITOR.put('domains', JSON.stringify(domains)); 3728 | 3729 | return domains[index]; 3730 | } 3731 | 3732 | // 获取Telegram配置 3733 | async function getTelegramConfig() { 3734 | const configStr = await DOMAIN_MONITOR.get('telegram_config') || '{}'; 3735 | const config = JSON.parse(configStr); 3736 | 3737 | // 检查是否使用环境变量 3738 | // 当环境变量存在且配置中的值为undefined、null或空字符串时,视为使用环境变量 3739 | const tokenFromEnv = typeof TG_TOKEN !== 'undefined' && ( 3740 | config.botToken === undefined || 3741 | config.botToken === null || 3742 | config.botToken === '' 3743 | ); 3744 | 3745 | const chatIdFromEnv = typeof TG_ID !== 'undefined' && ( 3746 | config.chatId === undefined || 3747 | config.chatId === null || 3748 | config.chatId === '' 3749 | ); 3750 | 3751 | // 检查是否使用代码中定义的变量 3752 | const tokenFromCode = !tokenFromEnv && DEFAULT_TG_TOKEN !== '' && ( 3753 | config.botToken === undefined || 3754 | config.botToken === null || 3755 | config.botToken === '' 3756 | ); 3757 | 3758 | const chatIdFromCode = !chatIdFromEnv && DEFAULT_TG_ID !== '' && ( 3759 | config.chatId === undefined || 3760 | config.chatId === null || 3761 | config.chatId === '' 3762 | ); 3763 | 3764 | // 返回完整的配置信息,包括token和chatId 3765 | return { 3766 | enabled: !!config.enabled, 3767 | chatId: chatIdFromEnv || chatIdFromCode ? '' : (config.chatId || ''), 3768 | botToken: tokenFromEnv || tokenFromCode ? '' : (config.botToken || ''), // 如果有环境变量或代码变量,则返回空字符串 3769 | chatIdFromEnv: chatIdFromEnv || chatIdFromCode, // 环境变量或代码中有设置都显示为已配置 3770 | tokenFromEnv: tokenFromEnv || tokenFromCode, // 环境变量或代码中有设置都显示为已配置 3771 | hasToken: tokenFromEnv || tokenFromCode || (config.botToken !== undefined && config.botToken !== null && config.botToken !== ''), 3772 | notifyDays: config.notifyDays || 30, 3773 | }; 3774 | } 3775 | 3776 | // 保存Telegram配置 3777 | async function saveTelegramConfig(configData) { 3778 | // 验证必要的配置 - 只有当启用Telegram通知且环境变量中也没有配置时才需要验证 3779 | if (configData.enabled) { 3780 | // 检查是否可以使用环境变量或用户输入的值 3781 | // 注意:空字符串("")被视为有效的清除操作,不应该抛出错误 3782 | const hasTokenSource = (configData.botToken !== undefined && configData.botToken !== null) || 3783 | typeof TG_TOKEN !== 'undefined' || 3784 | DEFAULT_TG_TOKEN !== ''; 3785 | const hasChatIdSource = (configData.chatId !== undefined && configData.chatId !== null) || 3786 | typeof TG_ID !== 'undefined' || 3787 | DEFAULT_TG_ID !== ''; 3788 | 3789 | if (!hasTokenSource) { 3790 | throw new Error('启用Telegram通知需要提供机器人Token或在环境变量中配置'); 3791 | } 3792 | if (!hasChatIdSource) { 3793 | throw new Error('启用Telegram通知需要提供聊天ID或在环境变量中配置'); 3794 | } 3795 | } 3796 | 3797 | // 保存配置到KV - 即使值为空也保存,表示用户有意清除值 3798 | const config = { 3799 | enabled: !!configData.enabled, 3800 | botToken: configData.botToken, // 可能为空字符串,表示用户清除了值 3801 | chatId: configData.chatId, // 可能为空字符串,表示用户清除了值 3802 | notifyDays: configData.notifyDays || 30, 3803 | }; 3804 | 3805 | await DOMAIN_MONITOR.put('telegram_config', JSON.stringify(config)); 3806 | 3807 | // 检查是否使用环境变量 3808 | // 当环境变量存在且配置中的值为undefined、null或空字符串时,视为使用环境变量 3809 | const tokenFromEnv = typeof TG_TOKEN !== 'undefined' && ( 3810 | config.botToken === undefined || 3811 | config.botToken === null || 3812 | config.botToken === '' 3813 | ); 3814 | 3815 | const chatIdFromEnv = typeof TG_ID !== 'undefined' && ( 3816 | config.chatId === undefined || 3817 | config.chatId === null || 3818 | config.chatId === '' 3819 | ); 3820 | 3821 | // 检查是否使用代码中定义的变量 3822 | const tokenFromCode = !tokenFromEnv && DEFAULT_TG_TOKEN !== '' && ( 3823 | config.botToken === undefined || 3824 | config.botToken === null || 3825 | config.botToken === '' 3826 | ); 3827 | 3828 | const chatIdFromCode = !chatIdFromEnv && DEFAULT_TG_ID !== '' && ( 3829 | config.chatId === undefined || 3830 | config.chatId === null || 3831 | config.chatId === '' 3832 | ); 3833 | 3834 | // 返回完整的配置信息,包括token和chatId 3835 | return { 3836 | enabled: config.enabled, 3837 | chatId: chatIdFromEnv || chatIdFromCode ? '' : (config.chatId || ''), 3838 | botToken: tokenFromEnv || tokenFromCode ? '' : (config.botToken || ''), // 如果有环境变量或代码变量,则返回空字符串 3839 | chatIdFromEnv: chatIdFromEnv || chatIdFromCode, // 环境变量或代码中有设置都显示为已配置 3840 | tokenFromEnv: tokenFromEnv || tokenFromCode, // 环境变量或代码中有设置都显示为已配置 3841 | hasToken: tokenFromEnv || tokenFromCode || !!config.botToken, 3842 | notifyDays: config.notifyDays, 3843 | }; 3844 | } 3845 | 3846 | // 测试Telegram通知 3847 | async function testTelegramNotification() { 3848 | const config = await getTelegramConfigWithToken(); 3849 | 3850 | if (!config.enabled) { 3851 | throw new Error('Telegram通知未启用'); 3852 | } 3853 | 3854 | if (!config.botToken && typeof TG_TOKEN === 'undefined' && DEFAULT_TG_TOKEN === '') { 3855 | throw new Error('未配置Telegram机器人Token'); 3856 | } 3857 | 3858 | if (!config.chatId && typeof TG_ID === 'undefined' && DEFAULT_TG_ID === '') { 3859 | throw new Error('未配置Telegram聊天ID'); 3860 | } 3861 | 3862 | const message = '这是一条来自域名监控系统的测试通知,如果您收到此消息,表示Telegram通知配置成功!'; 3863 | 3864 | const result = await sendTelegramMessage(config, message); 3865 | return { success: true, message: '测试通知已发送' }; 3866 | } 3867 | 3868 | // 获取完整的Telegram配置(包括token) 3869 | async function getTelegramConfigWithToken() { 3870 | const configStr = await DOMAIN_MONITOR.get('telegram_config') || '{}'; 3871 | const config = JSON.parse(configStr); 3872 | 3873 | // 如果KV中没有token或chatId,或者是空字符串,但环境变量中有值,则使用环境变量中的值 3874 | if (typeof TG_TOKEN !== 'undefined' && ( 3875 | config.botToken === undefined || 3876 | config.botToken === null || 3877 | config.botToken === '' 3878 | )) { 3879 | config.botToken = TG_TOKEN; 3880 | } 3881 | 3882 | // 同样处理chatId 3883 | if (typeof TG_ID !== 'undefined' && ( 3884 | config.chatId === undefined || 3885 | config.chatId === null || 3886 | config.chatId === '' 3887 | )) { 3888 | config.chatId = TG_ID; 3889 | } 3890 | 3891 | // 如果环境变量中没有,但代码中有,则使用代码中的值 3892 | else if (DEFAULT_TG_TOKEN !== '' && ( 3893 | config.botToken === undefined || 3894 | config.botToken === null || 3895 | config.botToken === '' 3896 | )) { 3897 | config.botToken = DEFAULT_TG_TOKEN; 3898 | } 3899 | 3900 | // 如果环境变量中没有,但代码中有,则使用代码中的值 3901 | else if (DEFAULT_TG_ID !== '' && ( 3902 | config.chatId === undefined || 3903 | config.chatId === null || 3904 | config.chatId === '' 3905 | )) { 3906 | config.chatId = DEFAULT_TG_ID; 3907 | } 3908 | 3909 | return { 3910 | enabled: !!config.enabled, 3911 | botToken: config.botToken || '', 3912 | chatId: config.chatId || '', 3913 | notifyDays: config.notifyDays || 30, 3914 | }; 3915 | } 3916 | 3917 | // 发送Telegram消息 3918 | async function sendTelegramMessage(config, message) { 3919 | // 优先使用配置中的值,如果没有则使用环境变量或代码中的值 3920 | let botToken = config.botToken; 3921 | let chatId = config.chatId; 3922 | 3923 | // 如果配置中没有值,检查环境变量 3924 | if (!botToken) { 3925 | if (typeof TG_TOKEN !== 'undefined') { 3926 | botToken = TG_TOKEN; 3927 | } else if (DEFAULT_TG_TOKEN !== '') { 3928 | botToken = DEFAULT_TG_TOKEN; 3929 | } 3930 | } 3931 | 3932 | if (!chatId) { 3933 | if (typeof TG_ID !== 'undefined') { 3934 | chatId = TG_ID; 3935 | } else if (DEFAULT_TG_ID !== '') { 3936 | chatId = DEFAULT_TG_ID; 3937 | } 3938 | } 3939 | 3940 | if (!botToken) { 3941 | throw new Error('未配置Telegram机器人Token'); 3942 | } 3943 | 3944 | if (!chatId) { 3945 | throw new Error('未配置Telegram聊天ID'); 3946 | } 3947 | 3948 | const url = 'https://api.telegram.org/bot' + botToken + '/sendMessage'; 3949 | 3950 | const response = await fetch(url, { 3951 | method: 'POST', 3952 | headers: { 3953 | 'Content-Type': 'application/json', 3954 | }, 3955 | body: JSON.stringify({ 3956 | chat_id: chatId, 3957 | text: message, 3958 | parse_mode: 'HTML', 3959 | }), 3960 | }); 3961 | 3962 | if (!response.ok) { 3963 | const error = await response.json(); 3964 | throw new Error('发送Telegram消息失败: ' + (error.description || '未知错误')); 3965 | } 3966 | 3967 | return await response.json(); 3968 | } 3969 | 3970 | // 返回JSON响应 3971 | function jsonResponse(data, status = 200) { 3972 | return new Response(JSON.stringify(data), { 3973 | headers: { 3974 | 'Content-Type': 'application/json', 3975 | }, 3976 | status, 3977 | }); 3978 | } 3979 | 3980 | // 设置定时任务,检查即将到期的域名并发送通知 3981 | async function checkExpiringDomains() { 3982 | const domains = await getDomains(); 3983 | const today = new Date(); 3984 | 3985 | // 获取Telegram配置 3986 | const telegramConfig = await getTelegramConfigWithToken(); 3987 | const globalNotifyDays = telegramConfig.enabled ? telegramConfig.notifyDays : 30; 3988 | 3989 | // 筛选出即将到期和已过期的域名 3990 | const domainsToNotify = domains.filter(domain => { 3991 | const expiryDate = new Date(domain.expiryDate); 3992 | const daysLeft = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24)); 3993 | 3994 | // 获取该域名的通知设置 3995 | const notifySettings = domain.notifySettings || { useGlobalSettings: true, enabled: true, notifyDays: 30 }; 3996 | 3997 | // 如果使用全局设置,则使用全局通知天数,否则使用域名自己的设置 3998 | const notifyDays = notifySettings.useGlobalSettings ? globalNotifyDays : notifySettings.notifyDays; 3999 | 4000 | // 通知已过期的域名或即将到期的域名 4001 | return notifySettings.enabled && ( 4002 | daysLeft <= 0 || // 已过期 4003 | (daysLeft > 0 && daysLeft <= notifyDays) // 即将到期 4004 | ); 4005 | }); 4006 | 4007 | // 将域名分为已过期和即将到期两组 4008 | const expiredDomains = domainsToNotify.filter(domain => { 4009 | const expiryDate = new Date(domain.expiryDate); 4010 | const daysLeft = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24)); 4011 | return daysLeft <= 0; 4012 | }); 4013 | 4014 | const expiringDomains = domainsToNotify.filter(domain => { 4015 | const expiryDate = new Date(domain.expiryDate); 4016 | const daysLeft = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24)); 4017 | return daysLeft > 0; 4018 | }); 4019 | 4020 | // 如果有即将到期或已过期的域名,发送通知 4021 | if (expiringDomains.length > 0 || expiredDomains.length > 0) { 4022 | 4023 | // 如果启用了Telegram通知,则发送通知 4024 | if (telegramConfig.enabled && 4025 | ((telegramConfig.botToken || typeof TG_TOKEN !== 'undefined') && 4026 | (telegramConfig.chatId || typeof TG_ID !== 'undefined'))) { 4027 | try { 4028 | // 发送即将到期的域名通知 4029 | if (expiringDomains.length > 0) { 4030 | await sendExpiringDomainsNotification(telegramConfig, expiringDomains, false); 4031 | } 4032 | 4033 | // 发送已过期的域名通知 4034 | if (expiredDomains.length > 0) { 4035 | await sendExpiringDomainsNotification(telegramConfig, expiredDomains, true); 4036 | } 4037 | } catch (error) { 4038 | console.error('发送Telegram通知失败: ' + error.message); 4039 | } 4040 | } 4041 | } 4042 | } 4043 | 4044 | // 发送域名通知(即将到期或已过期) 4045 | async function sendExpiringDomainsNotification(config, domains, isExpired) { 4046 | if (domains.length === 0) return; 4047 | 4048 | // 构建消息内容 4049 | let title = isExpired ? 4050 | '🚫 域名已过期提醒 🚫' : 4051 | '🚨 域名到期提醒 🚨'; 4052 | 4053 | // 根据不同通知类型使用不同长度的等号分隔线 4054 | // 域名到期提醒使用19个字符,域名已过期提醒使用21个字符 4055 | const separator = isExpired ? 4056 | '=====================' : 4057 | '==================='; 4058 | // 域名之间的短横线分隔符统一使用40个字符 4059 | const domainSeparator = '----------------------------------------'; 4060 | 4061 | let message = title + '\n' + separator + '\n\n'; 4062 | 4063 | domains.forEach((domain, index) => { 4064 | const expiryDate = new Date(domain.expiryDate); 4065 | const today = new Date(); 4066 | const daysLeft = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24)); 4067 | 4068 | if (index > 0) { 4069 | message += '\n' + domainSeparator + '\n\n'; 4070 | } 4071 | 4072 | message += '🌍 域名: ' + domain.name + '\n'; 4073 | if (domain.registrar) { 4074 | message += '🏬 注册商: ' + domain.registrar + '\n'; 4075 | } 4076 | message += '⏳ 剩余时间: ' + daysLeft + ' 天\n'; 4077 | message += '📅 到期日期: ' + formatDate(domain.expiryDate) + '\n'; 4078 | 4079 | if (domain.renewLink) { 4080 | message += '⚠️ 点击续期: ' + domain.renewLink + '\n'; 4081 | } else { 4082 | message += '⚠️ 点击续期: 未设置续期链接\n'; 4083 | } 4084 | }); 4085 | 4086 | // 发送消息 4087 | return await sendTelegramMessage(config, message); 4088 | } 4089 | 4090 | // 添加测试单个域名通知的后端函数 4091 | async function testSingleDomainNotification(id) { 4092 | // 获取域名信息 4093 | const domains = await getDomains(); 4094 | const domain = domains.find(d => d.id === id); 4095 | 4096 | if (!domain) { 4097 | throw new Error('域名不存在'); 4098 | } 4099 | 4100 | // 获取Telegram配置 4101 | const telegramConfig = await getTelegramConfigWithToken(); 4102 | 4103 | if (!telegramConfig.enabled) { 4104 | throw new Error('Telegram通知未启用'); 4105 | } 4106 | 4107 | if (!telegramConfig.botToken && typeof TG_TOKEN === 'undefined') { 4108 | throw new Error('未配置Telegram机器人Token'); 4109 | } 4110 | 4111 | if (!telegramConfig.chatId && typeof TG_ID === 'undefined') { 4112 | throw new Error('未配置Telegram聊天ID'); 4113 | } 4114 | 4115 | // 构建测试消息 4116 | const expiryDate = new Date(domain.expiryDate); 4117 | const today = new Date(); 4118 | const daysLeft = Math.ceil((expiryDate - today) / (1000 * 60 * 60 * 24)); 4119 | const isExpired = daysLeft <= 0; 4120 | 4121 | let title = isExpired ? 4122 | '🚫 域名已过期测试通知 🚫' : 4123 | '🚨 域名到期测试通知 🚨'; 4124 | 4125 | // 根据不同通知类型使用不同长度的分隔线 4126 | // 域名到期测试通知使用23个字符,域名已过期测试通知使用25个字符 4127 | const separator = isExpired ? 4128 | '=========================' : 4129 | '======================='; 4130 | 4131 | 4132 | let message = title + '\n' + separator + '\n\n'; 4133 | message += '这是一条测试通知,用于预览域名' + (isExpired ? '已过期' : '到期') + '提醒的格式:\n\n'; 4134 | 4135 | message += '🌍 域名: ' + domain.name + '\n'; 4136 | if (domain.registrar) { 4137 | message += '🏬 注册商: ' + domain.registrar + '\n'; 4138 | } 4139 | message += '⏳ 剩余时间: ' + daysLeft + ' 天\n'; 4140 | message += '📅 到期日期: ' + formatDate(domain.expiryDate) + '\n'; 4141 | 4142 | if (domain.renewLink) { 4143 | message += '⚠️ 点击续期: ' + domain.renewLink + '\n'; 4144 | } else { 4145 | message += '⚠️ 点击续期: 未设置续期链接\n'; 4146 | } 4147 | 4148 | // 发送测试消息 4149 | const result = await sendTelegramMessage(telegramConfig, message); 4150 | return { success: true, message: '测试通知已发送' }; 4151 | } 4152 | 4153 | // 格式化日期函数 4154 | function formatDate(dateString) { 4155 | const date = new Date(dateString); 4156 | const year = date.getFullYear(); 4157 | const month = String(date.getMonth() + 1).padStart(2, '0'); 4158 | const day = String(date.getDate()).padStart(2, '0'); 4159 | return year + '-' + month + '-' + day; 4160 | } 4161 | 4162 | // 注册Cloudflare Workers事件处理程序 4163 | addEventListener('fetch', event => { 4164 | event.respondWith(handleRequest(event.request)); 4165 | }); 4166 | 4167 | // 注册定时任务,每天检查一次 4168 | addEventListener('scheduled', event => { 4169 | event.waitUntil(checkExpiringDomains()); 4170 | }); 4171 | 4172 | // 添加页面底部版权信息 4173 | function addCopyrightFooter(html) { 4174 | // 定义页脚内容和样式,只需要修改这里 4175 | // 页脚文字大小 4176 | const footerFontSize = '14px'; 4177 | // 页脚图标大小 4178 | const footerIconSize = '14px'; 4179 | // 页脚图标颜色(使用CSS颜色值,如:#4e54c8、blue、rgba(0,0,0,0.7)等) 4180 | const footerIconColor = 'white'; 4181 | 4182 | const footerContent = `Copyright © 2025 Faiz | GitHub Repository | Faiz博客`; 4183 | 4184 | const bodyEndIndex = html.lastIndexOf(''); 4185 | 4186 | // 如果找到了标签 4187 | if (bodyEndIndex !== -1) { 4188 | // 在标签前插入页脚和相关脚本 4189 | const footer = ` 4190 | 4216 | 4217 | 4220 | 4221 | 4242 | `; 4243 | 4244 | return html.slice(0, bodyEndIndex) + footer + html.slice(bodyEndIndex); 4245 | } 4246 | 4247 | // 如果没找到标签,就直接添加到HTML末尾 4248 | const footerHtml = ` 4249 |
4250 | ${footerContent} 4251 |
4252 | `; 4253 | 4254 | // 在标签前插入页脚 4255 | return html.replace('', `${footerHtml}`); 4256 | } 4257 | 4258 | // 修改响应处理,添加版权信息 4259 | async function addFooterToResponse(response) { 4260 | const contentType = response.headers.get('Content-Type') || ''; 4261 | 4262 | // 只处理HTML响应 4263 | if (contentType.includes('text/html')) { 4264 | const html = await response.text(); 4265 | const modifiedHtml = addCopyrightFooter(html); 4266 | 4267 | return new Response(modifiedHtml, { 4268 | status: response.status, 4269 | statusText: response.statusText, 4270 | headers: response.headers 4271 | }); 4272 | } 4273 | 4274 | return response; 4275 | } 4276 | 4277 | // 添加Pages兼容性支持 4278 | export default { 4279 | async fetch(request, env, ctx) { 4280 | // 在Pages环境中,env包含绑定的变量 4281 | if (env) { 4282 | // 将环境变量绑定到全局,以便与Workers代码兼容 4283 | if (env.DOMAIN_MONITOR) { 4284 | globalThis.DOMAIN_MONITOR = env.DOMAIN_MONITOR; 4285 | } 4286 | if (env.TOKEN) { 4287 | globalThis.TOKEN = env.TOKEN; 4288 | } 4289 | if (env.LOGO_URL) { 4290 | globalThis.LOGO_URL = env.LOGO_URL; 4291 | } 4292 | if (env.BACKGROUND_URL) { 4293 | globalThis.BACKGROUND_URL = env.BACKGROUND_URL; 4294 | } 4295 | if (env.MOBILE_BACKGROUND_URL) { 4296 | globalThis.MOBILE_BACKGROUND_URL = env.MOBILE_BACKGROUND_URL; 4297 | } 4298 | if (env.SITE_NAME) { 4299 | globalThis.SITE_NAME = env.SITE_NAME; 4300 | } 4301 | if (env.TG_TOKEN) { 4302 | globalThis.TG_TOKEN = env.TG_TOKEN; 4303 | } 4304 | if (env.TG_ID) { 4305 | globalThis.TG_ID = env.TG_ID; 4306 | } 4307 | } 4308 | 4309 | // 使用相同的请求处理函数 4310 | return handleRequest(request); 4311 | }, 4312 | 4313 | async scheduled(event, env, ctx) { 4314 | // 在Pages环境中,env包含绑定的变量 4315 | if (env) { 4316 | // 将环境变量绑定到全局,以便与Workers代码兼容 4317 | if (env.DOMAIN_MONITOR) { 4318 | globalThis.DOMAIN_MONITOR = env.DOMAIN_MONITOR; 4319 | } 4320 | if (env.TG_TOKEN) { 4321 | globalThis.TG_TOKEN = env.TG_TOKEN; 4322 | } 4323 | if (env.TG_ID) { 4324 | globalThis.TG_ID = env.TG_ID; 4325 | } 4326 | } 4327 | 4328 | // 使用相同的定时任务处理函数 4329 | return checkExpiringDomains(); 4330 | } 4331 | }; 4332 | 4333 | // 检查是否已配置KV 4334 | function isKVConfigured() { 4335 | return typeof DOMAIN_MONITOR !== 'undefined'; 4336 | } 4337 | 4338 | // 获取配置向导HTML 4339 | function getSetupHTML() { 4340 | return ` 4341 | 4342 | 4343 | 4344 | 4345 | 域名监控系统 - 配置向导 4346 | 4347 | 4401 | 4402 | 4403 |
4404 |

域名监控系统 - 配置向导

4405 | 4406 |
4407 | 提示: 检测到您尚未完成必要的配置。请按照以下步骤设置您的域名监控系统。 4408 |
4409 | 4410 |
4411 |

1 创建KV命名空间

4412 |

首先,您需要创建一个KV命名空间来存储域名数据:

4413 |
    4414 |
  1. 登录到 Cloudflare仪表板
  2. 4415 |
  3. 选择您的账户,然后点击Workers & Pages
  4. 4416 |
  5. 在左侧菜单中,点击KV
  6. 4417 |
  7. 点击创建命名空间按钮
  8. 4418 |
  9. 输入命名空间名称,例如:domain-monitor
  10. 4419 |
  11. 点击添加按钮完成创建
  12. 4420 |
4421 |
4422 | 4423 |
4424 |

2 绑定KV命名空间到您的项目

4425 |

接下来,您需要将创建的KV命名空间绑定到您的Workers或Pages项目:

4426 | 4427 |

对于Workers项目:

4428 |
    4429 |
  1. 在Workers & Pages页面,点击您的Workers项目
  2. 4430 |
  3. 点击设置标签,然后选择变量
  4. 4431 |
  5. KV命名空间绑定部分,点击添加绑定
  6. 4432 |
  7. 变量名设置为:DOMAIN_MONITOR(必须使用此名称)
  8. 4433 |
  9. KV命名空间选择您刚才创建的命名空间
  10. 4434 |
  11. 点击保存按钮
  12. 4435 |
4436 | 4437 |

对于Pages项目:

4438 |
    4439 |
  1. 在Workers & Pages页面,点击您的Pages项目
  2. 4440 |
  3. 点击设置标签,然后选择函数
  4. 4441 |
  5. KV命名空间绑定部分,点击添加绑定
  6. 4442 |
  7. 变量名设置为:DOMAIN_MONITOR(必须使用此名称)
  8. 4443 |
  9. KV命名空间选择您刚才创建的命名空间
  10. 4444 |
  11. 点击保存按钮
  12. 4445 |
4446 |
4447 | 4448 |
4449 |

3 设置环境变量(可选)

4450 |

您可以设置以下环境变量来自定义您的域名监控系统:

4451 | 4460 |

在Workers或Pages的设置 > 变量部分添加这些环境变量。

4461 |
4462 | 4463 |
4464 | 我已完成设置,刷新页面 4465 |
4466 |
4467 | 4468 | `; 4469 | } 4470 | --------------------------------------------------------------------------------