阿里云盘观影(API)
阿里云盘观影(API)

阿里云盘观影(API)

Created
Oct 14, 2023 07:37 PM
Date
May 1, 2021
Tags
API
Electron
Node.js
Drive

密码登录流程

%%{init: { 'theme': 'base' }}%% flowchart TB subgraph 1(获取 SESSIONID) end 1 --> 2 subgraph 2[获取 bizExt] -- 解密 --> 3[得到 token] end 3 --> 4 subgraph 4[获取 code] --> 5(获取 access_token 及 refresh_token) end

获取 SESSIONID

urllib .request( "https://auth.aliyundrive.com/v2/oauth/authorize?client_id=25dzX3vbYqktVxyX&response_type=code&login_type=custom" ) .then((result) => { let cookie = null; for (let i = 0; i < !cookie && result.headers["set-cookie"].length; i++) { if (/SESSIONID=/.test(result.headers["set-cookie"][i])) { cookie = /^SESSIONID=[A-Za-z0-9]*/ .exec(result.headers["set-cookie"][i])[0] .toString(); } } return cookie; });

获取 bizExt

urllib .request( "https://passport.aliyundrive.com/newlogin/login.do?appName=aliyun_drive", { method: "POST", data: { loginId: 手机号, password2: 密码(密文), keepLogin: "true", }, dataType: "json", } ) .then((result) => { return result.data.content.data.bizExt ? result.data.content.data.bizExt : null; });

解密得到 token

let token = JSON.parse(Buffer.from(bizExt, "base64").toString()) .pds_login_result.accessToken;

获取 code

urllib .request("https://auth.aliyundrive.com/v2/oauth/token_login", { method: "POST", contentType: "json", headers: { Cookie: cookie, }, data: { token: token }, dataType: "json", }) .then((result) => { return result.data.goto ? /[^=]+$/.exec(result.data.goto)[0] : null; });

获取 access_tokenrefresh_token

urllib .request("https://websv.aliyundrive.com/token/get", { method: "POST", contentType: "json", data: { code: code }, dataType: "json", }) .then((result) => { if (result.status == 200) { let author = { access_token: result.data["access_token"], refresh_token: result.data["refresh_token"], }; return author; } else { return null; } });
⚠️
Deprecated
月起,官方在登录时加入了滑块校验,该方法已失效。

刷新 access_token

urllib .request("https://websv.aliyundrive.com/token/refresh", { method: "POST", dataType: "json", data: { refresh_token: author["refresh_token"] }, }) .then((result) => { if (result.status == 200) { let author = { access_token: result.data["access_token"], refresh_token: result.data["refresh_token"], }; return author; } else { return null; } });

获取 user_id

urllib .request("https://api.aliyundrive.com/adrive/v2/user/get", { method: "POST", dataType: "json", data: {}, headers: { Authorization: author["access_token"], }, }) .then((res) => res.data.user_id);

生成签名

const { utils, sign, getPublicKey } = require("@noble/secp256k1"); /** * @description string转Uint8Array * @param {string} string */ function string2U8Array(string) { const U8Array = new Uint8Array(string.length); for (let i = 0; i < string.length; i++) { U8Array[i] = string.charCodeAt(i); } return U8Array; } /** * @description 生成签名及公钥 * @param appID app_id * @param deviceId device_id * @param userId user_id * @returns [signature, public_key] */ async function genSignature(appID, deviceId, userId) { const originString = `${appID}:${deviceId}:${userId}:0`; const shaU8Array = await utils.sha256(string2U8Array(originString)); const privateKey = utils.randomPrivateKey(); const signature = await sign(shaU8Array, privateKey, { recovered: true, der: false, }); return [ `${utils.bytesToHex(signature[0])}0${signature[1]}`, utils.bytesToHex(getPublicKey(privateKey)), ]; }

登录设备注册

