├── .gitignore ├── .vercelignore ├── LICENSE ├── api ├── check_link.ts ├── generate.ts ├── sign.ts └── state-query.ts ├── index.html ├── index.ts ├── package.json ├── public └── favicon.ico ├── readme.md ├── serve ├── index.ts └── local-serve.ts ├── tsconfig.json ├── tsconfig.node.json ├── vercel.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .history 2 | node_modules 3 | .vercel 4 | .yarn 5 | package-lock.json 6 | .yarn.lock 7 | dist -------------------------------------------------------------------------------- /.vercelignore: -------------------------------------------------------------------------------- 1 | .history 2 | node_modules 3 | .vercel 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 itxve 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /api/check_link.ts: -------------------------------------------------------------------------------- 1 | import type { VercelRequest, VercelResponse } from "@vercel/node"; 2 | 3 | const headers = { 4 | Referer: "https://www.aliyundrive.com/", 5 | "User-Agent": 6 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36", 7 | }; 8 | 9 | async function matchShareId(link: string) { 10 | const aliRe = /www.aliyundrive.com\/s\/(.*)/; 11 | const aliResult = link.match(aliRe); 12 | 13 | if (!aliResult) { 14 | throw new Error("Link is error"); 15 | } 16 | return aliResult[1]; 17 | } 18 | 19 | async function linkCheck(link: string) { 20 | const aliRe = /www.aliyundrive.com\/s\/(.*)/; 21 | const aliResult = link.match(aliRe); 22 | 23 | if (!aliResult) { 24 | throw new Error("Link is error"); 25 | } 26 | const shareId = aliResult[1]; 27 | return fetch( 28 | "https://api.aliyundrive.com/adrive/v3/share_link/get_share_by_anonymous", 29 | { method: "post", body: JSON.stringify({ share_id: shareId }), headers } 30 | ); 31 | } 32 | 33 | export default async function (req: VercelRequest, res: VercelResponse) { 34 | res.setHeader("Access-Control-Allow-Origin", "*"); 35 | const { link } = req.query; 36 | 37 | if (!link) { 38 | res.status(200).send({ code: 500, msg: "link is empty", data: null }); 39 | return Promise.reject(); 40 | } 41 | 42 | return linkCheck(link as string) 43 | .then(async (r) => { 44 | if (r.status == 200) { 45 | res.status(200).send({ code: r.status, msg: "", data: await r.json() }); 46 | } else { 47 | res.status(200).send({ code: r.status, msg: "", data: await r.json() }); 48 | } 49 | }) 50 | .catch((e: Error) => { 51 | res.status(200).send({ code: 500, msg: e.message, data: null }); 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /api/generate.ts: -------------------------------------------------------------------------------- 1 | import type { VercelRequest, VercelResponse } from "@vercel/node"; 2 | import QRCode from "qrcode"; 3 | import https from "https"; 4 | 5 | export default function (req: VercelRequest, res: VercelResponse) { 6 | res.setHeader("Access-Control-Allow-Origin", "*"); 7 | const { img } = req.query; 8 | https 9 | .get( 10 | { 11 | host: "passport.aliyundrive.com", 12 | path: 13 | "/newlogin/qrcode/generate.do?" + 14 | "appName=aliyun_drive" + 15 | "&fromSite=52" + 16 | "&appName=aliyun_drive" + 17 | "&appEntrance=web" + 18 | "&isMobile=false" + 19 | "&lang=zh_CN" + 20 | "&returnUrl=" + 21 | "&bizParams=" + 22 | "&_bx-v=2.0.31", 23 | timeout: 3000, 24 | }, 25 | (response) => { 26 | let data: any = ""; 27 | response.on("data", (d) => { 28 | data += d; 29 | }); 30 | response.on("end", async () => { 31 | const json = JSON.parse(data); 32 | const result = { 33 | codeContent: json.content.data.codeContent, 34 | ck: json.content.data.ck, 35 | t: json.content.data.t, 36 | }; 37 | if (img) { 38 | const image = await QRCode.toDataURL(result.codeContent); 39 | result.codeContent = image; 40 | } 41 | res.send(result); 42 | }); 43 | } 44 | ) 45 | .on("error", (e) => { 46 | res.status(500).send(e); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /api/sign.ts: -------------------------------------------------------------------------------- 1 | import type { VercelRequest, VercelResponse } from "@vercel/node"; 2 | 3 | export default async function (req: VercelRequest, res: VercelResponse) { 4 | res.setHeader("Access-Control-Allow-Origin", "*"); 5 | const { refreshToken } = req.query; 6 | 7 | try { 8 | const ali = new AliyundriveSign(); 9 | await ali.updateAccesssToken(refreshToken as string); 10 | await ali.sign_in(); 11 | res.status(200).send(ali.messages.join("\n")); 12 | } catch (e) { 13 | res.status(500).send(e); 14 | } 15 | } 16 | 17 | // 源代码 :https://github.com/mrabit/aliyundriveDailyCheck/blob/master/autoSignin.js 18 | export class AliyundriveSign { 19 | updateAccesssTokenURL = "https://auth.aliyundrive.com/v2/account/token"; 20 | signinURL = 21 | "https://member.aliyundrive.com/v1/activity/sign_in_list?_rx-s=mobile"; 22 | rewardURL = 23 | "https://member.aliyundrive.com/v1/activity/sign_in_reward?_rx-s=mobile"; 24 | 25 | authorization = { 26 | nick_name: "", 27 | refresh_token: "", 28 | access_token: "", 29 | }; 30 | messages: string[] = []; 31 | 32 | // 使用 refresh_token 更新 access_token 33 | async updateAccesssToken(refreshToken: string) { 34 | return fetch(this.updateAccesssTokenURL, { 35 | method: "POST", 36 | body: JSON.stringify({ 37 | grant_type: "refresh_token", 38 | refresh_token: refreshToken, 39 | }), 40 | headers: { "Content-Type": "application/json" }, 41 | }) 42 | .then((d) => d.json()) 43 | .then((d) => { 44 | const { code, message, nick_name, refresh_token, access_token } = d; 45 | if ( 46 | code === "RefreshTokenExpired" || 47 | code === "InvalidParameter.RefreshToken" 48 | ) { 49 | return Promise.reject(message); 50 | } 51 | this.authorization = { 52 | nick_name, 53 | refresh_token, 54 | access_token, 55 | }; 56 | }); 57 | } 58 | 59 | //签到 60 | async sign_in(access_token: string = "") { 61 | access_token = this.authorization.access_token || access_token; 62 | return fetch(this.signinURL, { 63 | method: "POST", 64 | body: JSON.stringify({ 65 | isReward: false, 66 | }), 67 | headers: { 68 | authorization: access_token, 69 | "Content-Type": "application/json", 70 | }, 71 | }) 72 | .then((res) => res.json()) 73 | .then(async (json) => { 74 | if (!json.success) { 75 | return Promise.reject(json.message); 76 | } 77 | const { signInLogs, signInCount } = json.result; 78 | const currentSignInfo = signInLogs[signInCount - 1]; // 当天签到信息 79 | 80 | this.messages.push(`本月累计签到 ${signInCount} 天`); 81 | 82 | // 未领取奖励列表 83 | const rewards = signInLogs.filter( 84 | (v: any) => v.status === "normal" && !v.isReward 85 | ); 86 | 87 | if (rewards.length) { 88 | for await (let reward of rewards) { 89 | const signInDay = reward.day; 90 | try { 91 | const rewardInfo = await this.getReward(access_token, signInDay); 92 | this.messages.push( 93 | `第${signInDay}天奖励领取成功: 获得${rewardInfo.name || ""}${ 94 | rewardInfo.description || "" 95 | }` 96 | ); 97 | } catch (e: any) { 98 | this.messages.push(`第${signInDay}天奖励领取失败:`, e); 99 | } 100 | } 101 | } else if (currentSignInfo.isReward) { 102 | this.messages.push( 103 | `今日签到获得${currentSignInfo.reward.name || ""}${ 104 | currentSignInfo.reward.description || "" 105 | }` 106 | ); 107 | } 108 | }); 109 | } 110 | 111 | // 领取奖励 112 | async getReward(access_token: string, signInDay: number) { 113 | return fetch(this.rewardURL, { 114 | method: "POST", 115 | body: JSON.stringify({ signInDay }), 116 | headers: { 117 | authorization: access_token, 118 | "Content-Type": "application/json", 119 | }, 120 | }) 121 | .then((d) => d.json()) 122 | .then((json) => { 123 | if (!json.success) { 124 | return Promise.reject(json.message); 125 | } 126 | return json.result; 127 | }); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /api/state-query.ts: -------------------------------------------------------------------------------- 1 | import type { VercelRequest, VercelResponse } from "@vercel/node"; 2 | import https from "https"; 3 | 4 | export default function (req: VercelRequest, res: VercelResponse) { 5 | res.setHeader("Access-Control-Allow-Origin", "*"); 6 | const { ck, t } = req.query; 7 | const params: any = { 8 | t, 9 | ck, 10 | appName: "aliyun_drive", 11 | appEntrance: "web", 12 | isMobile: "false", 13 | lang: "zh_CN", 14 | returnUrl: "", 15 | fromSite: "52", 16 | bizParams: "", 17 | navlanguage: "zh-CN", 18 | navPlatform: "MacIntel", 19 | }; 20 | 21 | let body = ""; 22 | Object.keys(params).forEach((key) => { 23 | body += "&" + key + "=" + params[key]; 24 | }); 25 | 26 | const status: any = { 27 | NEW: "请用阿里云盘 App 扫码", 28 | SCANED: "请在手机上确认", 29 | EXPIRED: "二维码已过期", 30 | CANCELED: "已取消", 31 | CONFIRMED: "已确认", 32 | }; 33 | https 34 | .request( 35 | { 36 | method: "POST", 37 | host: "passport.aliyundrive.com", 38 | path: 39 | "/newlogin/qrcode/query.do?appName=aliyun_drive&fromSite=52&_bx-v=2.0.31", 40 | headers: { 41 | "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", 42 | }, 43 | timeout: 3000, 44 | }, 45 | (response) => { 46 | let data: any = ""; 47 | response.on("data", (d) => { 48 | data += d; 49 | }); 50 | response.on("end", () => { 51 | const rt = JSON.parse(data).content; 52 | if (rt.data.qrCodeStatus === "CONFIRMED") { 53 | const data = Buffer.from(rt.data.bizExt, "base64"); 54 | rt.data.bizExt = JSON.parse(String(data)); 55 | } 56 | //添加 一个tip 57 | rt.data.tip = status[rt.data.qrCodeStatus]; 58 | res.send(rt); 59 | }); 60 | } 61 | ) 62 | .on("error", (e) => { 63 | res.status(500).send(e); 64 | }) 65 | .end(body); 66 | } 67 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | QR Code扫码获取阿里云盘refresh token 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 |
17 |

二维码已失效

18 | 19 |
20 |
21 | 22 |
23 |

用户信息

24 | 25 | 26 |
27 |
28 | 32 |
33 |
34 | 35 | 36 | 37 | 117 | 118 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | let qr: any = ""; 2 | let checkInterval: NodeJS.Timeout; 3 | const host = ""; 4 | 5 | document.querySelector(".refresh")!.addEventListener("click", getQrCode); 6 | document.querySelector("#sign")!.addEventListener("click", sign); 7 | const userInfoDom = document.querySelector("#user-info")!; 8 | const expireDom = document.querySelector("#expire")!; 9 | const tipDom = document.querySelector("#tip")!; 10 | const nickNameDom = document.querySelector("#nick-name")!; 11 | const userImgDom = document.querySelector("#user-img")!; 12 | const userDom = document.querySelector("#user")!; 13 | 14 | export function getQrCode() { 15 | tipDom.innerHTML = "请用阿里云盘 App 扫码"; 16 | expireDom.classList.remove("show"); 17 | userDom.classList.add("hidden"); 18 | 19 | fetch(host + "/api/generate?img=true") 20 | .then((res) => res.json()) 21 | .then((res) => { 22 | qr = res; 23 | const qrcode = document.getElementById("qrcode")!; 24 | qrcode.setAttribute("src", res.codeContent); 25 | checkInterval = setInterval(checkQrCode, 2500); 26 | }); 27 | } 28 | 29 | export function checkQrCode() { 30 | fetch(host + "/api/state-query?ck=" + qr.ck + "&t=" + qr.t) 31 | .then((res) => res.json()) 32 | .then((res) => { 33 | if (["EXPIRED", "CANCELED"].includes(res.data.qrCodeStatus)) { 34 | clearInterval(checkInterval); 35 | expireDom.classList.add("show"); 36 | } else if (["CONFIRMED"].includes(res.data.qrCodeStatus)) { 37 | const { 38 | nickName, 39 | avatar, 40 | refreshToken, 41 | } = res.data.bizExt.pds_login_result; 42 | 43 | (userInfoDom as HTMLElement).innerText = refreshToken; 44 | nickNameDom.innerHTML = nickName; 45 | userImgDom.setAttribute("src", avatar); 46 | userDom.classList.remove("hidden"); 47 | expireDom.classList.add("show"); 48 | clearInterval(checkInterval); 49 | } 50 | tipDom.innerHTML = res.data.tip; 51 | }); 52 | } 53 | 54 | export function sign() { 55 | const refreshToken = (userInfoDom as HTMLElement).innerText!; 56 | fetch(host + "/api/sign?refreshToken=" + refreshToken) 57 | .then((res) => res.text()) 58 | .then((res) => { 59 | alert(res); 60 | }); 61 | } 62 | 63 | setTimeout(getQrCode, 200); 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aliyundrive-refresh-token", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "type": "module", 7 | "scripts": { 8 | "vercel:dev": "vercel dev --listen 4000", 9 | "serve": "SPORT=4000 tsx serve/index.ts", 10 | "build": "tsc --noEmit && vite build", 11 | "preview": "vite preview" 12 | }, 13 | "devDependencies": { 14 | "@types/express": "^4.17.17", 15 | "@types/qrcode": "^1.4.2", 16 | "express": "^4.18.2", 17 | "tsx": "^3.12.3", 18 | "typescript": "^4.6.3", 19 | "vite": "^2.9.5" 20 | }, 21 | "dependencies": { 22 | "@vercel/node": "^1.14.1", 23 | "qrcode": "^1.5.0" 24 | }, 25 | "engines": { 26 | "node": "18.x" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itxve/aliyundriver-refresh-token/e1429757ee14682f2d9031638b1e3582b919b216/public/favicon.ico -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## QR Code 扫码获取阿里云盘 refresh token 2 | 3 | ## Vercel 部署 4 | 5 | 6 | 7 | ## 本地 开发 8 | 9 | - `yarn serve` 10 | - `yarn vercel:dev` 需要登陆 11 | 12 | ### 生成二维码 13 | 14 | 说明 : 调用此接口 , 获取二维码 15 | 16 | **调用例子 :** `/api/generate` 17 | 18 | **可选参数 :** 19 | `img`: boolean 20 | 21 | **返回说明 :** 22 | 23 | - `t` : 一个用于查询的参数传递给 `/api/state-query` (tip:这个不是当前的时间戳) 24 | - `ck` : 一个用于查询的参数传递给 `/api/state-query` 25 | - `codeContent` : `img`为 true 时是一个 base64 图片否则是二维码的接口地址(可自行绘制二维码) 26 | 27 | ### 查询二维码状态 28 | 29 | 说明 : 调用此接口 ,查询二维码状态 30 | 31 | **调用例子 :** `/api/state-query?t=&ck=` 32 | 33 | **必选参数 :** 34 | 35 | `t` : 使用 `/api/generate`接口返回的 `t` 36 | 37 | `ck` : 使用 `/api/generate`接口返回的 `ck` 38 | 39 | tip : 如果不匹配 查询结果一直是 `EXPIRED`的状态 40 | 41 | **返回说明 :** 42 | 43 | - `qrCodeStatus` 44 | - NEW: "请用阿里云盘 App 扫码", 45 | - SCANED: "请在手机上确认", 46 | - EXPIRED: "二维码已过期", 47 | - CANCELED: "已取消", 48 | - CONFIRMED: "已确认" 49 | - `bizExt` 用户信息、token 等... (已经 base64 解码了,可直接食用) 50 | 51 | ### 签到 [源代码阿里云盘每日签到脚本 青龙面板支持](https://github.com/mrabit/aliyundriveDailyCheck) 52 | 53 | 说明 : 调用此接口 ,查询进行签到 54 | 55 | **调用例子 :** `/api/sign?refreshToken=` 56 | 57 | **必选参数 :** 58 | 59 | `refreshToken` : 一个用于刷新 token 的 refreshToken 60 | 61 | **返回 :** 62 | 63 | 64 | 本月累计签到 x 天
65 | 第 x 天奖励领取成功: 获得 xxx 66 |
67 | 68 | ### 查询阿里云资源链接是否有效 69 | 70 | 说明 : 调用此接口 ,查询源链接是否有效 71 | 72 | **调用例子 :** `/api/check_link?link=` 73 | 74 | **必选参数 :** 75 | 76 | `link` : 云盘资源链接 77 | 示例:https://www.aliyundrive.com/s/{xxx} 78 | 79 | **返回说明 :** 80 | 81 | - `code` 包装的是原接口的状态 `200即为有效` 82 | - `msg` 详情信息 83 | - `data` 包装的是原接口的响应 (不做说明) 84 | 85 | ## 相关项目 86 | 87 | [阿里云盘每日签到脚本 青龙面板支持](https://github.com/mrabit/aliyundriveDailyCheck) 88 | 89 | [阿里云盘 Go SDK](https://github.com/chyroc/go-aliyundrive) 90 | 91 | ## Demo 92 | 93 | [示例](https://aliyundriver-refresh-token.vercel.app/) 94 | 95 | ## License 96 | 97 | [The MIT License (MIT)](https://github.com/itxve/aliyundriver-refresh-token/blob/master/LICENSE) 98 | 99 | ## 最后申明 100 | 101 | 本项目仅做学习交流, 禁止用于各种非法途径!!! 102 | 本项目仅做学习交流, 禁止用于各种非法途径!!! 103 | 本项目仅做学习交流, 禁止用于各种非法途径!!! 104 | -------------------------------------------------------------------------------- /serve/index.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { fileURLToPath } from "url"; 4 | import { createServer as createViteServer } from "vite"; 5 | import { app } from "./local-serve"; 6 | const SERVE_PORT = process.env.SPORT; 7 | 8 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 9 | 10 | async function createServer() { 11 | const vite = await createViteServer({ 12 | server: { middlewareMode: true }, 13 | }); 14 | 15 | app.use(vite.middlewares); 16 | 17 | app.use("*", async (_, res) => { 18 | let indexhtml = fs.readFileSync( 19 | path.resolve(__dirname, "..", "index.html"), 20 | "utf-8" 21 | ); 22 | res.status(200).set({ "Content-Type": "text/html" }).end(indexhtml); 23 | }); 24 | app.listen(SERVE_PORT, () => { 25 | console.log(`server listen on http://localhost:${SERVE_PORT}`); 26 | }); 27 | } 28 | createServer(); 29 | -------------------------------------------------------------------------------- /serve/local-serve.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import genCode from "../api/generate"; 3 | import queryState from "../api/state-query"; 4 | import sign from "../api/sign"; 5 | import check_link from "../api/check_link"; 6 | 7 | const app = express(); 8 | 9 | app.get("/api/generate", async (req, res) => { 10 | await genCode(req as any, res as any); 11 | }); 12 | 13 | app.get("/api/state-query", async (req, res) => { 14 | await queryState(req as any, res as any); 15 | }); 16 | 17 | app.get("/api/sign", async (req, res) => { 18 | await sign(req as any, res as any); 19 | }); 20 | 21 | app.get("/api/check_link", async (req, res) => { 22 | await check_link(req as any, res as any); 23 | }); 24 | 25 | export { app }; 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "useDefineForClassFields": true, 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "lib": ["esnext", "dom"], 13 | "baseUrl": ".", 14 | "skipLibCheck": true 15 | }, 16 | "include": ["api/*.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "functions": { 3 | "api/*.ts": { 4 | "memory": 1024, 5 | "maxDuration": 8 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | export const SPORT = process.env.SPORT; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | server: { 7 | port: 8000, 8 | proxy: { 9 | "/api": { 10 | target: `http://localhost:${SPORT}/`, 11 | rewrite: (path) => path, 12 | }, 13 | }, 14 | }, 15 | build: { 16 | minify: true, 17 | }, 18 | }); 19 | --------------------------------------------------------------------------------