HidenCloud 全自动续期 & 账单自动支付脚本 (青龙面板版)

这是一个用于 HidenCloud 免费服务器自动续期和自动支付账单的青龙面板脚本。

原帖:https://linux.do/t/topic/1393805
感谢 Y 佬的教程

HidenCloud 的免费机器虽然好用,但机制比较繁琐,本脚本实现了全流程自动化,自动续期 + 支付 + 自动读写 cookie。

建议手动续期 84 天后,每 7 天执行一次,避免错过续期窗口


功能特点

  1. 全流程自动化:自动识别账号下的所有服务 → 自动提交续期请求 → 自动跳转账单页 → 自动完成 0 元支付
  2. 智能支付逻辑
    • 脚本会自动解析账单页面 HTML。
    • 动态提取隐藏的支付参数。
  3. Cookie 自动续命 (持久化)
    • 脚本运行期间会自动捕获服务器返回的新 Cookie。
    • 将最新 Cookie 保存到本地 hiden_cookies.json 文件。
    • 优势:只要脚本每天运行,理论上无需再手动更新环境变量中的 Cookie。
  4. 多账号支持:支持无限个账号,通过换行或 & 符号分隔。
  5. 防检测机制
    • 内置随机延迟(3-8 秒),模拟真人操作。
    • 伪装完整的 Chrome 浏览器 Headers,降低被 Cloudflare 拦截的风险。
  6. 消息推送:对接青龙面板的通知系统,任务完成后发送续期结果。


使用方法

1. 准备工作

确保你的青龙面板已安装以下 Node.js 依赖(在 依赖管理NodeJs 中添加):

  • axios
  • cheerio

2. 添加脚本

在青龙面板 脚本管理 中新建脚本,名称随意(例如 hiden_renew.js),将代码完全粘贴进去。

自定义续期天数:脚本第 23 行,将数字 10 修改为你想要的天数即可。
const RENEW_DAYS = 10;

青龙脚本 hiden_renew.js
/*
new Env('HidenCloud 自动续期-毕业版');
cron: 0 0 10 * * ?
checks: 自动续期、自动支付、Cookie自动持久化、消息推送
*/

const axios = require('axios');
const cheerio = require('cheerio');
const fs = require('fs');
const path = require('path');

// 尝试加载 notify,如果没有也不影响运行
let sendNotify = () => {};
try {
    const notify = require('./sendNotify');
    sendNotify = notify.sendNotify;
} catch (e) {
    console.log('未找到 sendNotify,跳过推送');
}

// 环境变量
const HIDEN_COOKIES_ENV = process.env.HIDEN_COOKIE ? process.env.HIDEN_COOKIE.split(/[&\n]/) : [];
const RENEW_DAYS = 10;
const CACHE_FILE = path.join(__dirname, 'hiden_cookies.json');

// 汇总消息
let summaryMsg = '';

const sleep = (min = 3000, max = 8000) => {
    const delay = Math.floor(Math.random() * (max - min + 1)) + min;
    return new Promise(resolve => setTimeout(resolve, delay));
};

// 本地缓存管理
const CacheManager = {
    load() {
        if (fs.existsSync(CACHE_FILE)) {
            try {
                return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
            } catch (e) {
                console.log('读取缓存文件失败,将重新创建');
            }
        }
        return {};
    },
    save(data) {
        fs.writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2));
    },
    get(index) {
        const data = this.load();
        return data[index] || null;
    },
    update(index, cookieStr) {
        const data = this.load();
        data[index] = cookieStr;
        this.save(data);
        console.log(`💾 [账号 ${index + 1}] 最新 Cookie 已保存到本地缓存`);
    }
};

class HidenCloudBot {
    constructor(envCookie, index) {
        this.index = index + 1;
        this.envCookie = envCookie;
        this.cookieData = {};
        this.logMsg = []; // 存储该账号的日志用于推送
        
        // 优先尝试读取缓存
        const cachedCookie = CacheManager.get(this.index - 1);
        if (cachedCookie) {
            console.log(`[账号 ${this.index}] 发现本地缓存 Cookie,优先使用...`);
            this.parseCookieStr(cachedCookie);
        } else {
            console.log(`[账号 ${this.index}] 使用环境变量 Cookie...`);
            this.parseCookieStr(envCookie);
        }

        this.commonHeaders = {
            'Host': 'dash.hidencloud.com',
            'Connection': 'keep-alive',
            'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
            'sec-ch-ua-mobile': '?0',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
            'Referer': 'https://dash.hidencloud.com/',
        };

        this.client = axios.create({
            baseURL: 'https://dash.hidencloud.com',
            maxRedirects: 0, 
            validateStatus: status => status >= 200 && status < 500,
            timeout: 30000 
        });
        
        this.services = [];
        this.csrfToken = ''; 
    }