axios .post( "https://api.aliyundrive.com/users/v1/users/device/create_session", { deviceName: "Electron浏览器", modelName: "Windows网页版", pubKey: public_key, }, { headers: { "x-signature": signature, Authorization: author["access_token"], "x-device-id": device_id, }, } ) .catch((e) => { console.error("设备注册失败,请稍后重试!"); });
 

获取文件列表

axios .post( "https://api.aliyundrive.com/v2/file/list", { drive_id: 云盘id, parent_file_id: 目录id, limit: 100, all: true, image_thumbnail_process: "image/resize,w_160/format,jpeg", image_url_process: "image/resize,w_1920/format,jpeg", video_thumbnail_process: "video/snapshot,t_0,f_jpg,w_300", fields: "*", order_by: "updated_at", order_direction: "DESC", }, { headers: { "x-signature": signature, Authorization: author["access_token"], "x-device-id": device_id, }, } ) .then((res) => { return res.status === 200 ? res.data : null; });

移入回收站

axios .post( "https://api.aliyundrive.com/v2/recyclebin/trash", { drive_id: 云盘id, file_id: 文件id }, { headers: { Authorization: author["access_token"], }, } ) .then((res) => { return res.status == 202 || res.status == 204; });

移出回收站

axios .post( "https://api.aliyundrive.com/v2/recyclebin/restore", { drive_id: 云盘id, file_id: 文件id }, { headers: { Authorization: author["access_token"], }, } ) .then((res) => { return res.status == 202 || res.status == 204; });

删除文件

axios .post( "https://api.aliyundrive.com/v2/file/delete", { drive_id: 云盘id, file_id: 文件id }, { headers: { Authorization: author["access_token"], }, } ) .then((res) => { return res.status == 202 || res.status == 204; });

获取回收站列表

axios .post( "https://api.aliyundrive.com/v2/recyclebin/list", { drive_id: 云盘id, limit: 100, image_thumbnail_process: "image/resize,w_160/format,jpeg", image_url_process: "image/resize,w_1920/format,jpeg", video_thumbnail_process: "video/snapshot,t_0,f_jpg,w_300", order_by: "name", order_direction: "DESC", }, { headers: { Authorization: author["access_token"], }, } ) .then((res) => { return res.status == 200 ? res.data : null; });

获取播放地址

axios .post( "https://api.aliyundrive.com/v2/databox/get_video_play_info", { drive_id: 云盘id, file_id: 文件id }, { headers: { Authorization: author["access_token"], }, } ) .then((res) => { return res.status == 200 ? res.data : null; });
⚠️
Deprecated
月起,官方修改了播放地址获取方式,请用下面方法代替。
axios .post( "https://api.aliyundrive.com/v2/file/get_video_preview_play_info", { category: "live_transcoding", drive_id: 云盘id, file_id: 文件id, template_id: "", }, { headers: { "x-signature": signature, Authorization: author["access_token"], "x-device-id": device_id, }, } ) .then((res) => { return res.status === 200 ? res.data : null; });

播放视频

GET /**/*.mp4
⚠️
Deprecated
月起,官方取消了 mp4 播放方式,并改用了 m3u8 流媒体,请用下面方法代替。
GET /**/*.m3u8 Referer: https://www.aliyundrive.com/
对于浏览器不支持的 Referer 请求头,在 Electron 中可以利用注册协议实现附带请求头。
Electron 协议注册
const { protocol } = require("electron"); function protocol_register() { protocol.registerHttpProtocol("ali-drive", (request, callback) => { callback({ url: "https" + request.url.slice(9), method: request.method, referrer: "https://www.aliyundrive.com/", headers: request.headers, data: request.data || {}, }); }); if (protocol.isProtocolRegistered("ali-drive")) { console.log("http协议注册成功!"); } else { console.error("http协议注册失败!"); } }
调用示例
axios.get('ali-drive://**/*.m3u8')

Electron 实现滑块登录

