diff --git a/README.md b/README.md index 92dabe9..011a32c 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,10 @@ * 关注用户 * 关注栏目 * 关注品牌 + * 站外浏览 + * 打开第三方 App + * 开启推送 + * 创建桌面签到快捷方式 * 限时累计活动 * 全民众测能量值任务 @@ -62,7 +66,7 @@ ql repo https://github.com/hex-ci/smzdm_script.git "smzdm_" "" "env.js|bot.js|se * `SMZDM_SK`: 这个值是可选值,会自动计算,如果你一定想用自己的,可以抓取,是从安卓 App 的 `https://user-api.smzdm.com/checkin` 请求参数中抓包抓到的,多用户可以用 `&` 分隔,或者使用多个同名环境变量,顺序要保持与 `SMZDM_COOKIE` 多用户顺序一致。 * `SMZDM_USER_AGENT_APP`: 这个值是可选值,是指 APP 的 User-Agent,从 APP 的 API 请求头中抓包得到,建议抓取 Android 的 User-Agent,不填使用脚本默认值。 * `SMZDM_USER_AGENT_WEB`: 这个值是可选值,是指 APP 中访问网页的 User-Agent,一般在 APP 内的转盘网页中抓包得到,建议抓取 Android 的 User-Agent,不填使用脚本默认值。 -* `SMZDM_COMMENT`: 如果要完成评论文章的任务请设置这个环境变量,环境变量的内容是评论的文案,文案要大于 10 个汉字,建议用比较个性化的文案,脚本发布评论后会删除这条评论,但是为防止删除失败的情况,请尽量用好一点的文案,防止被判定为恶意灌水。 +* `SMZDM_COMMENT`: 如果要完成评论文章的任务请设置这个环境变量,环境变量的内容是评论的文案,文案要大于 10 个汉字,建议用比较个性化的文案,脚本发布评论后会删除这条评论,但是为防止删除失败的情况,请尽量用好一点的文案,防止被判定为恶意灌水。可以写多条文案(用换行或 `|` 分隔,每条都要大于 10 个汉字),脚本每次随机选一条,避免每天发同样的内容。 * `SMZDM_CROWD_SILVER_5`: 每日抽奖任务默认只进行免费抽奖,如要进行 5 碎银子的抽奖,请设置这个环境变量的值为 `yes`,请注意,只有在没有免费抽奖的时候,才会执行非免费抽奖,而且,这个抽奖不是转盘抽奖。 * `SMZDM_CROWD_KEYWORD`: 抽奖关键词,执行非免费抽奖时,会优先选择包含此关键词的抽奖,如果未找到包含此关键词的抽奖,则会随机选择一个。 * `SMZDM_TASK_TESTING`: 是否运行全民众测能量值任务,如要运行此任务,请设置这个环境变量的值为 `yes`,否则不运行。 diff --git a/bot.js b/bot.js index ed57f56..5629595 100644 --- a/bot.js +++ b/bot.js @@ -3,28 +3,62 @@ const got = require('got'); // ------------------------------------ -const APP_VERSION = '10.4.26'; -const APP_VERSION_REV = '866'; +const APP_VERSION = '11.1.95'; +const APP_VERSION_REV = '1195'; const DEFAULT_USER_AGENT_APP = `smzdm_android_V${APP_VERSION} rv:${APP_VERSION_REV} (Redmi Note 3;Android10.0;zh)smzdmapp`; const DEFAULT_USER_AGENT_WEB = `Mozilla/5.0 (Linux; Android 10.0; Redmi Build/Redmi Note 3; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/95.0.4638.74 Mobile Safari/537.36 smzdm_android_V${APP_VERSION} rv:${APP_VERSION_REV} (Redmi;Android10.0;zh) jsbv_1.0.0 webv_2.0 smzdmapp`; const SIGN_KEY = 'apr1$AwP!wRRT$gJ/q.X24poeBInlUJC'; +const ZHUANZAI_AB = ''; + +// 无法从设备侧还原的本地开关取值,与全新安装的 APP 行为一致 +const CREATE_CENTER_VIEW = ''; +const IS_INSTALL_ZHANGDAMA = '0'; + +// APP 用 HTTP Date 头作为签名时间,且偏差超过 5 分钟会重新获取 +const SERVER_TIME_URL = 'https://app-api.smzdm.com/get_server_time'; +const SERVER_TIME_MAX_AGE = 5 * 60 * 1000; + // ------------------------------------ +let serverTime = 0; +let serverTimeFetchedAt = 0; + const reVersion = /(smzdm_android_V|smzdm\s|iphone_smzdmapp\/)([\d.]+)/i; const reRev = /rv:([\d.]+)/i; -const randomStr = (len = 18) => { - const char = '0123456789'; - let str = ''; +// request_key = 8 位随机数 + 秒级时间戳 +const requestKey = () => `${Math.floor(Math.random() * 90000000) + 10000000}${Math.floor(Date.now() / 1000)}`; - for (let i = 0; i < len; i++) { - str += char.charAt(Math.floor(Math.random() * char.length)); +const getServerTime = async () => { + if (serverTime && Date.now() - serverTimeFetchedAt <= SERVER_TIME_MAX_AGE) { + return serverTime; } - return str; + try { + // APP 直接读 HTTP Date 头,不关心状态码,取不到才回退本地时间 + const response = await got(SERVER_TIME_URL, { + method: 'GET', + throwHttpErrors: false, + retry: { + limit: 0 + } + }); + + const date = new Date(response.headers.date).getTime(); + + if (date) { + serverTime = date; + serverTimeFetchedAt = Date.now(); + } + } + catch (error) { + console.log(`获取服务器时间失败,使用本地时间: ${error.message}`); + } + + return serverTime || Date.now(); }; const parseJSON = (str) => { @@ -39,19 +73,20 @@ const parseJSON = (str) => { const removeTags = (str) => str.replace(/<[^<]+?>/g, ''); // 添加公共参数并签名数据 -const signFormData = (data) => { +const signFormData = async (data) => { const newData = { weixin: 1, basic_v: 0, f: 'android', v: APP_VERSION, - time: `${Math.round(new Date().getTime() / 1000)}000`, + time: `${Math.round((await getServerTime()) / 1000)}000`, + zhuanzai_ab: ZHUANZAI_AB, ...data }; const keys = Object.keys(newData).filter(key => newData[key] !== '').sort(); - const signData = keys.map(key => `${key}=${String(newData[key]).replace(/\s+/, '')}`).join('&'); - const sign = crypto.createHash('md5').update(`${signData}&key=${SIGN_KEY}`).digest('hex').toUpperCase(); + const signData = keys.map(key => `${key}=${newData[key]}`).join('&'); + const sign = crypto.createHash('md5').update(`${signData.replace(/ /g, '')}&key=${SIGN_KEY}`).digest('hex').toUpperCase(); return { ...newData, @@ -74,7 +109,7 @@ const requestApi = async (url, inputOptions = {}) => { Object.keys(options.data).forEach(key => options.data[key] === undefined && delete options.data[key]); if (options.sign !== false) { - options.data = signFormData(options.data); + options.data = await signFormData(options.data); } const gotOptions = { @@ -139,10 +174,22 @@ const requestApi = async (url, inputOptions = {}) => { }) }; -const updateCookie = (cookie, name, value) => { - const re = new RegExp(`(^|;)${name}=[^;]+;`, 'ig'); +// APP 每次请求都用自己维护的字段重建 Cookie,缺失的字段需要补上 +const setCookie = (cookie, name, value) => { + const re = new RegExp(`(^|;)\\s*${name}=[^;]+`, 'i'); - return cookie.replace(re, `$1${name}=${encodeURIComponent(value)};`); + if (re.test(cookie)) { + return cookie.replace(re, `$1${name}=${encodeURIComponent(value)}`); + } + + return `${cookie.replace(/;?\s*$/, ';')} ${name}=${encodeURIComponent(value)};`; +}; + +// APP 不发送这些历史字段,保留会导致请求与 APP 不一致 +const removeCookie = (cookie, name) => { + const re = new RegExp(`(^|;)\\s*${name}=[^;]+;?`, 'ig'); + + return cookie.replace(re, '$1'); }; const getEnvCookies = () => { @@ -179,6 +226,14 @@ const wait = (minSecond, maxSecond) => { return new Promise(resolve => setTimeout(resolve, randomSecond * 1000)); }; +const waitHuman = async (minSecond, maxSecond) => { + await wait(minSecond, maxSecond); + + if (Math.random() < 0.05) { + await wait(15, 45); + } +}; + // ------------------------------------ class SmzdmBot { @@ -190,16 +245,23 @@ class SmzdmBot { // 处理 cookie this.androidCookie = this.cookie.replace('iphone', 'android').replace('iPhone', 'Android'); - this.androidCookie = updateCookie(this.androidCookie, 'smzdm_version', APP_VERSION); - this.androidCookie = updateCookie(this.androidCookie, 'device_smzdm_version', APP_VERSION); - this.androidCookie = updateCookie(this.androidCookie, 'v', APP_VERSION); - this.androidCookie = updateCookie(this.androidCookie, 'device_smzdm_version_code', APP_VERSION_REV); - this.androidCookie = updateCookie(this.androidCookie, 'device_system_version', '10.0'); - this.androidCookie = updateCookie(this.androidCookie, 'apk_partner_name', 'smzdm_download'); - this.androidCookie = updateCookie(this.androidCookie, 'partner_name', 'smzdm_download'); - this.androidCookie = updateCookie(this.androidCookie, 'device_type', 'Android'); - this.androidCookie = updateCookie(this.androidCookie, 'device_smzdm', 'android'); - this.androidCookie = updateCookie(this.androidCookie, 'device_name', 'Android'); + this.androidCookie = setCookie(this.androidCookie, 'v', APP_VERSION); + this.androidCookie = setCookie(this.androidCookie, 'device_smzdm_version_code', APP_VERSION_REV); + this.androidCookie = setCookie(this.androidCookie, 'device_system_version', '10.0'); + this.androidCookie = setCookie(this.androidCookie, 'apk_partner_name', 'smzdm_download'); + this.androidCookie = setCookie(this.androidCookie, 'partner_name', 'smzdm_download'); + this.androidCookie = setCookie(this.androidCookie, 'device_smzdm', 'android'); + this.androidCookie = setCookie(this.androidCookie, 'f', 'android'); + this.androidCookie = setCookie(this.androidCookie, 'basic_v', '0'); + + [ + 'smzdm_version', + 'device_smzdm_version', + 'device_type', + 'device_name' + ].forEach(name => { + this.androidCookie = removeCookie(this.androidCookie, name); + }); } getHeaders() { @@ -215,7 +277,7 @@ class SmzdmBot { Accept: '*/*', 'Accept-Language': 'zh-Hans-CN;q=1', 'Accept-Encoding': 'gzip', - 'request_key': randomStr(18), + 'request_key': requestKey(), 'User-Agent': userAgent, Cookie: this.androidCookie }; @@ -242,6 +304,18 @@ class SmzdmBot { getOneByRandom(listing) { return listing[Math.floor(Math.random() * listing.length)]; } + + // 打乱顺序 + getShuffled(listing) { + const result = listing.slice(); + + for (let i = result.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [result[i], result[j]] = [result[j], result[i]]; + } + + return result; + } } module.exports = { @@ -250,5 +324,9 @@ module.exports = { removeTags, parseJSON, getEnvCookies, - wait + getServerTime, + CREATE_CENTER_VIEW, + IS_INSTALL_ZHANGDAMA, + wait, + waitHuman }; diff --git a/library_task.js b/library_task.js index f841e54..d641f83 100644 --- a/library_task.js +++ b/library_task.js @@ -1,4 +1,10 @@ -const { SmzdmBot, requestApi, removeTags, wait } = require('./bot'); +const { + SmzdmBot, + requestApi, + removeTags, + wait, + waitHuman +} = require('./bot'); // ------------------------------------ @@ -13,8 +19,10 @@ class SmzdmTaskBot extends SmzdmBot { async doTasks(tasks) { let notifyMsg = ''; - for (let i = 0; i < tasks.length; i++) { - const task = tasks[i]; + const list = this.getShuffled(tasks); + + for (let i = 0; i < list.length; i++) { + const task = list[i]; // 待领取任务 if (task.task_status == '3') { @@ -24,7 +32,7 @@ class SmzdmTaskBot extends SmzdmBot { notifyMsg += `${isSuccess ? '🟢' : '❌'}领取[${task.task_name}]奖励${isSuccess ? '成功' : '失败!请查看日志'}\n`; - await wait(5, 15); + await waitHuman(5, 15); } // 未完成任务 else if (task.task_status == '2') { @@ -34,7 +42,7 @@ class SmzdmTaskBot extends SmzdmBot { notifyMsg += this.getTaskNotifyMessage(isSuccess, task); - await wait(5, 15); + await waitHuman(5, 15); } // 分享任务 else if (task.task_event_type == 'interactive.share') { @@ -42,7 +50,7 @@ class SmzdmTaskBot extends SmzdmBot { notifyMsg += this.getTaskNotifyMessage(isSuccess, task); - await wait(5, 15); + await waitHuman(5, 15); } // 抽奖任务 else if (task.task_event_type == 'guide.crowd') { @@ -52,7 +60,7 @@ class SmzdmTaskBot extends SmzdmBot { notifyMsg += this.getTaskNotifyMessage(isSuccess, task); } - await wait(5, 15); + await waitHuman(5, 15); } // 关注用户任务 else if (task.task_event_type == 'interactive.follow.user') { @@ -60,7 +68,7 @@ class SmzdmTaskBot extends SmzdmBot { notifyMsg += this.getTaskNotifyMessage(isSuccess, task); - await wait(5, 15); + await waitHuman(5, 15); } // 关注栏目任务 else if (task.task_event_type == 'interactive.follow.tag') { @@ -68,7 +76,7 @@ class SmzdmTaskBot extends SmzdmBot { notifyMsg += this.getTaskNotifyMessage(isSuccess, task); - await wait(5, 15); + await waitHuman(5, 15); } // 关注品牌 else if (task.task_event_type == 'interactive.follow.brand') { @@ -76,7 +84,7 @@ class SmzdmTaskBot extends SmzdmBot { notifyMsg += this.getTaskNotifyMessage(isSuccess, task); - await wait(5, 15); + await waitHuman(5, 15); } // 收藏任务 else if (task.task_event_type == 'interactive.favorite') { @@ -84,7 +92,7 @@ class SmzdmTaskBot extends SmzdmBot { notifyMsg += this.getTaskNotifyMessage(isSuccess, task); - await wait(5, 15); + await waitHuman(5, 15); } // 点赞任务 else if (task.task_event_type == 'interactive.rating') { @@ -92,21 +100,67 @@ class SmzdmTaskBot extends SmzdmBot { notifyMsg += this.getTaskNotifyMessage(isSuccess, task); - await wait(5, 15); + await waitHuman(5, 15); } // 评论任务 else if (task.task_event_type == 'interactive.comment') { - if (process.env.SMZDM_COMMENT && String(process.env.SMZDM_COMMENT).length > 10) { - const { isSuccess } = await this.doCommentTask(task); + const comment = this.getCommentContent(); + + if (comment) { + const { isSuccess } = await this.doCommentTask(task, comment); notifyMsg += this.getTaskNotifyMessage(isSuccess, task); - await wait(5, 15); + await waitHuman(5, 15); } else { this.$env.log('🟡请设置 SMZDM_COMMENT 环境变量后才能做评论任务!'); } } + // 站外浏览任务 + else if (task.task_event_type == 'interactive.view.zhanwai') { + const { isSuccess } = await this.doViewZhanwaiTask(task); + + notifyMsg += this.getTaskNotifyMessage(isSuccess, task); + + await waitHuman(5, 15); + } + // 打开第三方 App 任务 + else if (task.task_event_type == 'interactive.open_third_party_app') { + const { isSuccess } = await this.doOpenThirdPartyTask(task); + + notifyMsg += this.getTaskNotifyMessage(isSuccess, task); + + await waitHuman(5, 15); + } + // 开启推送任务 + else if (task.task_event_type == 'guide.open_app_push') { + const { isSuccess } = await this.doOpenAppPushTask(task); + + notifyMsg += this.getTaskNotifyMessage(isSuccess, task); + + await waitHuman(5, 15); + } + // 创建桌面签到快捷方式任务 + else if (task.task_event_type == 'guide.create_app_checkin_shortcut') { + const { isSuccess } = await this.doCreateCheckinShortcutTask(task); + + notifyMsg += this.getTaskNotifyMessage(isSuccess, task); + + await waitHuman(5, 15); + } + // 爆料任务,静默跳过 + else if (task.task_event_type == 'publish.baoliao_new') { + continue; + } + // 下载张大妈任务 + else if (task.task_event_type == 'interactive.download_zhangdama') { + this.$env.log(`跳过[${task.task_name}]任务:APP 只跳转下载页,没有完成接口`); + } + // 未知类型 + else { + this.$env.log(`跳过[${task.task_name}]任务:未知类型 ${task.task_event_type}`); + } } } @@ -117,8 +171,23 @@ class SmzdmTaskBot extends SmzdmBot { return `${isSuccess ? '🟢' : '❌'}完成[${task.task_name}]任务${isSuccess ? '成功' : '失败!请查看日志'}\n`; } + // APP 在列表场景下用 article_hash_id 作为文章标识 + getArticleId(article) { + return article.article_hash_id || article.article_id; + } + + // 随机取一条评论文案,多条时用换行或 | 分隔 + getCommentContent() { + const words = String(process.env.SMZDM_COMMENT || '') + .split(/[\n|]/) + .map(word => word.trim()) + .filter(word => word.length > 10); + + return words.length > 0 ? this.getOneByRandom(words) : ''; + } + // 执行评论任务 - async doCommentTask(task) { + async doCommentTask(task, content) { this.$env.log(`开始任务: ${task.task_name}`); const articles = await this.getArticleList(20); @@ -132,12 +201,15 @@ class SmzdmTaskBot extends SmzdmBot { // 随机选一篇文章来评论 const article = articles[Math.floor(Math.random() * articles.length)]; + // 先打开文章 + await this.getArticleDetail(article.article_id); + await wait(3, 10); const {isSuccess, data } = await this.submitComment({ articleId: article.article_id, channelId: article.article_channel_id, - content: process.env.SMZDM_COMMENT + content }); if (!isSuccess) { @@ -160,7 +232,7 @@ class SmzdmTaskBot extends SmzdmBot { } this.$env.log('领取奖励'); - await wait(5, 15); + await waitHuman(5, 15); return await this.receiveReward(task.task_id); } @@ -219,12 +291,14 @@ class SmzdmTaskBot extends SmzdmBot { await wait(3, 10); + const articleId = this.getArticleId(article); + if (article.article_price) { // 点值 await this.rating({ method: 'worth_cancel', type: 3, - id: article.article_id, + id: articleId, channelId: article.article_channel_id }); @@ -233,7 +307,7 @@ class SmzdmTaskBot extends SmzdmBot { await this.rating({ method: 'worth_create', type: 1, - id: article.article_id, + id: articleId, channelId: article.article_channel_id }); @@ -242,7 +316,7 @@ class SmzdmTaskBot extends SmzdmBot { await this.rating({ method: 'worth_cancel', type: 3, - id: article.article_id, + id: articleId, channelId: article.article_channel_id }); } @@ -250,7 +324,7 @@ class SmzdmTaskBot extends SmzdmBot { // 点赞 await this.rating({ method: 'like_cancel', - id: article.article_id, + id: articleId, channelId: article.article_channel_id }); @@ -258,7 +332,7 @@ class SmzdmTaskBot extends SmzdmBot { await this.rating({ method: 'like_create', - id: article.article_id, + id: articleId, channelId: article.article_channel_id }); @@ -266,7 +340,7 @@ class SmzdmTaskBot extends SmzdmBot { await this.rating({ method: 'like_cancel', - id: article.article_id, + id: articleId, channelId: article.article_channel_id }); @@ -274,7 +348,7 @@ class SmzdmTaskBot extends SmzdmBot { await this.rating({ method: 'like_create', - id: article.article_id, + id: articleId, channelId: article.article_channel_id }); @@ -282,13 +356,13 @@ class SmzdmTaskBot extends SmzdmBot { await this.rating({ method: 'like_cancel', - id: article.article_id, + id: articleId, channelId: article.article_channel_id }); } this.$env.log('领取奖励'); - await wait(5, 15); + await waitHuman(5, 15); return await this.receiveReward(task.task_id); } @@ -312,12 +386,12 @@ class SmzdmTaskBot extends SmzdmBot { const article = this.getOneByRandom(articles); - articleId = article.article_id; + articleId = this.getArticleId(article); channelId = article.article_channel_id; } else if (task.task_redirect_url.link_type === 'tag') { - // 从 Tag 获取文章 - const articles = await this.getArticleListFromTag(task.task_redirect_url.link_val, task.task_redirect_url.link_title, 20); + // 从标签/话题获取文章 + const articles = await this.getArticleListFromTag(task.task_redirect_url.link_val, 20); if (articles.length < 1) { return { @@ -327,7 +401,7 @@ class SmzdmTaskBot extends SmzdmBot { const article = this.getOneByRandom(articles); - articleId = article.article_id; + articleId = this.getArticleId(article); channelId = article.article_channel_id; } else if (task.task_redirect_url.link_val == '0' || !task.task_redirect_url.link_val) { @@ -342,7 +416,7 @@ class SmzdmTaskBot extends SmzdmBot { const article = this.getOneByRandom(articles); - articleId = article.article_id; + articleId = this.getArticleId(article); channelId = article.article_channel_id; } else { @@ -362,6 +436,7 @@ class SmzdmTaskBot extends SmzdmBot { await wait(3, 10); + // 先取消 await this.favorite({ method: 'destroy', id: articleId, @@ -385,7 +460,7 @@ class SmzdmTaskBot extends SmzdmBot { }); this.$env.log('领取奖励'); - await wait(5, 15); + await waitHuman(5, 15); return await this.receiveReward(task.task_id); } @@ -410,6 +485,7 @@ class SmzdmTaskBot extends SmzdmBot { await this.follow({ method: 'destroy', type: 'user', + keywordId: user.keyword_id, keyword: user.keyword }); @@ -419,6 +495,7 @@ class SmzdmTaskBot extends SmzdmBot { await this.follow({ method: 'create', type: 'user', + keywordId: user.keyword_id, keyword: user.keyword }); @@ -428,6 +505,7 @@ class SmzdmTaskBot extends SmzdmBot { await this.follow({ method: 'destroy', type: 'user', + keywordId: user.keyword_id, keyword: user.keyword }); } @@ -436,7 +514,7 @@ class SmzdmTaskBot extends SmzdmBot { } this.$env.log('领取奖励'); - await wait(5, 15); + await waitHuman(5, 15); return await this.receiveReward(task.task_id); } @@ -456,7 +534,7 @@ class SmzdmTaskBot extends SmzdmBot { }; } - lanmuId = tag.lanmu_id; + lanmuId = tag.keyword_id; await wait(3, 10); } @@ -467,7 +545,7 @@ class SmzdmTaskBot extends SmzdmBot { // 获取栏目信息 const tagDetail = await this.getTagDetail(lanmuId); - if (!tagDetail.lanmu_id) { + if (!tagDetail.keyword_id) { this.$env.log('获取栏目信息失败!'); return { @@ -480,8 +558,8 @@ class SmzdmTaskBot extends SmzdmBot { await this.follow({ method: 'destroy', type: 'tag', - keywordId: tagDetail.lanmu_id, - keyword: tagDetail.lanmu_info.lanmu_name + keywordId: tagDetail.keyword_id, + keyword: tagDetail.keyword }); await wait(3, 10); @@ -489,8 +567,8 @@ class SmzdmTaskBot extends SmzdmBot { await this.follow({ method: 'create', type: 'tag', - keywordId: tagDetail.lanmu_id, - keyword: tagDetail.lanmu_info.lanmu_name + keywordId: tagDetail.keyword_id, + keyword: tagDetail.keyword }); await wait(3, 10); @@ -498,12 +576,12 @@ class SmzdmTaskBot extends SmzdmBot { await this.follow({ method: 'destroy', type: 'tag', - keywordId: tagDetail.lanmu_id, - keyword: tagDetail.lanmu_info.lanmu_name + keywordId: tagDetail.keyword_id, + keyword: tagDetail.keyword }); this.$env.log('领取奖励'); - await wait(5, 15); + await waitHuman(5, 15); return await this.receiveReward(task.task_id); } @@ -546,7 +624,7 @@ class SmzdmTaskBot extends SmzdmBot { }); this.$env.log('领取奖励'); - await wait(5, 15); + await waitHuman(5, 15); return await this.receiveReward(task.task_id); } @@ -578,7 +656,7 @@ class SmzdmTaskBot extends SmzdmBot { } } - await wait(5, 15); + await waitHuman(5, 15); const result = await this.joinCrowd(data); @@ -589,7 +667,7 @@ class SmzdmTaskBot extends SmzdmBot { } this.$env.log('领取奖励'); - await wait(5, 15); + await waitHuman(5, 15); return await this.receiveReward(task.task_id); } @@ -633,7 +711,7 @@ class SmzdmTaskBot extends SmzdmBot { await this.shareDailyReward(article.article_channel_id); await this.shareCallback(article.article_id, article.article_channel_id); - await wait(5, 15); + await waitHuman(5, 15); } this.$env.log('领取奖励'); @@ -649,7 +727,18 @@ class SmzdmTaskBot extends SmzdmBot { let articles = []; let isRead = true; - if (task.article_id == '0') { + // APP 按任务给的浏览时长决定行为,为 0 时不打开文章页 + const seconds = parseInt(task.view_seconds) || 0; + const readSeconds = seconds > 0 ? (parseInt(task.task_redirect_url.browse_task_second) || seconds) : 0; + + if (seconds === 0 && task.article_id != '0') { + isRead = false; + articles = [{ + article_id: task.article_id, + article_channel_id: task.channel_id + }]; + } + else if (task.article_id == '0') { isRead = true; articles = await this.getArticleList(task.task_even_num - task.task_finished_num); @@ -681,8 +770,15 @@ class SmzdmTaskBot extends SmzdmBot { } } - this.$env.log('模拟阅读文章'); - await wait(20, 50); + if (readSeconds > 0) { + const dwell = readSeconds + Math.floor(Math.random() * 10); + + this.$env.log(`模拟阅读文章 ${dwell} 秒`); + await wait(dwell, dwell + 5); + } + else { + await wait(3, 10); + } const { isSuccess, response } = await requestApi('https://user-api.smzdm.com/task/event_view_article_sync', { method: 'post', @@ -701,7 +797,7 @@ class SmzdmTaskBot extends SmzdmBot { this.$env.log(`完成阅读失败!${response}`); } - await wait(5, 15); + await waitHuman(5, 15); } this.$env.log('领取奖励'); @@ -710,6 +806,58 @@ class SmzdmTaskBot extends SmzdmBot { return await this.receiveReward(task.task_id); } + // 上报任务完成事件 + async reportTaskEvent(url, task) { + const { isSuccess, response } = await requestApi(url, { + method: 'post', + headers: this.getHeaders(), + data: { + task_id: task.task_id + } + }); + + if (isSuccess) { + this.$env.log('任务事件上报成功。'); + } + else { + this.$env.log(`任务事件上报失败!${response}`); + } + + await waitHuman(5, 15); + + this.$env.log('领取奖励'); + + return await this.receiveReward(task.task_id); + } + + // 执行站外浏览任务 + async doViewZhanwaiTask(task) { + this.$env.log(`开始任务: ${task.task_name}`); + + return await this.reportTaskEvent('https://user-api.smzdm.com/task/event_view_zhanwai', task); + } + + // 执行打开第三方 App 任务 + async doOpenThirdPartyTask(task) { + this.$env.log(`开始任务: ${task.task_name}`); + + return await this.reportTaskEvent('https://user-api.smzdm.com/task/event_view_third_party_app', task); + } + + // 执行开启推送任务 + async doOpenAppPushTask(task) { + this.$env.log(`开始任务: ${task.task_name}`); + + return await this.reportTaskEvent('https://user-api.smzdm.com/task/open_app_push', task); + } + + // 执行创建桌面签到快捷方式任务 + async doCreateCheckinShortcutTask(task) { + this.$env.log(`开始任务: ${task.task_name}`); + + return await this.reportTaskEvent('https://user-api.smzdm.com/task/create_app_checkin_shortcut', task); + } + // 关注/取关 async follow({keywordId, keyword, type, method}) { let touchstone = ''; @@ -775,9 +923,9 @@ class SmzdmTaskBot extends SmzdmBot { headers: this.getHeaders(), data: { nav_id: 0, - page: 1, type: 'user', - time_code: '' + time_code: '', + limit: 20 } }); @@ -1008,17 +1156,13 @@ class SmzdmTaskBot extends SmzdmBot { // 获取文章列表 async getArticleList(num = 1) { - const { isSuccess, data, response } = await requestApi('https://article-api.smzdm.com/ranking_list/articles', { + const { isSuccess, data, response } = await requestApi('https://article-api.smzdm.com/ranking_list/article_and_biji', { headers: this.getHeaders(), data: { + tab: '2', + order: '1', offset: 0, - channel_id: 76, - tab: 2, - order: 0, - limit: 20, - exclude_article_ids: '', - stream: 'a', - ab_code: 'b' + exclude_article_ids: '' } }); @@ -1050,12 +1194,13 @@ class SmzdmTaskBot extends SmzdmBot { // 获取栏目信息 async getTagDetail(id) { - const { isSuccess, data, response } = await requestApi('https://common-api.smzdm.com/lanmu/config_data', { + const { isSuccess, data, response } = await requestApi('https://app-api.smzdm.com/common/config_data', { headers: this.getHeaders(), data: { - middle_page: '', - tab_selects: '', - redirect_params: id + id, + type: 'lanmu', + calendar_time: '', + token: this.token } }); @@ -1063,7 +1208,7 @@ class SmzdmTaskBot extends SmzdmBot { return data.data; } else { - this.$env.log(`获取栏目信息失败!${response}`); + this.$env.log(`获取栏目信息失败: ${response}`); return {}; } @@ -1071,13 +1216,11 @@ class SmzdmTaskBot extends SmzdmBot { // 获取栏目列表 async getTagByRandom() { - const { isSuccess, data, response } = await requestApi('https://dingyue-api.smzdm.com/tuijian/search_result', { + const { isSuccess, data, response } = await requestApi('https://dingyue-api.smzdm.com/dy/user/dingyue/tuijian_search', { + method: 'post', headers: this.getHeaders(), data: { - time_code: '', - nav_id: '', - type: 'tag', - limit: 20 + type: 'tag' } }); @@ -1097,12 +1240,10 @@ class SmzdmTaskBot extends SmzdmBot { headers: this.getHeaders(), data: { comment_flow: '', - hashcode: '', lastest_update_time: '', uhome: 0, imgmode: 0, - article_channel_id: 0, - h5hash: '' + article_channel_id: 0 } }); @@ -1139,24 +1280,30 @@ class SmzdmTaskBot extends SmzdmBot { // 收藏 async favorite({id, channelId, method}) { + const data = { + touchstone_event: this.getTouchstoneEvent({ + event_value: { + aid: id, + cid: channelId, + is_detail: true + }, + sourceMode: '我的_我的任务页', + sourcePage: `Android/长图文/P/${id}/`, + upperLevel_url: '个人中心/赚奖励/' + }), + token: this.token, + id, + channel_id: channelId + }; + + if (method === 'create') { + data.sub_channel_id = '2'; + } + const { isSuccess, response } = await requestApi(`https://user-api.smzdm.com/favorites/${method}`, { method: 'post', headers: this.getHeaders(), - data: { - touchstone_event: this.getTouchstoneEvent({ - event_value: { - aid: id, - cid: channelId, - is_detail: true - }, - sourceMode: '我的_我的任务页', - sourcePage: `Android/长图文/P/${id}/`, - upperLevel_url: '个人中心/赚奖励/' - }), - token: this.token, - id, - channel_id: channelId - } + data }); if (isSuccess) { @@ -1236,36 +1383,29 @@ class SmzdmTaskBot extends SmzdmBot { if (isSuccess) { return data.data; } - else { - this.$env.log(`获取品牌信息失败!${response}`); - return {}; - } + this.$env.log(`获取品牌信息失败!${response}`); + + return {}; } // 根据栏目信息获取文章列表 async getArticleListFromLanmu(id, num = 1) { const lanmuDetail = await this.getTagDetail(id); - if (!lanmuDetail.lanmu_id) { + if (!lanmuDetail.keyword_id) { return []; } - const { isSuccess, data, response } = await requestApi('https://common-api.smzdm.com/lanmu/list_data', { + const tabParams = lanmuDetail.tab && lanmuDetail.tab[0] ? lanmuDetail.tab[0].params : ''; + + const { isSuccess, data, response } = await requestApi('https://app-api.smzdm.com/common/list_data', { headers: this.getHeaders(), data: { - price_lt: '', - order: '', - category_ids: '', - price_gt: '', - referer_article: '', - tag_params: '', - mall_ids: '', - time_sort: '', + params: lanmuDetail.params, + tab_params: tabParams, page: 1, - params: id, - limit: 20, - tab_params: lanmuDetail.tab[0].params + limit: 20 } }); @@ -1279,6 +1419,29 @@ class SmzdmTaskBot extends SmzdmBot { } } + // 根据标签/话题获取文章列表 + async getArticleListFromTag(id, num = 1) { + const { isSuccess, data, response } = await requestApi('https://article-api.smzdm.com/shaiwu/shaiwu_juhe_new', { + headers: this.getHeaders(), + data: { + page: 1, + size: 20, + tag_id: id, + tab_id: '0', + middle_page: '' + } + }); + + if (isSuccess) { + return data.data.rows.slice(0, num); + } + else { + this.$env.log(`获取文章列表失败: ${response}`); + + return []; + } + } + // 点赞 async rating({id, channelId, method, type}) { const { isSuccess, response } = await requestApi(`https://user-api.smzdm.com/rating/${method}`, { @@ -1332,7 +1495,6 @@ class SmzdmTaskBot extends SmzdmBot { upperLevel_url: '好物社区/首页/全部/', sourceRoot: '社区' }), - is_like: 3, reply_from: 3, smiles: 0, atta: 0, @@ -1381,58 +1543,6 @@ class SmzdmTaskBot extends SmzdmBot { }; } - // 获取 Dingyue 状态 - async getDingyueStatus(name) { - const { isSuccess, data, response } = await requestApi('https://dingyue-api.smzdm.com/dingyue/follow_status', { - method: 'post', - headers: this.getHeaders(), - data: { - rules: JSON.stringify([{ - type: 'tag', - keyword: name - }]) - } - }); - - if (isSuccess) { - return data; - } - else { - this.$env.log(`获取订阅状态失败: ${response}`); - return {}; - } - } - - // 根据 Tag ID 获取文章列表 - async getArticleListFromTag(id, name, num = 1) { - const status = this.getDingyueStatus(name); - - const { isSuccess, data, response } = await requestApi('https://tag-api.smzdm.com/theme/detail_feed', { - headers: this.getHeaders(), - data: { - article_source: 1, - past_num: 0, - feed_sort: 2, - smzdm_id: status.smzdm_id, - tag_id: id, - name, - time_sort: 0, - page: 1, - article_tab: 0, - limit: 20 - } - }); - - if (isSuccess) { - // 取前 num 个做任务 - return data.data.rows.slice(0, num); - } - else { - this.$env.log(`获取文章列表失败: ${response}`); - return []; - } - } - // 通过 url 获取文章 channel_id async getArticleChannelIdForTesting(url) { const { isSuccess, response } = await requestApi(url, { diff --git a/smzdm_checkin.js b/smzdm_checkin.js index f3eca0f..8cff56e 100644 --- a/smzdm_checkin.js +++ b/smzdm_checkin.js @@ -6,7 +6,16 @@ cron: 10 8 * * * */ const Env = require('./env'); -const { SmzdmBot, requestApi, removeTags, getEnvCookies, wait } = require('./bot'); +const { + SmzdmBot, + requestApi, + removeTags, + getEnvCookies, + wait, + waitHuman, + CREATE_CENTER_VIEW, + IS_INSTALL_ZHANGDAMA +} = require('./bot'); const notify = require('./sendNotify'); const CryptoJS = require('crypto-js'); @@ -49,7 +58,7 @@ class SmzdmCheckinBot extends SmzdmBot { 🏅碎银: ${data.data.pre_re_silver} 🏅补签卡: ${data.data.cards}`; - await wait(3, 10); + await waitHuman(3, 10); const vip = await this.getVipInfo(); @@ -85,22 +94,24 @@ class SmzdmCheckinBot extends SmzdmBot { }); if (isSuccess) { - const msg1 = `${data.data.normal_reward.reward_add.title}: ${data.data.normal_reward.reward_add.content}`; + const normal = data.data.normal_reward; - let msg2 = ''; + // reward_add 为空说明今天没有奖励增加(已领过或没有),退回展示签到卡片文案 + const msg1 = normal.reward_add.title + ? `${normal.reward_add.title}: ${normal.reward_add.content}` + : removeTags(normal.gift.content); - if (data.data.normal_reward.gift.title) { - msg2 = `${data.data.normal_reward.gift.title}: ${data.data.normal_reward.gift.content_str}`; - } - else { - msg2 = `${data.data.normal_reward.gift.sub_content}`; - } + const msg2 = normal.gift.title + ? `${normal.gift.title}: ${normal.gift.content_str}` + : normal.gift.sub_content; - $.log(`${msg1}\n${msg2}\n`); + const msg = [msg1, msg2].filter(text => text).join('\n'); + + $.log(`${msg}\n`); return { isSuccess, - msg: `${msg1}\n${msg2}\n\n` + msg: `${msg}\n\n` }; } else { @@ -129,7 +140,7 @@ class SmzdmCheckinBot extends SmzdmBot { }; } - await wait(5, 10); + await waitHuman(5, 10); const { isSuccess, data, response } = await requestApi('https://user-api.smzdm.com/checkin/extra_reward', { method: 'post', @@ -159,13 +170,17 @@ class SmzdmCheckinBot extends SmzdmBot { async isContinueCheckin() { const { isSuccess, data, response } = await requestApi('https://user-api.smzdm.com/checkin/show_view_v2', { method: 'post', - headers: this.getHeaders() + headers: this.getHeaders(), + data: { + is_install_zhangdama: IS_INSTALL_ZHANGDAMA + } }); if (isSuccess) { - const result = data.data.rows.find(item => item.cell_type == '18001'); + // 连续签到的卡片,直接按有没有 checkin_continue 来找 + const result = data.data.rows.find(item => item.cell_data && item.cell_data.checkin_continue); - return result.cell_data.checkin_continue.continue_checkin_reward_show; + return !!(result && result.cell_data.checkin_continue.continue_checkin_reward_show); } else { $.log(`查询是否有额外奖励失败!${response}`); @@ -179,7 +194,8 @@ class SmzdmCheckinBot extends SmzdmBot { method: 'post', headers: this.getHeaders(), data: { - token: this.token + token: this.token, + create_center_view: CREATE_CENTER_VIEW } }); diff --git a/smzdm_lottery.js b/smzdm_lottery.js index f1c7bf8..cef1a16 100644 --- a/smzdm_lottery.js +++ b/smzdm_lottery.js @@ -6,7 +6,14 @@ cron: 20 8 * * * */ const Env = require('./env'); -const { SmzdmBot, requestApi, parseJSON, getEnvCookies, wait } = require('./bot'); +const { + SmzdmBot, + requestApi, + parseJSON, + getEnvCookies, + wait, + waitHuman +} = require('./bot'); const notify = require('./sendNotify'); // ------------------------------------ @@ -34,7 +41,7 @@ class SmzdmLotteryBot extends SmzdmBot { const vipId1 = await this.getActivityIdFromVip('https://m.smzdm.com/topic/bwrzf5/516lft'); if (vipId1) { - await wait(3, 10); + await waitHuman(3, 10); notifyMsg += `转盘抽奖ID: ${vipId1}\n`; notifyMsg += await this.draw(vipId1); @@ -42,13 +49,13 @@ class SmzdmLotteryBot extends SmzdmBot { } $.log(); - await wait(5, 15); + await waitHuman(5, 15); $.log(); const vipId2 = await this.getActivityIdFromVip('https://m.smzdm.com/topic/zhyzhuanpan/cjzp/'); if (vipId2) { - await wait(3, 10); + await waitHuman(3, 10); notifyMsg += `转盘抽奖ID: ${vipId2}\n`; notifyMsg += await this.draw(vipId2); diff --git a/smzdm_task.js b/smzdm_task.js index 3056958..d809cb7 100644 --- a/smzdm_task.js +++ b/smzdm_task.js @@ -6,7 +6,14 @@ cron: 20 14 * * * */ const Env = require('./env'); -const { requestApi, removeTags, getEnvCookies, wait } = require('./bot'); +const { + requestApi, + removeTags, + getEnvCookies, + wait, + waitHuman, + IS_INSTALL_ZHANGDAMA +} = require('./bot'); const notify = require('./sendNotify'); const { SmzdmTaskBot } = require('./library_task'); @@ -25,21 +32,21 @@ class SmzdmNormalTaskBot extends SmzdmTaskBot { const { tasks } = await this.getTaskList(); - await wait(5, 10); + await waitHuman(5, 10); let notifyMsg = ''; notifyMsg = await this.doTasks(tasks); $.log('查询是否有限时累计活动阶段奖励'); - await wait(5, 15); + await waitHuman(5, 15); // 领取活动奖励 const { detail } = await this.getTaskList(); if (detail.cell_data && detail.cell_data.activity_reward_status == '1') { $.log('有奖励,领取奖励'); - await wait(5, 15); + await waitHuman(5, 15); const { isSuccess } = await this.receiveActivity(detail.cell_data); @@ -56,7 +63,10 @@ class SmzdmNormalTaskBot extends SmzdmTaskBot { async getTaskList() { const { isSuccess, data, response } = await requestApi('https://user-api.smzdm.com/task/list_v2', { method: 'post', - headers: this.getHeaders() + headers: this.getHeaders(), + data: { + is_install_zhangdama: IS_INSTALL_ZHANGDAMA + } }); if (isSuccess) { diff --git a/smzdm_testing.js b/smzdm_testing.js index 7deb6f1..29bd32d 100644 --- a/smzdm_testing.js +++ b/smzdm_testing.js @@ -6,7 +6,7 @@ cron: 20 15 * * * */ const Env = require('./env'); -const { requestApi, getEnvCookies, wait } = require('./bot'); +const { requestApi, getEnvCookies, wait, waitHuman } = require('./bot'); const notify = require('./sendNotify'); const { SmzdmTaskBot } = require('./library_task'); @@ -21,13 +21,28 @@ class SmzdmTestingTaskBot extends SmzdmTaskBot { // 主函数 async run() { + let notifyMsg = await this.doTestingTasks(); + + await waitHuman(5, 10); + + notifyMsg += await this.getEnergyList(); + + await waitHuman(5, 10); + + notifyMsg += await this.getWinCouponList(); + + return notifyMsg; + } + + // 执行众测能量值任务 + async doTestingTasks() { const activityId = await this.getTestingActivityId(); if (!activityId) { - return ''; + return this.wafBlocked ? '🟡众测任务未执行:接口被 WAF/风控拦截\n' : ''; } - await wait(5, 10); + await waitHuman(5, 10); const activityInfo = await this.getTestingActivityInfo(activityId); @@ -35,87 +50,71 @@ class SmzdmTestingTaskBot extends SmzdmTaskBot { return ''; } - await wait(5, 10); + await waitHuman(5, 10); - let notifyMsg = await this.doTasks(activityInfo.activity_task.default_list); - - await wait(3, 5); - - // 查询当前拥有的能量数量, 以及过期时间 - notifyMsg += await this.getMyTestingInfo(); - - return notifyMsg || '无可执行任务'; + return await this.doTasks(activityInfo.activity_task.default_list); } - // 获取当前能量值任务的活动 ID - async getTestingActivityId() { - $.log('获取活动 ID'); + // 众测接口被 WAF 拦时返回 403、429 或 HTML 验证码页,不是 JSON + isWafBlocked(response) { + return /WAF|验证码|| 0) { + msg += `🏅能量值明细:\n${list.map(item => ` ${item.article_title}`).join('\n')}\n`; + } + + $.log(msg); + + return `${msg}\n`; + } + + // 必中券列表 + async getWinCouponList() { + $.log('查询必中券列表'); + + const { isSuccess, data, response } = await requestApi('https://test-api.smzdm.com/win_coupon/win_coupon_list', { + method: 'get', + headers: this.getHeaders(), + data: { + 'page': 1, + 'type': '0' + } + }); + + if (!isSuccess) { + $.log(`查询必中券列表失败!${response}`); + + return ''; + } + + const list = data.data.list || []; + + if (list.length < 1) { + return '🏅暂无必中券\n'; + } + + const msg = `🏅必中券 ${list.length} 项:\n${list.map(item => ` ${item.article_title}`).join('\n')}\n`; + + $.log(msg); + + return msg; } }