    log(msg) {
        console.log(`[账号 ${this.index}] ${msg}`);
        this.logMsg.push(msg);
    }

    parseCookieStr(str) {
        if (!str) return;
        str.split(';').forEach(pair => {
            const idx = pair.indexOf('=');
            if (idx > 0) {
                const key = pair.substring(0, idx).trim();
                const val = pair.substring(idx + 1).trim();
                if (!['path', 'domain', 'expires', 'httponly', 'secure', 'samesite'].includes(key.toLowerCase())) {
                    this.cookieData[key] = val;
                }
            }
        });
    }

    updateCookiesFromResponse(headers) {
        const setCookie = headers['set-cookie'];
        if (setCookie) {
            setCookie.forEach(sc => {
                const firstPart = sc.split(';')[0];
                const idx = firstPart.indexOf('=');
                if (idx > 0) {
                    const key = firstPart.substring(0, idx).trim();
                    const val = firstPart.substring(idx + 1).trim();
                    this.cookieData[key] = val;
                }
            });
            // 每次更新 Cookie 都保存到本地
            CacheManager.update(this.index - 1, this.getCookieStr());
        }
    }

    getCookieStr() {
        return Object.keys(this.cookieData).map(k => `${k}=${this.cookieData[k]}`).join('; ');
    }

    async request(method, url, data = null, extraHeaders = {}) {
        let currentUrl = url;
        let methodToUse = method;
        let finalResponse = null;

        const requestHeaders = {
            ...this.commonHeaders,
            ...extraHeaders,
            'Cookie': this.getCookieStr()
        };

        if (methodToUse === 'POST' && !requestHeaders['Content-Type']) {
            requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded';
        }

        try {
            const res = await this.client({
                method: methodToUse,
                url: currentUrl,
                headers: requestHeaders,
                data: data
            });
            
            this.updateCookiesFromResponse(res.headers);
            res.finalUrl = currentUrl; 
            finalResponse = res;

            if (res.status === 301 || res.status === 302) {
                const location = res.headers['location'];
                if (location) {
                    this.log(`🔄 重定向 -> ${location}`);
                    currentUrl = location.startsWith('http') ? location : `https://dash.hidencloud.com${location.startsWith('/') ? '' : '/'}${location}`;
                    return this.request('GET', currentUrl);
                }
            }
            finalResponse.finalUrl = currentUrl;
            return finalResponse;
        } catch (err) {
            throw err;
        }
    }

    extractTokens($) {
        const metaToken = $('meta[name="csrf-token"]').attr('content');
        if (metaToken) this.csrfToken = metaToken;
    }

    async init() {
        this.log('正在验证登录状态...');
        try {
            const res = await this.request('GET', '/dashboard');
            
            // 检查失效
            if (res.headers.location && res.headers.location.includes('/login')) {
                 this.log('❌ 当前 Cookie 已失效');
                 return false;
            }

            const $ = cheerio.load(res.data);
            this.extractTokens($);

            // 解析服务列表
            $('a[href*="/service/"]').each((i, el) => {
                const href = $(el).attr('href');
                const match = href.match(/\/service\/(\d+)\/manage/);
                if (match) {
                    this.services.push({ id: match[1], url: href });
                }
            });
            this.services = this.services.filter((v, i, a) => a.findIndex(t => t.id === v.id) === i);

            this.log(`✅ 登录成功,发现 ${this.services.length} 个服务。`);
            return true;
        } catch (e) {
            this.log(`❌ 初始化异常: ${e.message}`);
            return false;
        }
    }

    // 重置为环境变量 Cookie (用于缓存失效时重试)
    resetToEnv() {
        this.cookieData = {};
        this.parseCookieStr(this.envCookie);
        console.log(`[账号 ${this.index}] 切换回环境变量原始 Cookie 重试...`);
    }