notion image
let iframeDoc; const setBackgroundInterval = setInterval(setBackground, 100); const checkCompleteInterval = setInterval(checkComplete, 500); /** * @description 与主进程通信 * @param message 消息 */ function sendMessage(message) { const { ipcRenderer } = require("electron"); ipcRenderer.send("recordAuthor", message); } /** * @description 生成页面遮罩 */ function background() { const appendBG = document.createElement("div"); appendBG.id = "bg-inject"; appendBG.style.width = "100vw"; appendBG.style.height = "100vh"; appendBG.style.backgroundColor = "white"; appendBG.style.position = "fixed"; appendBG.style.top = "0"; appendBG.style.left = "0"; } /** * @description 设置页面遮罩 */ function setBackground() { if (!document.getElementById("bg-inject")) { const imgSrc = 图片地址; document.body.style.overflow = "hidden"; const appendBG = background(); document.body.appendChild(appendBG); appendBG.innerHTML = '<div style="text-align: center;font-family: \'PingFangSC-Medium\';font-weight: 600;height: 30px;">登录中😑请稍后</div><img src="' + imgSrc + '" width="300" style="position: absolute;top: -85px;-webkit-user-drag: none;"></img>'; } } /** * @description 检查页面是否加载完成 */ function checkComplete() { if (document.getElementById("PDSFolder")) { clearInterval(checkCompleteInterval); getToken(); } let iframe = document.getElementsByTagName("iframe")[0]; if ( !iframe || !(iframe = iframe.contentWindow.document.getElementsByTagName("iframe")[0]) || !(iframe = iframe.contentWindow.document) ) { return; } iframeDoc = iframe; const loginLinks = iframe.getElementsByClassName("sms-login-link"); for (let i = 0; i < loginLinks.length; i++) { if (loginLinks[i] && loginLinks[i].innerHTML.includes("账号")) { clearInterval(checkCompleteInterval); loginLinks.click(); setTimeout(formConfrim, 200); break; } } } /** * @description 自动填写账号密码 */ function formConfrim() { iframeDoc.getElementById("fm-login-id").value = 手机号; iframeDoc.getElementById("fm-login-password").value = 密码; iframeDoc.getElementsByClassName("password-login")[0].click(); if ( !iframeDoc.getElementById("fm-login-id").value || !iframeDoc.getElementById("fm-login-password").value ) { setTimeout(formConfrim, 200); } if (iframeDoc.getElementsByClassName("password-login")[0]) { function checkVericode() { if (iframeDoc.getElementById("baxia-dialog-content")) { clearInterval(checkVericodeInterval); showSilde(); } } const checkVericodeInterval = setInterval(checkVericode, 100); } } /** * @description 等待滑块验证 */ function slideConfirm() { if ( document .getElementById("baxia-dialog-content") .contentWindow.document.getElementsByClassName("nc-lang-cnt")[0] .innerText === "加载中" ) { setTimeout(() => { iframeDoc.getElementsByClassName("password-login")[0].click(); setTimeout(() => { sendMessage("需要重新登录"); }, 500); }, 500); } } /** * @description 显示滑块验证 */ function showSilde() { const slideIframe = iframeDoc.getElementById("baxia-dialog-content"); slideIframe.style.position = "fixed"; slideIframe.style.top = "30px"; slideIframe.style.left = "1px"; document.getElementById("bg-inject").innerHTML = "<div style=\"text-align: center;font-family: 'PingFangSC-Medium';font-weight: 600;height: 30px;\">请完成😎滑块验证</div>"; document.body.appendChild(slideIframe); setTimeout(() => setInterval(slideConfirm, 200), 1000); } /** * @description 读取token */ function getToken() { try { const token = JSON.parse(window.localStorage.getItem("token")); if (token) { const author = { access_token: `Bearer ${token["access_token"]}`, refresh_token: token["refresh_token"], app_id: window.Global.app_id, device_id: document.cookie.match(/cna=[^;]+/)[0].split("=")[1], }; sendMessage(author); } } catch (e) { sendMessage("登录失败"); } }