564 lines
16 KiB
JavaScript
564 lines
16 KiB
JavaScript
// # Source: https://github.com/huwangkeji/juejin_checkin/blob/main/juejin_checkin.js
|
||
// # Raw: https://raw.githubusercontent.com/huwangkeji/juejin_checkin/main/juejin_checkin.js
|
||
// # Repo: huwangkeji/juejin_checkin
|
||
// # Path: juejin_checkin.js
|
||
// # UploadedAt: 2026-05-19T14:34:11Z
|
||
// # SHA256: 6f8a138b51c397220444c9312040dbe3407af5aeed4fa4467a36672aeb8bcfd1
|
||
// # Category: web版/账密
|
||
// # Evidence: web/H5关键词 + username/password/login
|
||
//
|
||
|
||
/*
|
||
* @name 掘金自动签到脚本
|
||
* @description 掘金社区每日签到 + 免费抽奖 + 沾喜气 + Bug消除,支持Cookie自动签到
|
||
* @namespace https://juejin.cn
|
||
* @version 1.0.0
|
||
* @author CodeBuddy
|
||
*
|
||
* 【青龙面板使用说明】
|
||
* 1. 依赖管理 -> NodeJS -> 添加依赖: axios
|
||
* 2. 环境变量 (必填):
|
||
* - JUEJIN_COOKIE: 掘金Cookie (从浏览器开发者工具复制)
|
||
* - JUEJIN_AID: API参数aid (通常为2608)
|
||
* - JUEJIN_UUID: API参数uuid
|
||
* - JUEJIN_MSTOKEN: API参数msToken
|
||
* - JUEJIN_A_BOGUS: API参数a_bogus
|
||
* 3. 环境变量 (可选):
|
||
* - JUEJIN_COOKIE_2 ~ JUEJIN_COOKIE_5: 多账号Cookie
|
||
* - JUEJIN_NOTIFY: 是否推送通知 (默认true)
|
||
* 4. 定时规则: 30 6 * * * (每天早上6:30执行)
|
||
*
|
||
* 【参数获取方式】
|
||
* 1. 浏览器打开 https://juejin.cn 并登录
|
||
* 2. 按F12打开开发者工具 -> Network(网络)标签
|
||
* 3. 刷新页面,找到任意一个 api.juejin.cn 的请求
|
||
* 4. 在 Request Headers 中复制完整的 Cookie 值
|
||
* 5. 在 Query String Parameters 中复制 aid, uuid, msToken, a_bogus
|
||
* 6. 粘贴到青龙面板对应的环境变量中
|
||
* 7. Cookie和参数有效期约30天,过期后需重新获取
|
||
*
|
||
* 【功能列表】
|
||
* - Cookie自动签到(支持多账号)
|
||
* - 每日签到
|
||
* - 免费抽奖
|
||
* - 沾喜气
|
||
* - Bug消除
|
||
* - 查询矿石余额/签到天数/幸运值
|
||
* - 青龙面板通知推送
|
||
*/
|
||
|
||
const axios = require('axios');
|
||
|
||
// ==================== 配置区域 ====================
|
||
|
||
const API_BASE = 'https://api.juejin.cn';
|
||
|
||
// 请求头
|
||
const DEFAULT_HEADERS = {
|
||
'User-Agent':
|
||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36',
|
||
'Referer': 'https://juejin.cn/',
|
||
'Origin': 'https://juejin.cn',
|
||
'Content-Type': 'application/json',
|
||
'sec-ch-ua': '"Not/A)Brand";v="99", "Chromium";v="148"',
|
||
'sec-ch-ua-mobile': '?0',
|
||
'sec-ch-ua-platform': '"Windows"',
|
||
};
|
||
|
||
// ==================== 工具函数 ====================
|
||
|
||
function log(msg) {
|
||
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' });
|
||
console.log(`[${now}] ${msg}`);
|
||
}
|
||
|
||
function sleep(ms) {
|
||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||
}
|
||
|
||
/**
|
||
* 创建带Cookie和参数的axios实例
|
||
*/
|
||
function createApiClient(cookie, params) {
|
||
const instance = axios.create({
|
||
baseURL: API_BASE,
|
||
headers: {
|
||
...DEFAULT_HEADERS,
|
||
Cookie: cookie,
|
||
},
|
||
timeout: 30000,
|
||
});
|
||
|
||
// 请求拦截 - 自动添加查询参数
|
||
instance.interceptors.request.use((config) => {
|
||
if (params && Object.keys(params).length > 0) {
|
||
config.params = { ...config.params, ...params };
|
||
}
|
||
// POST请求body为空对象
|
||
if (config.method === 'post' && !config.data) {
|
||
config.data = {};
|
||
}
|
||
return config;
|
||
});
|
||
|
||
// 响应拦截
|
||
instance.interceptors.response.use(
|
||
(response) => {
|
||
const data = response.data;
|
||
// 处理空响应
|
||
if (data === '' || data === null || data === undefined) {
|
||
throw new Error('API返回空响应,可能需要更新参数');
|
||
}
|
||
if (typeof data === 'object' && data.err_no !== 0) {
|
||
throw new Error(`API错误: [${data.err_no}] ${data.err_msg || '未知错误'}`);
|
||
}
|
||
return data.data;
|
||
},
|
||
(error) => {
|
||
if (error.response) {
|
||
const data = error.response.data;
|
||
if (data && typeof data === 'object' && data.err_no !== undefined) {
|
||
throw new Error(`API错误: [${data.err_no}] ${data.err_msg || '未知错误'}`);
|
||
}
|
||
throw new Error(`HTTP错误: ${error.response.status} ${error.response.statusText}`);
|
||
}
|
||
throw error;
|
||
}
|
||
);
|
||
|
||
return instance;
|
||
}
|
||
|
||
// ==================== API 调用 ====================
|
||
|
||
/**
|
||
* 检查今日签到状态
|
||
*/
|
||
async function getTodayStatus(api) {
|
||
try {
|
||
const data = await api.get('/growth_api/v1/get_today_status');
|
||
return data === true;
|
||
} catch (e) {
|
||
log('⚠️ 查询签到状态失败: ' + e.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 执行签到
|
||
*/
|
||
async function checkIn(api) {
|
||
try {
|
||
const data = await api.post('/growth_api/v1/check_in');
|
||
log('✅ 签到成功!');
|
||
return { success: true, data };
|
||
} catch (e) {
|
||
if (e.message.includes('15001') || e.message.includes('已签到') || e.message.includes('duplicate')) {
|
||
log('📌 今日已签到,无需重复签到');
|
||
return { success: true, already: true };
|
||
}
|
||
if (e.message.includes('3013')) {
|
||
log('📌 今日已签到 (3013)');
|
||
return { success: true, already: true };
|
||
}
|
||
if (e.message.includes('空响应')) {
|
||
log('⚠️ API返回空响应,参数可能已过期,建议重新获取');
|
||
return { success: false, error: e.message };
|
||
}
|
||
log('❌ 签到失败: ' + e.message);
|
||
return { success: false, error: e.message };
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取签到天数统计
|
||
*/
|
||
async function getCounts(api) {
|
||
try {
|
||
const data = await api.get('/growth_api/v1/get_counts');
|
||
return data;
|
||
} catch (e) {
|
||
log('⚠️ 获取签到统计失败: ' + e.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取当前矿石数
|
||
*/
|
||
async function getCurrentPoint(api) {
|
||
try {
|
||
const data = await api.get('/growth_api/v1/get_cur_point');
|
||
return data;
|
||
} catch (e) {
|
||
log('⚠️ 获取矿石余额失败: ' + e.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取抽奖配置
|
||
*/
|
||
async function getLotteryConfig(api) {
|
||
try {
|
||
const data = await api.get('/growth_api/v1/lottery_config/get');
|
||
return data;
|
||
} catch (e) {
|
||
log('⚠️ 获取抽奖配置失败: ' + e.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 免费抽奖
|
||
*/
|
||
async function drawLottery(api) {
|
||
try {
|
||
const data = await api.post('/growth_api/v1/lottery/draw');
|
||
log('🎉 抽奖成功!奖品: ' + (data.lottery_name || '未知'));
|
||
return { success: true, data };
|
||
} catch (e) {
|
||
log('❌ 抽奖失败: ' + e.message);
|
||
return { success: false, error: e.message };
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 沾喜气
|
||
*/
|
||
async function dipLucky(api) {
|
||
try {
|
||
// 先获取幸运用户列表
|
||
const luckyUsers = await api.post('/growth_api/v1/lottery_history/global_big', {
|
||
page_no: 1,
|
||
page_size: 5,
|
||
});
|
||
|
||
if (luckyUsers && luckyUsers.lotteries && luckyUsers.lotteries.length > 0) {
|
||
const historyId = luckyUsers.lotteries[0].history_id;
|
||
const data = await api.post('/growth_api/v1/lottery_lucky/dip_lucky', {
|
||
lottery_history_id: historyId,
|
||
});
|
||
log('🍀 沾喜气成功!幸运值: ' + (data.dip_value || 0));
|
||
return { success: true, data };
|
||
}
|
||
log('⚠️ 暂无可沾喜气的用户');
|
||
return { success: false };
|
||
} catch (e) {
|
||
log('❌ 沾喜气失败: ' + e.message);
|
||
return { success: false, error: e.message };
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取我的幸运值
|
||
*/
|
||
async function getMyLucky(api) {
|
||
try {
|
||
const data = await api.post('/growth_api/v1/lottery_lucky/my_lucky');
|
||
return data;
|
||
} catch (e) {
|
||
log('⚠️ 获取幸运值失败: ' + e.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 查询用户信息 (验证Cookie有效性)
|
||
*/
|
||
async function getUserInfo(api) {
|
||
try {
|
||
const data = await api.get('/user_api/v1/user/get');
|
||
return data;
|
||
} catch (e) {
|
||
log('⚠️ Cookie无效或已过期: ' + e.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取Bug消除配置
|
||
*/
|
||
async function getBugFixConfig(api) {
|
||
try {
|
||
const data = await api.get('/growth_api/v1/bugfix/not_collect');
|
||
return data;
|
||
} catch (e) {
|
||
log('⚠️ 获取Bug配置失败: ' + e.message);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 消除Bug
|
||
*/
|
||
async function fixBug(api, bugId) {
|
||
try {
|
||
const data = await api.post('/growth_api/v1/bugfix/collect', {
|
||
bug_type: bugId,
|
||
});
|
||
log('🐛 Bug消除成功!');
|
||
return { success: true, data };
|
||
} catch (e) {
|
||
log('❌ Bug消除失败: ' + e.message);
|
||
return { success: false, error: e.message };
|
||
}
|
||
}
|
||
|
||
// ==================== 青龙面板通知 ====================
|
||
|
||
/**
|
||
* 青龙面板通知推送
|
||
*/
|
||
async function sendNotify(title, content) {
|
||
if (process.env.JUEJIN_NOTIFY === 'false') {
|
||
log('📢 通知已禁用');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 尝试加载青龙面板的通知模块
|
||
const notifyPath = require('path').join(process.cwd(), 'notify.js');
|
||
if (require('fs').existsSync(notifyPath)) {
|
||
const sendNotifyFn = require(notifyPath);
|
||
if (typeof sendNotifyFn === 'function') {
|
||
await sendNotifyFn(title, content);
|
||
log('📢 青龙通知已发送');
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 尝试从全局加载
|
||
try {
|
||
const { sendNotify: qinglongNotify } = require('qlnotify');
|
||
await qinglongNotify(title, content);
|
||
log('📢 青龙通知已发送');
|
||
return;
|
||
} catch (e) {
|
||
// 忽略
|
||
}
|
||
|
||
log('📢 青龙通知模块未找到,仅控制台输出');
|
||
} catch (e) {
|
||
log('⚠️ 发送通知失败: ' + e.message);
|
||
}
|
||
}
|
||
|
||
// ==================== 单个账号签到流程 ====================
|
||
|
||
async function runAccount(cookie, params, index) {
|
||
const prefix = index > 1 ? `[账号${index}] ` : '';
|
||
log('');
|
||
log(`${prefix}═══════════════════════════════════════`);
|
||
log(`${prefix} 开始处理账号 ${index}`);
|
||
log(`${prefix}═══════════════════════════════════════`);
|
||
|
||
if (!cookie || cookie.trim().length === 0) {
|
||
log(`${prefix}❌ Cookie为空,跳过`);
|
||
return null;
|
||
}
|
||
|
||
// 创建API客户端
|
||
const api = createApiClient(cookie, params);
|
||
|
||
// 验证Cookie有效性
|
||
log(`${prefix}🔍 验证Cookie...`);
|
||
const userInfo = await getUserInfo(api);
|
||
if (!userInfo) {
|
||
log(`${prefix}❌ Cookie已失效,请重新获取`);
|
||
return {
|
||
success: false,
|
||
error: 'Cookie已失效',
|
||
userName: '未知用户',
|
||
};
|
||
}
|
||
|
||
const userName = userInfo.user_name || userInfo.nick_name || '未知用户';
|
||
log(`${prefix}✅ Cookie有效!用户: ${userName}`);
|
||
|
||
// 签到
|
||
log(`${prefix}📅 执行签到...`);
|
||
const todayStatus = await getTodayStatus(api);
|
||
let signInResult = { success: true, already: todayStatus === true };
|
||
|
||
if (todayStatus === true) {
|
||
log(`${prefix}📌 今日已签到,跳过`);
|
||
} else if (todayStatus === false) {
|
||
signInResult = await checkIn(api);
|
||
if (!signInResult.success) {
|
||
log(`${prefix}🔄 签到失败,1秒后重试...`);
|
||
await sleep(1000);
|
||
signInResult = await checkIn(api);
|
||
}
|
||
} else {
|
||
log(`${prefix}⚠️ 无法查询签到状态,直接尝试签到...`);
|
||
signInResult = await checkIn(api);
|
||
}
|
||
|
||
// 抽奖
|
||
log(`${prefix}🎰 执行抽奖...`);
|
||
const lotteryConfig = await getLotteryConfig(api);
|
||
let lotteryResult = { success: false };
|
||
if (lotteryConfig && lotteryConfig.free_count > 0) {
|
||
log(`${prefix}🎁 有 ${lotteryConfig.free_count} 次免费抽奖机会`);
|
||
lotteryResult = await drawLottery(api);
|
||
} else if (lotteryConfig) {
|
||
log(`${prefix}📌 今日免费抽奖次数已用完`);
|
||
}
|
||
|
||
// 沾喜气
|
||
log(`${prefix}🍀 沾喜气...`);
|
||
const dipResult = await dipLucky(api);
|
||
|
||
// Bug消除
|
||
log(`${prefix}🐛 消除Bug...`);
|
||
const bugConfig = await getBugFixConfig(api);
|
||
let bugFixResult = { success: false };
|
||
if (bugConfig && Array.isArray(bugConfig) && bugConfig.length > 0) {
|
||
const bug = bugConfig[0];
|
||
log(`${prefix}🔧 发现未消除的Bug: ${bug.bug_type}`);
|
||
bugFixResult = await fixBug(api, bug.bug_type);
|
||
} else {
|
||
log(`${prefix}📌 暂无Bug可消除`);
|
||
}
|
||
|
||
// 查询统计信息
|
||
log(`${prefix}📊 查询统计...`);
|
||
const counts = await getCounts(api);
|
||
const point = await getCurrentPoint(api);
|
||
const lucky = await getMyLucky(api);
|
||
|
||
if (counts) {
|
||
log(`${prefix}📅 连续签到: ${counts.cont_count || 0} 天`);
|
||
log(`${prefix}📅 累计签到: ${counts.sum_count || 0} 天`);
|
||
}
|
||
if (point !== null) {
|
||
log(`${prefix}💰 当前矿石: ${point}`);
|
||
}
|
||
if (lucky) {
|
||
log(`${prefix}🍀 幸运值: ${lucky.total_value || 0}`);
|
||
}
|
||
|
||
log(`${prefix}✅ 账号处理完成`);
|
||
|
||
return {
|
||
success: true,
|
||
userName,
|
||
signIn: signInResult,
|
||
lottery: lotteryResult,
|
||
dip: dipResult,
|
||
bugFix: bugFixResult,
|
||
counts,
|
||
point,
|
||
lucky,
|
||
};
|
||
}
|
||
|
||
// ==================== 主流程 ====================
|
||
|
||
async function main() {
|
||
log('═══════════════════════════════════════');
|
||
log(' 掘金自动签到脚本 v1.0.0');
|
||
log('═══════════════════════════════════════');
|
||
|
||
// 收集所有账号的配置
|
||
const accounts = [];
|
||
|
||
// 主账号
|
||
const mainCookie = process.env.JUEJIN_COOKIE || '';
|
||
const mainParams = {
|
||
aid: process.env.JUEJIN_AID || '2608',
|
||
uuid: process.env.JUEJIN_UUID || '',
|
||
msToken: process.env.JUEJIN_MSTOKEN || '',
|
||
a_bogus: process.env.JUEJIN_A_BOGUS || '',
|
||
spider: '0',
|
||
};
|
||
|
||
if (mainCookie) {
|
||
accounts.push({ cookie: mainCookie, params: mainParams });
|
||
}
|
||
|
||
// 多账号支持
|
||
for (let i = 2; i <= 5; i++) {
|
||
const cookie = process.env[`JUEJIN_COOKIE_${i}`] || '';
|
||
if (cookie) {
|
||
accounts.push({
|
||
cookie,
|
||
params: {
|
||
aid: process.env[`JUEJIN_AID_${i}`] || mainParams.aid,
|
||
uuid: process.env[`JUEJIN_UUID_${i}`] || '',
|
||
msToken: process.env[`JUEJIN_MSTOKEN_${i}`] || '',
|
||
a_bogus: process.env[`JUEJIN_A_BOGUS_${i}`] || '',
|
||
spider: '0',
|
||
},
|
||
});
|
||
}
|
||
}
|
||
|
||
if (accounts.length === 0) {
|
||
log('');
|
||
log('❌ 未配置Cookie,请设置环境变量 JUEJIN_COOKIE');
|
||
log('');
|
||
log('【参数获取方式】');
|
||
log('1. 浏览器打开 https://juejin.cn 并登录');
|
||
log('2. 按F12打开开发者工具 -> Network(网络)标签');
|
||
log('3. 刷新页面,找到任意一个 api.juejin.cn 的请求');
|
||
log('4. 在 Request Headers 中复制完整的 Cookie 值');
|
||
log('5. 在 Query String Parameters 中复制 aid, uuid, msToken, a_bogus');
|
||
log('6. 粘贴到青龙面板对应的环境变量中');
|
||
log('');
|
||
process.exit(1);
|
||
}
|
||
|
||
log(`📱 共配置 ${accounts.length} 个账号`);
|
||
|
||
// 执行每个账号的签到
|
||
const results = [];
|
||
for (let i = 0; i < accounts.length; i++) {
|
||
const result = await runAccount(accounts[i].cookie, accounts[i].params, i + 1);
|
||
if (result) results.push(result);
|
||
}
|
||
|
||
// 生成通知内容
|
||
log('');
|
||
log('📢 发送通知...');
|
||
|
||
let notifyTitle = '掘金每日签到报告';
|
||
let notifyContent = '';
|
||
|
||
for (let i = 0; i < results.length; i++) {
|
||
const r = results[i];
|
||
const prefix = results.length > 1 ? `【账号${i + 1}】` : '';
|
||
|
||
if (!r.success) {
|
||
notifyContent += `${prefix}❌ ${r.userName}: ${r.error}\n`;
|
||
continue;
|
||
}
|
||
|
||
notifyContent += `${prefix}👤 ${r.userName}\n`;
|
||
notifyContent += ` 📅 连续签到: ${r.counts?.cont_count || 0} 天\n`;
|
||
notifyContent += ` 📅 累计签到: ${r.counts?.sum_count || 0} 天\n`;
|
||
notifyContent += ` 💰 当前矿石: ${r.point ?? '未知'}\n`;
|
||
notifyContent += ` 🍀 幸运值: ${r.lucky?.total_value || 0}\n`;
|
||
notifyContent += ` ✅ 签到: ${r.signIn.already ? '今日已签到' : r.signIn.success ? '成功' : '失败'}\n`;
|
||
notifyContent += ` 🎰 抽奖: ${r.lottery.success ? '成功 - ' + (r.lottery.data?.lottery_name || '') : '未抽奖/失败'}\n`;
|
||
notifyContent += ` 🍀 沾喜气: ${r.dip.success ? '成功' : '失败'}\n`;
|
||
notifyContent += ` 🐛 Bug消除: ${r.bugFix.success ? '成功' : '无/失败'}\n`;
|
||
notifyContent += '\n';
|
||
}
|
||
|
||
await sendNotify(notifyTitle, notifyContent);
|
||
|
||
log('');
|
||
log('═══════════════════════════════════════');
|
||
log(' 脚本执行完成!');
|
||
log('═══════════════════════════════════════');
|
||
}
|
||
|
||
// 执行主函数
|
||
main().catch((error) => {
|
||
log('💥 脚本异常退出: ' + error.message);
|
||
console.error(error);
|
||
process.exit(1);
|
||
});
|