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=201905
2.部署教程
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 {
// 构建基础URL
let 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)}`);
}
// 点击跳转URL
if (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`);
}
// 添加参数到URL
if (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
。logs
getter
将累积消息格式化为多行文本,便于汇总/通知。
通知与入口
sendBarkNotification(title, message)
读取 Bark 配置,按可选参数拼 URL 并发送通知;支持分组、图标、声音、跳转、复制文本/自动复制。init()
(脚本入口)
解析账号配置 → 为每个账号实例化NineBot
并执行run()
→ 汇总各账号结果(成功/失败+日志) → 统一调用 Bark 推送。
注意:
本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。
没有评论:
发表评论