first commit

This commit is contained in:
裴浩宇 2024-01-30 21:51:34 +08:00
commit 02b36bb973
12 changed files with 740 additions and 0 deletions

173
.gitignore vendored Normal file
View File

@ -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

9
.prettierrc Normal file
View File

@ -0,0 +1,9 @@
{
"singleQuote": true,
"semi": false,
"endOfLine": "lf",
"tabWidth": 2,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "avoid"
}

21
LICENSE Normal file
View File

@ -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.

72
README.md Normal file
View File

@ -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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

BIN
assets/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 973 KiB

BIN
assets/refresh_token_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
assets/refresh_token_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

207
autoSignin.js Normal file
View File

@ -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'))
})()

69
package-lock.json generated Normal file
View File

@ -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=="
}
}
}

22
package.json Normal file
View File

@ -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"
}
}

167
qlApi.js Normal file
View File

@ -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