import dayjs from 'dayjs'; import {IOS_UA} from './misc.js'; import req from './req.js'; import {ENV} from "./env.js"; const apiUrl = 'https://api.aliyundrive.com'; // const openApiUrl = 'https://open.aliyundrive.com/adrive/v1.0'; const userUrl = 'https://user.aliyundrive.com' const subtitleExts = ['srt', 'ass', 'scc', 'stl', 'ttml']; class AliDrive { constructor() { this.shareTokenCache = {}; // 共享令牌缓存 this.user = {}; // 用户信息 this.oauth = {}; // OAuth 信息 this.saveFileIdCaches = {}; this.saveDirName = 'drpy'; // 保存目录名称 this.CLIENT_ID = "76917ccccd4441c39457a04f6084fb2f"; this.deviceId = 'PkDvH8q6GRgBASQOA6EMvOI8'; this.saveDirId = null; // 保存目录ID this.sid = '' this.baseHeaders = { 'User-Agent': IOS_UA, // 用户代理 Referer: 'https://www.aliyundrive.com/', // 引用来源 }; } // 初始化方法,加载本地配置 async init() { if (this.token) { console.log('阿里token获取成功:' + this.token) } if (this.ali_refresh_token === '') { this.oauth.access_token = null } else { let exp = JSON.parse(CryptoJS.enc.Base64.parse(this.ali_refresh_token.split('.')[1]).toString(CryptoJS.enc.Utf8)) let now = Math.floor(Date.now() / 1000) if (exp.exp < now) { this.oauth.access_token = null console.log('阿里ali_refresh_token已过期') } else { this.oauth.access_token = this.ali_refresh_token console.log('阿里ali_refresh_token未过期,继续使用,可使用时间截止到:' + (new Date(exp.exp * 1000)).toLocaleString()) console.log('阿里ali_refresh_token获取成功:' + this.ali_refresh_token) } } } get token() { // console.log('env.cookie.quark:',ENV.get('quark_cookie')); return ENV.get('ali_token'); } get ali_refresh_token() { // console.log('env.cookie.quark:',ENV.get('quark_cookie')); return ENV.get('ali_refresh_token'); } // 从分享链接中提取分享ID和文件夹ID getShareData(url) { const regex = /https:\/\/www\.alipan\.com\/s\/([^\\/]+)(\/folder\/([^\\/]+))?|https:\/\/www\.aliyundrive\.com\/s\/([^\\/]+)(\/folder\/([^\\/]+))?/; const matches = regex.exec(url); if (matches) { return { shareId: matches[1] || matches[4], folderId: matches[3] || matches[6] || 'root', }; } return null; } // 计算两个字符串的最长公共子序列(LCS) lcs(str1, str2) { if (!str1 || !str2) { return { length: 0, sequence: '', offset: 0, }; } let sequence = ''; const str1Length = str1.length; const str2Length = str2.length; const num = new Array(str1Length); let maxlen = 0; let lastSubsBegin = 0; for (let i = 0; i < str1Length; i++) { const subArray = new Array(str2Length); for (let j = 0; j < str2Length; j++) { subArray[j] = 0; } num[i] = subArray; } let thisSubsBegin = null; for (let i = 0; i < str1Length; i++) { for (let j = 0; j < str2Length; j++) { if (str1[i] !== str2[j]) { num[i][j] = 0; } else { if (i === 0 || j === 0) { num[i][j] = 1; } else { num[i][j] = 1 + num[i - 1][j - 1]; } if (num[i][j] > maxlen) { maxlen = num[i][j]; thisSubsBegin = i - num[i][j] + 1; if (lastSubsBegin === thisSubsBegin) { sequence += str1[i]; } else { lastSubsBegin = thisSubsBegin; sequence = ''; // clear it sequence += str1.substr(lastSubsBegin, i + 1 - lastSubsBegin); } } } } } return { length: maxlen, sequence: sequence, offset: thisSubsBegin, }; } // 找到最佳的最长公共子序列匹配 findBestLCS(mainItem, targetItems) { const results = []; let bestMatchIndex = 0; for (let i = 0; i < targetItems.length; i++) { const currentLCS = this.lcs(mainItem.name, targetItems[i].name); results.push({target: targetItems[i], lcs: currentLCS}); if (currentLCS.length > results[bestMatchIndex].lcs.length) { bestMatchIndex = i; } } const bestMatch = results[bestMatchIndex]; return {allLCS: results, bestMatch: bestMatch, bestMatchIndex: bestMatchIndex}; } // 延迟函数 delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } // 发送API请求 async api(url, data, headers, retry) { headers = headers || {}; const auth = url.startsWith('adrive/'); Object.assign(headers, this.baseHeaders); if (auth) { await this.refreshAccessToken() Object.assign(headers, { Authorization: this.user.auth, }); } const resp = await req .post(`${apiUrl}/${url}`, data, { headers: headers, }) .catch((err) => { console.error(err); return err.response || {status: 500, data: {}}; }); if (resp.status === 401) { console.error('请求未授权,尝试刷新 Token 并重试'); await this.refreshAccessToken(); Object.assign(headers, { Authorization: this.user.auth, }); return await this.api(url, data, headers, retry - 1); } else if (resp.status === 429 && retry > 0) { console.error('请求频繁,请稍后再试'); await this.delay(1000); await this.refreshAccessToken(); return await this.api(url, data, headers, retry - 1); } return resp.data || {}; } // 发送开放API请求 async openApi(url, data, headers, retry = 3) { headers = headers || {}; let resp = '' if (url.startsWith('http')) { Object.assign(headers, { Authorization: "Bearer " + this.oauth.access_token, }); resp = await req .post(`${url}`, data, { headers: headers, }) .catch((err) => { console.error(err); return err.response || {status: 500, data: {}}; }); } else { Object.assign(headers, { Authorization: this.user.auth, }); resp = await req .post(`${apiUrl}/${url}`, data, { headers: headers, }) .catch((err) => { console.error(err); return err.response || {status: 500, data: {}}; }); } if (resp.status === 429 && retry > 0) { await this.delay(1000); return await this.api(url, data, headers, retry - 1); } return resp.data || {}; } //alist获取授权码 // async getCode(auth){ // const url = `https://open.aliyundrive.com/oauth/users/authorize?client_id=${this.CLIENT_ID}&redirect_uri=https://alist.nn.ci/tool/aliyundrive/callback&scope=user:base,file:all:read,file:all:write&state=Ojo%3D&response_type=code&relogin=true` // let data = JSON.stringify({ // "scope": "user:base,file:all:read,file:all:write", // "authorize": 1, // "drives": [ // "backup", // "resource" // ], // "folder": "" // }) // let headers = { // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', // 'Accept': 'application/json, text/plain, */*', // 'Accept-Encoding': 'gzip, deflate, br, zstd', // 'Content-Type': 'application/json', // } // Object.assign(headers, { // Authorization: auth, // }); // const res = await req.post(url,data,{headers}) // const parsedUrl = new URL(res.data.redirectUri); // return parsedUrl.searchParams.get("code"); // } // // // 获取 OAuth Token // async openAuth() { // if (!this.oauth.access_token || this.oauth.expire_time - dayjs().unix() < 120 && this.user.auth) { // try { // const code = await this.getCode(this.user.auth) // const response = await req.post('https://api.nn.ci/alist/ali_open/code', { // "code": code, // "grant_type": "authorization_code", // "client_id": "", // "client_secret": "" // }, { // headers: { // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', // 'Content-Type': 'application/json', // 'origin': 'https://alist.nn.ci', // 'referer': 'https://alist.nn.ci/' // } // }) // if (response.status === 200) { // this.oauth = response.data; // const info = JSON.parse(CryptoJS.enc.Base64.parse(this.oauth.access_token.split('.')[1]).toString(CryptoJS.enc.Utf8)); // this.oauth.expire_time = info.exp; // this.oauth.auth = `${this.oauth.token_type} ${this.oauth.access_token}`; // await ENV.set('ali_refresh_token', this.oauth.access_token); // // await ENV.set('oauth_cache', this.oauth); // console.log("授权成功") // } // } catch(err){ // if(err.status === '429'){ // console.error('请求频繁,请稍后再试') // }else if(err.status === '401'){ // console.error("授权未成功,请重新授权") // } // return err.response || {status: 500, data: {}}; // } // // }else { // console.log("已授权") // } // } // 获取 drive_sid async getDriveSid() { let data = JSON.stringify({ "scopes": [ "user:base", "file:all:read", "file:all:write" ], "width": 300, "height": 300 }); let headers = { 'Content-Type': 'application/json' } let resp = await req.post('https://aliyundrive-oauth.messense.me/oauth/authorize/qrcode', data, {headers}) if (resp.data.sid) { this.sid = resp.data.sid } } //drive_flag async getDriveFlag() { let body = { "authorize": 1, "scope": "user:base,file:all:read,file:all:write", "drives": ["backup", "resource"], "scopes": ["user:base", "file:all:read", "file:all:write"], "sid": this.sid } let headers = { 'authorization': this.user.auth }; let resp = await req.post('https://open.aliyundrive.com/oauth/users/qrcode/authorize?sid=' + this.sid, body, {headers}) return resp.data.result } //drive_status async getDriveCode() { let status = await req.get(`https://openapi.aliyundrive.com/oauth/qrcode/${this.sid}/status`) if (status.data.status === 'LoginSuccess') { return status.data.authCode } } // drive获取授权码 async driveAuth() { await this.init() if (!this.oauth.access_token || this.oauth.expire_time - dayjs().unix() < 120 && this.user.auth) { let headers = this.baseHeaders Object.assign(headers, { 'Authorization': this.user.auth, }) await this.getDriveSid() let flag = await this.getDriveFlag() if (flag) { let code = await this.getDriveCode() let data = { code: code, grant_type: "authorization_code" } let response = await req.post('https://aliyundrive-oauth.messense.me/oauth/access_token', data) if (response.status === 200) { this.oauth = response.data; const info = JSON.parse(CryptoJS.enc.Base64.parse(this.oauth.access_token.split('.')[1]).toString(CryptoJS.enc.Utf8)); this.oauth.expire_time = info.exp; this.oauth.auth = `${this.oauth.token_type} ${this.oauth.access_token}`; await ENV.set('ali_refresh_token', this.oauth.access_token); // await ENV.set('oauth_cache', this.oauth); console.log("授权成功") } } } else { console.log("已授权,无需再授权") } } // 登录并获取用户信息 async refreshAccessToken() { if (!this.user.user_id || this.user.expire_time - dayjs().unix() < 120) { let loginResp = await req .post( 'https://auth.aliyundrive.com/v2/account/token', { refresh_token: this.user.refresh_token || this.token, grant_type: 'refresh_token', }, {headers: this.baseHeaders} ) .catch((err) => { return err.response || {status: 500, data: {}}; }); if (loginResp.status === 200) { this.user = loginResp.data; this.user.expire_time = dayjs(loginResp.data.expire_time).unix(); this.user.auth = `${this.user.token_type} ${this.user.access_token}`; ENV.set('ali_token', this.user.refresh_token); // await ENV.set('user_cache', this.user); } else { console.error('刷新 Access Token 失败'); } } } // 清除保存目录中的文件 async clearSaveDir() { const listData = await this.openApi(`adrive/v3/file/list`, { drive_id: this.user.drive.resource_drive_id, parent_file_id: this.saveDirId, limit: 100, order_by: 'updated_at', order_direction: 'DESC', }); if (listData.items) { for (const item of listData.items) { const del = await this.openApi(`v2/recyclebin/trash`, { drive_id: this.user.drive.resource_drive_id, file_id: item.file_id, }); console.log(del); } } } // 创建保存目录 async createSaveDir(clean) { if (!this.user.device_id) return; if (this.saveDirId) { if (clean) await this.clearSaveDir(); return; } let driveInfo = await this.userApi(`/v2/user/get`, {}); if (driveInfo.resource_drive_id) { this.user.drive = driveInfo; const resource_drive_id = driveInfo.resource_drive_id; const listData = await this.openApi(`adrive/v3/file/list`, { drive_id: resource_drive_id, parent_file_id: 'root', limit: 100, order_by: 'updated_at', order_direction: 'DESC', }); if (listData.items) { for (const item of listData.items) { if (item.name === this.saveDirName) { this.saveDirId = item.file_id; await this.clearSaveDir(); break; } } if (!this.saveDirId) { const create = await this.openApi(`adrive/v2/file/createWithFolders`, { check_name_mode: 'refuse', drive_id: resource_drive_id, name: this.saveDirName, parent_file_id: 'root', type: 'folder', }); console.log(create); if (create.file_id) { this.saveDirId = create.file_id; } } } } } // 获取共享令牌 async getShareToken(shareData) { if (!this.shareTokenCache[shareData.shareId] || this.shareTokenCache[shareData.shareId].expire_time - dayjs().unix() < 120) { delete this.shareTokenCache[shareData.shareId]; const shareToken = await this.api(`v2/share_link/get_share_token`, { share_id: shareData.shareId, share_pwd: shareData.sharePwd || '', }); if (shareToken.expire_time) { shareToken.expire_time = dayjs(shareToken.expire_time).unix(); this.shareTokenCache[shareData.shareId] = shareToken; } } } // 根据分享链接获取文件列表 async getFilesByShareUrl(shareInfo) { const shareData = typeof shareInfo === 'string' ? this.getShareData(shareInfo) : shareInfo; if (!shareData) return []; await this.getShareToken(shareData); if (!this.shareTokenCache[shareData.shareId]) return []; const videos = []; const subtitles = []; const listFile = async (shareId, folderId, nextMarker) => { const listData = await this.api( `adrive/v2/file/list_by_share`, { share_id: shareId, parent_file_id: folderId, limit: 200, order_by: 'name', order_direction: 'ASC', marker: nextMarker || '', }, { 'X-Share-Token': this.shareTokenCache[shareId].share_token, } ); const items = listData.items; if (!items) return []; if (listData.next_marker) { const nextItems = await listFile(shareId, folderId, listData.next_marker); for (const item of nextItems) { items.push(item); } } const subDir = []; for (const item of items) { if (item.type === 'folder') { subDir.push(item); } else if (item.type === 'file' && item.category === 'video') { if (item.size < 1024 * 1024 * 5) continue; item.name = item.name.replace(/玩偶哥.*【神秘的哥哥们】/g, ''); videos.push(item); } else if (item.type === 'file' && subtitleExts.some((x) => item.file_extension.endsWith(x))) { subtitles.push(item); } } for (const dir of subDir) { const subItems = await listFile(dir.share_id, dir.file_id); for (const item of subItems) { items.push(item); } } return items; }; await listFile(shareData.shareId, shareData.folderId); if (subtitles.length > 0) { videos.forEach((item) => { const matchSubtitle = this.findBestLCS(item, subtitles); if (matchSubtitle.bestMatch) { item.subtitle = matchSubtitle.bestMatch.target; } }); } return videos; } // 保存文件到指定目录 async save(shareId, fileId, clean) { await this.refreshAccessToken(); await this.driveAuth() // await this.openAuth(); await this.createSaveDir(clean); if (clean) { const saves = Object.keys(this.saveFileIdCaches); for (const save of saves) { delete this.saveFileIdCaches[save]; } } if (!this.saveDirId) return null; await this.getShareToken({ shareId: shareId, }); if (!this.shareTokenCache[shareId]) return null; const saveResult = await this.api( `adrive/v2/file/copy`, { file_id: fileId, share_id: shareId, auto_rename: true, to_parent_file_id: this.saveDirId, to_drive_id: this.user.drive.resource_drive_id, }, { 'X-Share-Token': this.shareTokenCache[shareId].share_token, } ); if (saveResult.file_id) return saveResult.file_id; return false; } // 获取实时转码信息 async getLiveTranscoding(shareId, fileId) { if (!this.saveFileIdCaches[fileId]) { const saveFileId = await this.save(shareId, fileId, true); if (!saveFileId) return null; this.saveFileIdCaches[fileId] = saveFileId; } const transcoding = await this.openApi(`v2/file/get_video_preview_play_info`, { file_id: this.saveFileIdCaches[fileId], drive_id: this.user.drive.resource_drive_id, category: 'live_transcoding', url_expire_sec: '14400', }); if (transcoding.video_preview_play_info && transcoding.video_preview_play_info.live_transcoding_task_list) { return transcoding.video_preview_play_info.live_transcoding_task_list; } return null; } // 获取下载链接 async getDownload(shareId, fileId) { if (!this.saveFileIdCaches[fileId]) { const saveFileId = await this.save(shareId, fileId, true); if (!saveFileId) return null; this.saveFileIdCaches[fileId] = saveFileId; } const down = await this.openApi(`https://open.aliyundrive.com/adrive/v1.0/openFile/getDownloadUrl`, { file_id: this.saveFileIdCaches[fileId], drive_id: this.user.drive.resource_drive_id, }, { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) aDrive/6.7.3 Chrome/112.0.5615.165 Electron/24.1.3.7 Safari/537.36', 'Content-Type': 'application/json', 'x-canary': 'client=windows,app=adrive,version=v6.7.3', }); if (down.url) { return down; } return null; } async userApi(url, param) { url = url.startsWith('http') ? url : `${userUrl}${url}`; let headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', 'Accept': 'application/json, text/plain, */*', 'Content-Type': 'application/json', 'accept-language': 'zh-CN,zh;q=0.9', 'referer': 'https://www.alipan.com/' } Object.assign(headers, { Authorization: this.user.auth, }); let resp = await req.post(url, {param}, {headers}) if (resp.status === 200) { return resp.data } else { console.log("获取用户信息失败") return null } } } export const Ali = new AliDrive();