├── 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 | |  |  |
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 |
198 |
199 | ${title}
200 |
201 |
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 |
1607 |
1608 |
1609 |
1613 |
1730 |
1734 |
1735 |
1736 |
1737 |
1738 |
1739 |
1740 |
1741 |
1742 |
1746 |
1776 |
1780 |
1781 |
1782 |
1783 |
1784 |
1785 |
1786 |
1787 |
1788 |
1792 |
1793 |
确定要删除域名 吗?此操作不可撤销。
1794 |
1795 |
1799 |
1800 |
1801 |
1802 |
1803 |
1804 |
1805 |
1806 |
1807 |
1811 |
1812 |
为域名 续期
1813 |
1814 |
1815 |
1816 |
1817 |
1822 |
1823 |
1824 |
1825 |
1826 |
1827 |
1828 |
1829 |
1833 |
1834 |
1835 |
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('
4403 |
4404 |
域名监控系统 - 配置向导
4405 |
4406 |
4407 | 提示: 检测到您尚未完成必要的配置。请按照以下步骤设置您的域名监控系统。
4408 |
4409 |
4410 |
4411 |
1 创建KV命名空间
4412 |
首先,您需要创建一个KV命名空间来存储域名数据:
4413 |
4414 | - 登录到 Cloudflare仪表板
4415 | - 选择您的账户,然后点击Workers & Pages
4416 | - 在左侧菜单中,点击KV
4417 | - 点击创建命名空间按钮
4418 | - 输入命名空间名称,例如:
domain-monitor
4419 | - 点击添加按钮完成创建
4420 |
4421 |
4422 |
4423 |
4424 |
2 绑定KV命名空间到您的项目
4425 |
接下来,您需要将创建的KV命名空间绑定到您的Workers或Pages项目:
4426 |
4427 |
对于Workers项目:
4428 |
4429 | - 在Workers & Pages页面,点击您的Workers项目
4430 | - 点击设置标签,然后选择变量
4431 | - 在KV命名空间绑定部分,点击添加绑定
4432 | - 变量名设置为:
DOMAIN_MONITOR(必须使用此名称)
4433 | - KV命名空间选择您刚才创建的命名空间
4434 | - 点击保存按钮
4435 |
4436 |
4437 |
对于Pages项目:
4438 |
4439 | - 在Workers & Pages页面,点击您的Pages项目
4440 | - 点击设置标签,然后选择函数
4441 | - 在KV命名空间绑定部分,点击添加绑定
4442 | - 变量名设置为:
DOMAIN_MONITOR(必须使用此名称)
4443 | - KV命名空间选择您刚才创建的命名空间
4444 | - 点击保存按钮
4445 |
4446 |
4447 |
4448 |
4449 |
3 设置环境变量(可选)
4450 |
您可以设置以下环境变量来自定义您的域名监控系统:
4451 |
4452 | TOKEN - 登录密码,如果不设置则默认使用"domain"
4453 | SITE_NAME - 网站标题
4454 | LOGO_URL - 自定义Logo图片URL
4455 | BACKGROUND_URL - 自定义桌面端背景图片URL
4456 | MOBILE_BACKGROUND_URL - 自定义移动端背景图片URL(可选,如果不设置则使用桌面端背景图片)
4457 | TG_TOKEN - Telegram机器人Token
4458 | TG_ID - Telegram聊天ID
4459 |
4460 |
在Workers或Pages的设置 > 变量部分添加这些环境变量。
4461 |
4462 |
4463 |
4464 |
我已完成设置,刷新页面
4465 |
4466 |
4467 | ');
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 |
4468 |