    async processService(service) {
        await sleep(2000, 4000);
        this.log(`>>> 处理服务 ID: ${service.id}`);

        try {
            const manageRes = await this.request('GET', `/service/${service.id}/manage`);
            const $ = cheerio.load(manageRes.data);
            const formToken = $('input[name="_token"]').val();

            this.log(`提交续期 (${RENEW_DAYS}天)...`);
            await sleep(1000, 2000); 

            const params = new URLSearchParams();
            params.append('_token', formToken);
            params.append('days', RENEW_DAYS);

            const res = await this.request('POST', `/service/${service.id}/renew`, params, {
                'X-CSRF-TOKEN': this.csrfToken,
                'Referer': `https://dash.hidencloud.com/service/${service.id}/manage`
            });
            
            if (res.finalUrl && res.finalUrl.includes('/invoice/')) {
                this.log(`⚡️ 续期成功,前往支付`);
                await this.performPayFromHtml(res.data, res.finalUrl);
            } else {
                this.log('⚠️ 续期后未跳转,检查列表...');
                await this.checkAndPayInvoices(service.id);
            }

        } catch (e) {
            this.log(`处理异常: ${e.message}`);
        }
    }

    async checkAndPayInvoices(serviceId) {
        await sleep(2000, 3000);
        try {
            const res = await this.request('GET', `/service/${serviceId}/invoices?where=unpaid`);
            const $ = cheerio.load(res.data);
            
            const invoiceLinks = [];
            $('a[href*="/invoice/"]').each((i, el) => {
                const href = $(el).attr('href');
                if (href && !href.includes('download')) invoiceLinks.push(href);
            });

            const uniqueInvoices = [...new Set(invoiceLinks)];
            if (uniqueInvoices.length === 0) {
                this.log(`✅ 无未支付账单`);
                return;
            }

            for (const url of uniqueInvoices) {
                await this.paySingleInvoice(url);
                await sleep(3000, 5000); 
            }
        } catch (e) {
            this.log(`查账单出错: ${e.message}`);
        }
    }

    async paySingleInvoice(url) {
        try {
            this.log(`📄 打开账单: ${url}`);
            const res = await this.request('GET', url);
            await this.performPayFromHtml(res.data, url);
        } catch (e) {
            this.log(`访问失败: ${e.message}`);
        }
    }

    async performPayFromHtml(html, currentUrl) {
        const $ = cheerio.load(html);
        
        let targetForm = null;
        let targetAction = '';
        
        $('form').each((i, form) => {
            const btnText = $(form).find('button').text().trim().toLowerCase();
            const action = $(form).attr('action');
            if (btnText.includes('pay') && action && !action.includes('balance/add')) {
                targetForm = $(form);
                targetAction = action;
                return false; 
            }
        });

        if (!targetForm) {
            this.log(`⚪ 页面未找到支付表单 (可能已支付)。`);
            return;
        }

        const payParams = new URLSearchParams();
        targetForm.find('input').each((i, el) => {
            const name = $(el).attr('name');
            const value = $(el).val();
            if (name) payParams.append(name, value || '');
        });

        this.log(`👉 提交支付...`);
        
        try {
            const payRes = await this.request('POST', targetAction, payParams, {
                'X-CSRF-TOKEN': this.csrfToken,
                'Referer': currentUrl
            });

            if (payRes.status === 200) {
                 this.log(`✅ 支付成功!`);
            } else {
                this.log(`⚠️ 支付响应: ${payRes.status}`);
            }
        } catch (e) {
            this.log(`❌ 支付失败: ${e.message}`);
        }
    }
}

