commit 02b36bb97369180a9789caaa8df8d1e45e530ed0 Author: 裴浩宇 Date: Tue Jan 30 21:51:34 2024 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..05415c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,173 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,node +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# Support for Project snippet scope +.vscode/*.code-snippets + +# Ignore code-workspaces +*.code-workspace + +# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,node +n +.vscode +sendNotify.js \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..61f2b0d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "singleQuote": true, + "semi": false, + "endOfLine": "lf", + "tabWidth": 2, + "trailingComma": "none", + "bracketSpacing": true, + "arrowParens": "avoid" +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e9f6429 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 mrabit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0e2add --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +## 阿里云盘每日签到 + +> 基于 Node.js 实现的阿里云盘每日签到 + +### TODO + +- [x] 阿里云盘签到 +- [x] 青龙面板支持 +- [ ] 本地运行 +- [ ] ~~github action 支持~~ + +### Use 使用 + +#### 第一步:获取 refresh_token 并复制 + +- 自动获取: 登录[阿里云盘](https://www.aliyundrive.com/drive/)后,控制台粘贴 +```javascript +copy(JSON.parse(localStorage.token).refresh_token); console.log(JSON.parse(localStorage.token).refresh_token); +``` +` + ![](./assets/refresh_token_1.png) + +- 手动获取: 登录[阿里云盘](https://www.aliyundrive.com/drive/)后,可以在开发者工具 -> + Application -> Local Storage 中的 `token` 字段中找到。 + 注意:不是复制整段 JSON 值,而是 JSON 里 `refresh_token` 字段的值,如下图所示红色部分: + ![refresh token](./assets/refresh_token_2.png) + +#### 第二步:青龙面板添加依赖项 + +- axios + +#### 第三步:添加环境变量 + +> `CLIENT_ID` 需添加 `环境变量` 权限 + +| 参数 | 说明 | +| ------------- | ------------------------------------------------ | +| refreshToken | 阿里云盘 refresh_token, 添加多个可支持多账户签到 | +| CLIENT_ID | 可选项, 用于青龙面板 API 更新 refreshToken 字段 | +| CLIENT_SECRET | 可选项, 用于青龙面板 API 更新 refreshToken 字段 | +| QL_PATH | 可选项, 青龙面板path | + +`CLIENT_ID` 和 `CLIENT_SECRET` 可在 `青龙面板 -> 系统设置 -> 应用设置 -> 新建应用` 新增, 用于自动更新环境变量内 `refreshToken` 配置 + +#### 第四步:添加订阅 + +> 添加订阅后可在定时任务列表发现新增任务, 可自行调整任务执行时间 +```shell +ql repo https://github.com/mrabit/aliyundriveDailyCheck.git "autoSignin" "" "qlApi" +``` + +##### 新版本: + +`青龙面板 -> 订阅管理 -> 新建订阅`, 在名称输入框粘贴命令并执行 + +##### 旧版本: + +`青龙面板 -> 定时任务 -> 新建任务` 添加命令并执行 + +![aliyundriveDailyCheck.png](./assets/aliyundriveDailyCheck.png) + +### 申明 + +- 本项目仅做学习交流, 禁止用于各种非法途径 +- 项目中的所有内容均源于互联网, 仅限于小范围内学习参考, 如有侵权请第一时间联系 [本项目作者](https://github.com/mrabit) 进行删除 + +### 鸣谢 + +#### 特别感谢以下作者及所开发的程序,本项目参考过以下几位开发者代码及思想。 + +- @Anonym-w: [Anonym-w/autoSigninAliyun](https://github.com/Anonym-w/autoSigninAliyun) +- @ImYrS: [ImYrS/aliyun-auto-signin](https://github.com/ImYrS/aliyun-auto-signin) diff --git a/assets/aliyundriveDailyCheck.png b/assets/aliyundriveDailyCheck.png new file mode 100644 index 0000000..a48cf4e Binary files /dev/null and b/assets/aliyundriveDailyCheck.png differ diff --git a/assets/img.png b/assets/img.png new file mode 100644 index 0000000..9d5f82a Binary files /dev/null and b/assets/img.png differ diff --git a/assets/refresh_token_1.png b/assets/refresh_token_1.png new file mode 100644 index 0000000..3d36a82 Binary files /dev/null and b/assets/refresh_token_1.png differ diff --git a/assets/refresh_token_2.png b/assets/refresh_token_2.png new file mode 100644 index 0000000..6f70bc7 Binary files /dev/null and b/assets/refresh_token_2.png differ diff --git a/autoSignin.js b/autoSignin.js new file mode 100644 index 0000000..4cc679f --- /dev/null +++ b/autoSignin.js @@ -0,0 +1,207 @@ +/* +cron "0 9 * * *" autoSignin.js, tag=阿里云盘签到 +*/ + +const axios = require('axios') +const { initInstance, getEnv, updateCkEnv } = require('./qlApi.js') +const notify = require('./sendNotify') + +const updateAccesssTokenURL = 'https://auth.aliyundrive.com/v2/account/token' +const signinURL = + 'https://member.aliyundrive.com/v1/activity/sign_in_list?_rx-s=mobile' +const rewardURL = + 'https://member.aliyundrive.com/v1/activity/sign_in_reward?_rx-s=mobile' + +// 使用 refresh_token 更新 access_token +function updateAccesssToken(queryBody, remarks) { + const errorMessage = [remarks, '更新 access_token 失败'] + return axios(updateAccesssTokenURL, { + method: 'POST', + data: queryBody, + headers: { 'Content-Type': 'application/json' } + }) + .then(d => d.data) + .then(d => { + const { code, message, nick_name, refresh_token, access_token } = d + if (code) { + if ( + code === 'RefreshTokenExpired' || + code === 'InvalidParameter.RefreshToken' + ) + errorMessage.push('refresh_token 已过期或无效') + else errorMessage.push(message) + return Promise.reject(errorMessage.join(', ')) + } + return { nick_name, refresh_token, access_token } + }) + .catch(e => { + errorMessage.push(e.message) + return Promise.reject(errorMessage.join(', ')) + }) +} + +//签到列表 +function sign_in(access_token, remarks) { + const sendMessage = [remarks] + return axios(signinURL, { + method: 'POST', + data: { + isReward: false + }, + headers: { + Authorization: access_token, + 'Content-Type': 'application/json' + } + }) + .then(d => d.data) + .then(async json => { + if (!json.success) { + sendMessage.push('签到失败', json.message) + return Promise.reject(sendMessage.join(', ')) + } + + sendMessage.push('签到成功') + + const { signInLogs, signInCount } = json.result + const currentSignInfo = signInLogs[signInCount - 1] // 当天签到信息 + + sendMessage.push(`本月累计签到 ${signInCount} 天`) + + // 未领取奖励列表 + const rewards = signInLogs.filter( + v => v.status === 'normal' && !v.isReward + ) + + if (rewards.length) { + for await (reward of rewards) { + const signInDay = reward.day + try { + const rewardInfo = await getReward(access_token, signInDay) + sendMessage.push( + `第${signInDay}天奖励领取成功: 获得${rewardInfo.name || ''}${ + rewardInfo.description || '' + }` + ) + } catch (e) { + sendMessage.push(`第${signInDay}天奖励领取失败:`, e) + } + } + } else if (currentSignInfo.isReward) { + sendMessage.push( + `今日签到获得${currentSignInfo.reward.name || ''}${ + currentSignInfo.reward.description || '' + }` + ) + } + + return sendMessage.join(', ') + }) + .catch(e => { + sendMessage.push('签到失败') + sendMessage.push(e.message) + return Promise.reject(sendMessage.join(', ')) + }) +} + +// 领取奖励 +function getReward(access_token, signInDay) { + return axios(rewardURL, { + method: 'POST', + data: { signInDay }, + headers: { + authorization: access_token, + 'Content-Type': 'application/json' + } + }) + .then(d => d.data) + .then(json => { + if (!json.success) { + return Promise.reject(json.message) + } + + return json.result + }) +} + +// 获取环境变量 +async function getRefreshToken() { + let instance = null + try { + instance = await initInstance() + } catch (e) {} + + let refreshToken = process.env.refreshToken || [] + try { + if (instance) refreshToken = await getEnv(instance, 'refreshToken') + } catch (e) {} + + let refreshTokenArray = [] + + if (Array.isArray(refreshToken)) refreshTokenArray = refreshToken + else if (refreshToken.indexOf('&') > -1) + refreshTokenArray = refreshToken.split('&') + else if (refreshToken.indexOf('\n') > -1) + refreshTokenArray = refreshToken.split('\n') + else refreshTokenArray = [refreshToken] + + if (!refreshTokenArray.length) { + console.log('未获取到refreshToken, 程序终止') + process.exit(1) + } + + return { + instance, + refreshTokenArray + } +} + +!(async () => { + const { instance, refreshTokenArray } = await getRefreshToken() + + const message = [] + let index = 1 + for await (refreshToken of refreshTokenArray) { + let remarks = refreshToken.remarks || `账号${index}` + const queryBody = { + grant_type: 'refresh_token', + refresh_token: refreshToken.value || refreshToken + } + + try { + const { nick_name, refresh_token, access_token } = + await updateAccesssToken(queryBody, remarks) + + if (nick_name && nick_name !== remarks) + remarks = `${nick_name}(${remarks})` + + // 更新环境变量 + if (instance) { + let params = { + name: refreshToken.name, + value: refresh_token, + remarks: refreshToken.remarks || nick_name // 优先存储原有备注信息 + } + // 新版青龙api + if (refreshToken.id) { + params.id = refreshToken.id + } + // 旧版青龙api + if (refreshToken._id) { + params._id = refreshToken._id + } + await updateCkEnv(instance, params) + } + + const sendMessage = await sign_in(access_token, remarks) + console.log(sendMessage) + console.log('\n') + message.push(sendMessage) + } catch (e) { + console.log(e) + console.log('\n') + message.push(e) + } + index++ + } + await notify.sendNotify(`阿里云盘签到`, message.join('\n')) +})() diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..701cca7 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,69 @@ +{ + "name": "aliyundrive_daily_check", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "axios": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.3.2.tgz", + "integrity": "sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..38d5f9b --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "aliyundrive_daily_check", + "version": "1.0.0", + "description": "阿里云盘每日签到", + "main": "autoSignin.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/mrabit/aliyundriveDailyCheck.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/mrabit/aliyundriveDailyCheck/issues" + }, + "homepage": "https://github.com/mrabit/aliyundriveDailyCheck#readme", + "dependencies": { + "axios": "^1.3.2" + } +} diff --git a/qlApi.js b/qlApi.js new file mode 100644 index 0000000..8c148e8 --- /dev/null +++ b/qlApi.js @@ -0,0 +1,167 @@ +/* + * @Author: chenghao + * @Date: 2022-02-14 10:19:21 + * @Last Modified by: chenghao + * @Last Modified time: 2022-03-20 13:57:10 + * @Desc: 青龙依赖 + * @From: https://github.com/whyour/qinglong/issues/1369 + */ +const axios = require('axios') +const QL_PATH = process.env.QL_PATH?process.env.QL_PATH:'' +const QL_URL = `http://127.0.0.1:5700${QL_PATH}` +const CLIENT_ID = process.env.CLIENT_ID +const CLIENT_SECRET = process.env.CLIENT_SECRET + +/** + *获取青龙token + */ +function getQLToken() { + return new Promise((resolve, reject) => { + axios + .get( + QL_URL + + `/open/auth/token?client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}` + ) + .then(res => { + if (res.data.code === 200) { + resolve(res.data.data.token) + } else { + reject(res.data.message) + } + }) + }) +} + +/** + *构造请求头 + * @returns headers + */ +async function generateRequestHeader() { + return new Promise(async resolve => { + const token = await getQLToken() + resolve({ + Authorization: 'Bearer ' + token, + 'User-Agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4577.63 Safari/537.36', + 'Content-Type': 'application/json;charset=UTF-8', + 'Accept-Encoding': 'gzip, deflate', + 'Accept-Language': 'zh-CN,zh;q=0.9' + }) + }) +} + +/** + *初始化请求实例 + * @returns axios instance + */ +async function init() { + if (!CLIENT_ID || !CLIENT_SECRET) + return Promise.reject('未获取到 CLIENT_ID 或 CLIENT_SECRET') + const headers = await generateRequestHeader() + return new Promise(resolve => { + resolve( + axios.create({ + baseURL: QL_URL, + timeout: 10000, + headers + }) + ) + }) +} + +/** + * + *获取青龙环境变量 + * @param {*} instance + * @returns [] envlist + */ +function getQLEnvs(instance, searchValue = 'JD_COOKIE') { + return new Promise(resolve => { + instance + .get('/open/envs', { + params: { + searchValue, + t: +new Date() + } + }) + .then(res => { + resolve(res.data.data.filter(v => v.status === 0)) + }) + }) +} + +/** + *创建ck环境变量 + * @param {*} instance + * @param {*} [ck=[]] + * @returns + */ +function createCkEnv(instance, ck = []) { + return new Promise(resolve => { + instance + .post(`/open/envs?t=${+new Date()}`, ck) + .then(res => { + resolve(res.data) + }) + .catch(error => { + console.log(error.response.data) + }) + }) +} + +/** + * 更新环境变量 + * @param {*} instance + * @param {*} ck + * @returns + */ +function updateCkEnv(instance, ck = {}) { + return new Promise(resolve => { + instance + .put(`/open/envs?t=${+new Date()}`, ck) + .then(res => { + resolve(res.data) + }) + .catch(error => { + console.log(error.response.data) + }) + }) +} + +/** + * 删除环境变量 + * @param {*} instance + * @param {*} ckIds + * @returns + */ +function deleteCkEnv(instance, ckIds = []) { + return new Promise(resolve => { + instance({ + method: 'delete', + url: `/open/envs?t=${+new Date()}`, + data: ckIds + }).then(resolve) + }) +} + +/** + *切换ck状态 + * @param {*} instance + * @param {*} path + * @param {*} id + * @returns + */ +function toggleCKEnv(instance, id, path = 'enable') { + return new Promise(resolve => { + instance.put(`/open/envs/${path}?t=${+new Date()}`, [id]).then(res => { + resolve(res.data) + }) + }) +} + +exports.createEnv = createCkEnv +exports.deleteEnv = deleteCkEnv +exports.getEnv = getQLEnvs +exports.initInstance = init +exports.updateCkEnv = updateCkEnv +exports.toggleCKEnv = toggleCKEnv