1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/W9mv4W若失效,可用地址
阿里云:
服务器购买地址
https://t.aliyun.com/U/W9mv4W若失效,可用地址
https://www.aliyun.com/minisite/goods?source=5176.29345612&userCode=49hts92d腾讯云:
https://curl.qcloud.com/wJpWmSfU若失效,可用地址
https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=ad201ee2ef3b771157f72ee5464b1fea&from=console华为云
https://activity.huaweicloud.com/cps.html?fromacct=64b5cf7cc11b4840bb4ed2ea0b2f4468&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=2019052.部署教程
3.代码如下
import axios from "axios";import moment from "moment";import dotenv from "dotenv";import { fileURLToPath } from "url";import { dirname } from "path";// 初始化环境变量和路径const __dirname = dirname(fileURLToPath(import.meta.url));dotenv.config({ path: `${__dirname}/.env` });class NineBot {constructor(deviceId, authorization, name = "九号出行") {if (!deviceId || !authorization) {throw new Error("缺少必要的参数: deviceId 或 authorization");}this.msg = [];this.name = name; // 账号名称(支持自定义)this.deviceId = deviceId;this.headers = {Accept: "application/json, text/plain, */*",Authorization: authorization,"Accept-Encoding": "gzip, deflate, br","Accept-Language": "zh-CN,zh-Hans;q=0.9","Content-Type": "application/json",Host: "cn-cbu-gateway.ninebot.com",Origin: "https://h5-bj.ninebot.com",from_platform_1: "1",language: "zh","User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 Segway v6 C 609033420",Referer: "https://h5-bj.ninebot.com/",};// API端点this.endpoints = {sign: "https://cn-cbu-gateway.ninebot.com/portal/api/user-sign/v2/sign",status: "https://cn-cbu-gateway.ninebot.com/portal/api/user-sign/v2/status"};// 请求配置this.requestConfig = {timeout: 10000,retry: 3,retryDelay: 2000};}// 带重试机制的请求方法async makeRequest(method, url, data = null) {let attempts = 0;const maxAttempts = this.requestConfig.retry;while (attempts < maxAttempts) {try {console.log(`[${this.name}] 尝试 ${attempts + 1}/${maxAttempts}: ${method} ${url}`);const response = await axios({method,url,data,headers: this.headers,timeout: this.requestConfig.timeout});console.log(`[${this.name}] 请求成功: ${url}`);return response.data;} catch (error) {attempts++;console.error(`[${this.name}] 请求失败 (${attempts}/${maxAttempts}):`, error.message);if (attempts === maxAttempts) {throw error;}await new Promise(resolve => setTimeout(resolve, this.requestConfig.retryDelay));}}}// 执行签到async sign() {try {console.log(`[${this.name}] 开始签到...`);const responseData = await this.makeRequest("post",this.endpoints.sign,{ deviceId: this.deviceId });if (responseData.code === 0) {console.log(`[${this.name}] 签到成功`);return true;} else {const errorMsg = responseData.msg || "未知错误";this.msg.push({ name: "签到结果", value: `签到失败: ${errorMsg}` });console.error(`[${this.name}] 签到失败:`, errorMsg);return false;}} catch (error) {this.handleError("签到", error);return false;}}// 验证登录状态并获取签到信息async valid() {try {console.log(`[${this.name}] 验证登录状态并获取签到信息...`);const timestamp = moment().valueOf();const responseData = await this.makeRequest("get",`${this.endpoints.status}?t=${timestamp}`);if (responseData.code === 0) {console.log(`[${this.name}] 验证成功,获取到签到信息`);return [responseData.data, ""];}const errorMsg = responseData.msg || "验证失败";console.error(`[${this.name}] 验证失败:`, errorMsg);return [false, errorMsg];} catch (error) {const errorMsg = `登录验证异常: ${this.getErrorMessage(error)}`;console.error(`[${this.name}] ${errorMsg}`);return [false, errorMsg];}}// 错误处理handleError(action, error) {const errorMessage = this.getErrorMessage(error);console.error(`[${this.name}] ${action}错误:`, errorMessage);this.msg.push({ name: `${action}结果`, value: `${action}失败` },{ name: "错误详情", value: errorMessage });}// 提取错误信息getErrorMessage(error) {return error.response? `状态码: ${error.response.status}, 信息: ${error.response.data?.msg || error.message}`: error.message;}// 获取日志信息get logs() {return this.msg.map((one) => `${one.name}: ${one.value}`).join("\n");}// 运行签到流程async run() {try {console.log(`[${this.name}] 开始执行签到任务...`);// 首次获取签到状态let [validData, errInfo] = await this.valid();if (validData) {const completed = validData.currentSignStatus === 1;// 记录初始状态this.msg.push({name: "连续签到天数",value: `${validData.consecutiveDays || 0}天`,});this.msg.push({name: "今日签到状态",value: completed ? "已签到🎉" : "未签到❌",});if (!completed) {// 执行签到const signSuccess = await this.sign();if (signSuccess) {// 签到成功后重新获取最新状态console.log(`[${this.name}] 签到成功,获取最新签到数据...`);const [newValidData] = await this.valid();if (newValidData) {// 更新连续签到天数为最新值this.msg = this.msg.map(item =>item.name === "连续签到天数"? { name: "连续签到天数", value: `${newValidData.consecutiveDays || 0}天` }: item);// 更新今日签到状态this.msg = this.msg.map(item =>item.name === "今日签到状态"? { name: "今日签到状态", value: "已签到🎉" }: item);this.msg.push({ name: "签到结果", value: "签到成功🎉🎉" });} else {this.msg.push({ name: "签到结果", value: "签到成功,但获取最新状态失败" });}}} else {console.log(`[${this.name}] 今日已签到,无需重复签到`);}} else {this.msg.push({ name: "验证结果", value: errInfo });}} catch (error) {this.msg.push({ name: "执行结果", value: `执行异常: ${error.message}` });console.error(`[${this.name}] 执行异常:`, error);} finally {console.log(`[${this.name}] 任务执行完成`);}}}// 发送Bark通知(支持完整参数配置)async function sendBarkNotification(title, message) {// 从环境变量获取Bark配置const barkUrl = process.env.BARK_URL || "https://api.day.app";const barkKey = process.env.BARK_KEY;// 没有Bark密钥则不发送if (!barkKey) {console.log("未配置BARK_KEY,跳过Bark通知");return false;}try {// 构建基础URLlet url = `${barkUrl}/${barkKey}/${encodeURIComponent(title)}/${encodeURIComponent(message)}`;// 收集所有可选参数const params = [];// 通知分组if (process.env.BARK_GROUP) {params.push(`group=${encodeURIComponent(process.env.BARK_GROUP)}`);}// 通知图标if (process.env.BARK_ICON) {params.push(`icon=${encodeURIComponent(process.env.BARK_ICON)}`);}// 通知铃声if (process.env.BARK_SOUND) {params.push(`sound=${encodeURIComponent(process.env.BARK_SOUND)}`);}// 点击跳转URLif (process.env.BARK_URL_JUMP) {params.push(`url=${encodeURIComponent(process.env.BARK_URL_JUMP)}`);}// 可复制文本if (process.env.BARK_COPY) {// 提取连续天数用于替换变量const dayMatch = message.match(/连续签到天数: (\d+)天/);const day = dayMatch ? dayMatch[1] : "未知";const copyText = process.env.BARK_COPY.replace('%day%', day);params.push(`copy=${encodeURIComponent(copyText)}`);}// 自动复制if (process.env.BARK_AUTO_COPY === '1') {params.push(`autoCopy=1`);}// 添加参数到URLif (params.length > 0) {url += `?${params.join('&')}`;}console.log(`发送Bark通知: ${url}`);// 发送请求const response = await axios.get(url, { timeout: 5000 });if (response.data.code === 200) {console.log("Bark通知发送成功");return true;} else {console.error("Bark通知发送失败:", response.data);return false;}} catch (error) {console.error("发送Bark通知异常:", error.message);return false;}}// 初始化并执行签到async function init() {// 处理多账号配置let accounts = [];if (process.env.NINEBOT_ACCOUNTS) {try {accounts = JSON.parse(process.env.NINEBOT_ACCOUNTS);// 为每个账号添加默认名称(如果未配置)accounts = accounts.map((acc, index) => ({name: acc.name || `账号${index + 1}`, // 默认为"账号1"、"账号2"deviceId: acc.deviceId,authorization: acc.authorization}));} catch (e) {console.error("NINEBOT_ACCOUNTS 格式错误:", e.message);return;}}// 处理单账号配置else if (process.env.NINEBOT_DEVICE_ID && process.env.NINEBOT_AUTHORIZATION) {accounts.push({name: process.env.NINEBOT_NAME || "默认账号", // 支持单账号设置名称deviceId: process.env.NINEBOT_DEVICE_ID,authorization: process.env.NINEBOT_AUTHORIZATION});} else {console.error("未配置任何账号信息");return;}// 执行所有账号的签到并收集结果const allResults = [];for (const account of accounts) {console.log(`\n===== 开始处理账号: ${account.name} =====`);try {const bot = new NineBot(account.deviceId, account.authorization, account.name);await bot.run();allResults.push({name: account.name,success: bot.logs.includes("签到成功") || bot.logs.includes("已签到"),logs: bot.logs});} catch (e) {allResults.push({name: account.name,success: false,logs: `初始化失败: ${e.message}`});}}// 生成汇总通知内容const title = "九号出行签到结果";let message = allResults.map(acc => {const status = acc.success ? "✅" : "❌";return `${status} ${acc.name}\n${acc.logs.replace(/\n/g, "\n ")}`;}).join("\n\n");// 发送Bark通知await sendBarkNotification(title, message);}// 启动执行init();
该脚本为九号出行(Ninebot)签到脚本,主要作用:
使用抓到的
authorization(九号出行 H5/App 接口鉴权)对设备ID执行每日签到。先查询今日签到状态,未签到再调用签到接口;成功后二次查询以回填"连续签到天数/今日状态"。
支持多账号(环境变量 JSON 列表)与单账号两种配置方式。
结果可通过 Bark 推送(可设置分组、图标、声音、点击跳转、自动复制等)。
主要方法
初始化与配置
构造函数
constructor(deviceId, authorization, name)
校验必要参数;设置统一请求头(含Authorization、UA 等)、接口地址(sign/status)、超时与重试策略;name用于日志/区分账号。环境变量
多账号:
NINEBOT_ACCOUNTS(JSON 数组,元素含name?、deviceId、authorization)单账号:
NINEBOT_DEVICE_ID、NINEBOT_AUTHORIZATION、NINEBOT_NAME?Bark:
BARK_KEY(必填否则不发)、BARK_URL?、BARK_GROUP?、BARK_ICON?、BARK_SOUND?、BARK_URL_JUMP?、BARK_COPY?(支持占位%day%)、BARK_AUTO_COPY=1?
请求与错误处理
makeRequest(method, url, data)
统一的 Axios 请求封装,带重试(默认 3 次、间隔 2s)与超时(10s)。handleError(action, error)/getErrorMessage(error)
规范化错误信息并记录到this.msg。
业务逻辑
valid()GET /user-sign/v2/status查询当前签到状态与连续天数。返回[data, ""]或[false, 错误信息]。sign()POST /user-sign/v2/sign执行签到(入参含deviceId)。成功返回true,否则记录失败原因。run()(单账号完整流程)调
valid()取今日状态与连续天数并记录到msg;若未签到则调
sign();成功后再次valid()以刷新连续天数与"今日状态";整个过程中的关键信息累积到
this.msg。logsgetter
将累积消息格式化为多行文本,便于汇总/通知。
通知与入口
sendBarkNotification(title, message)
读取 Bark 配置,按可选参数拼 URL 并发送通知;支持分组、图标、声音、跳转、复制文本/自动复制。init()(脚本入口)
解析账号配置 → 为每个账号实例化NineBot并执行run()→ 汇总各账号结果(成功/失败+日志) → 统一调用 Bark 推送。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论