(async () => {
    if (HIDEN_COOKIES_ENV.length === 0) {
        console.log('❌ 未配置环境变量 HIDEN_COOKIE');
        return;
    }
    
    console.log(`=== HidenCloud 续期脚本启动 (账号数: ${HIDEN_COOKIES_ENV.length}) ===\n`);

    for (let i = 0; i < HIDEN_COOKIES_ENV.length; i++) {
        const bot = new HidenCloudBot(HIDEN_COOKIES_ENV[i], i);
        
        // 第一次尝试(可能用的是缓存)
        let success = await bot.init();
        
        // 如果失败,且当前用的是缓存,则回退到环境变量重试
        if (!success && CacheManager.get(i)) {
            bot.resetToEnv();
            success = await bot.init();
        }

        if (success) {
            for (const svc of bot.services) {
                await bot.processService(svc);
            }
            summaryMsg += `账号 ${i + 1}: 成功续期 ${bot.services.length} 个服务\n`;
        } else {
            summaryMsg += `账号 ${i + 1}: 登录失败,请更新 Cookie\n`;
        }
        
        console.log('\n----------------------------------------\n');
        if (i < HIDEN_COOKIES_ENV.length - 1) await sleep(5000, 10000);
    }

    // 发送推送
    if (summaryMsg) {
        await sendNotify('HidenCloud 续期报告', summaryMsg);
    }
})();

3. 设置环境变量

环境变量 中添加变量:

变量名必填说明
HIDEN_COOKIE你的 HidenCloud 面板 Cookie

如何获取 Cookie:

  1. 浏览器打开并登录 HidenCloud Dashboard
  2. F12 打开开发者工具,点击 网络 (Network) 标签。
  3. 刷新页面,点击第一个请求( dashboardmanage)。
  4. 在右侧 请求头 (Request Headers) 中找到 Cookie,复制后面的一长串内容。

多账号设置:
如果有多个账号,直接新建多个同名变量 HIDEN_COOKIE,或者在一个变量值里用 & 或换行符分隔。

4. 设置定时任务

建议手动续期 84 天后,每 7 天执行一次,避免错过续期窗口。
Cron 表达式示例:0 10 */7 * * (每 7 天上午 10 点执行)


消息推送配置说明

本脚本已深度集成青龙面板自带的通知系统 (sendNotify.js)。你只需要在青龙面板配置好推送方式,脚本运行结束后就会自动发送报告。

支持的推送渠道

微信 (Server 酱 / 企业微信)、Telegram、钉钉、飞书、Bark、PushPlus 等青龙支持的所有渠道。

配置步骤

  1. 打开青龙面板 → 系统设置通知设置
  2. 选择你喜欢的推送方式(例如 PushPlusTelegram)。
  3. 填入相应的 Token 或 Key,点击保存并测试。
  4. 无需修改脚本代码。脚本会自动检测青龙的通知配置。

推送效果预览

任务完成后,你会收到类似如下的通知:

HidenCloud 续期报告

账号 1: 成功续期 2 个服务
账号 2: 成功续期 1 个服务
账号 3: 登录失败,请更新 Cookie


脚本逻辑说明

  1. 初始化:脚本启动时,优先读取本地缓存文件 hiden_cookies.json 中的 Cookie。如果缓存不存在或失效,则回退使用环境变量 HIDEN_COOKIE
  2. 检测:登录 Dashboard,自动扫描账号下所有活跃的服务 ID。
  3. 续期:进入管理页面,获取 CSRF Token,提交续期 10 天的请求。
  4. 支付
    • 续期成功后,脚本会自动检测是否跳转到了 Invoice 页面。
    • 如果是,直接在当前页面提取表单参数并提交支付。
    • 如果未跳转,脚本会额外检查 “未支付账单” 列表,确保没有漏网之鱼。
  5. 保存:运行结束时,将最新的有效 Cookie 写入本地缓存,供下次使用。


常见问题

Q: 提示 403 Forbidden / Cloudflare 拦截?
A: 这通常是因为你的青龙面板服务器 IP (如阿里云、腾讯云数据中心 IP) 被 Cloudflare 拉黑了。

  • 解决方法:尝试在本地电脑(家庭宽带)运行脚本,或者给青龙面板配置代理。

Q: 为什么日志显示 “支付成功” 但网页看还是未支付?
A: HidenCloud 后端偶尔有延迟。只要日志显示 Status 200支付成功,通常稍等几分钟刷新网页即可变更为 Paid。

Q: 必须要手动抓包 Cookie 吗?
A: 第一次必须手动抓取。之后脚本会自动维护 Cookie,除非服务器强制登出所有会话,否则不需要频繁更新。


免责声明

本脚本仅供学习交流使用,请勿用于非法用途。使用本脚本产生的任何后果由使用者自行承担。



📌 转载信息
转载时间:
2026/1/14 10:45:15

标签: 青龙面板, NODE.JS, HidenCloud, 自动续期, 账单支付

添加新评论