Files
MoonTVPlus/src/lib/pancheck/vendor/checkers/cmcc.js
T
2026-04-27 21:25:31 +08:00

122 lines
3.9 KiB
JavaScript

const crypto = require('crypto');
const { request } = require('./http');
/**
* 中国移动云盘链接检测
* URL格式:
* https://yun.139.com/shareweb/#/w/i/{shareID}
* https://caiyun.139.com/m/i?{shareID}
* API: POST https://share-kd-njs.yun.139.com/yun-share/richlifeApp/devapp/IOutLink/getOutLinkInfoV6
* 请求和响应均通过AES-CBC加密(固定密钥)
*/
const CMCC_AES_KEY = 'PVGDwmcvfs1uV3d1';
function aesCBCEncrypt(plaintext, key) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-128-cbc', Buffer.from(key, 'utf-8'), iv);
// PKCS7 padding
const blockSize = 16;
const padLen = blockSize - (Buffer.byteLength(plaintext, 'utf-8') % blockSize);
const padded = Buffer.concat([
Buffer.from(plaintext, 'utf-8'),
Buffer.alloc(padLen, padLen),
]);
const encrypted = Buffer.concat([cipher.update(padded), cipher.final()]);
return Buffer.concat([iv, encrypted]).toString('base64');
}
function aesCBCDecrypt(encryptedBase64, key) {
const rawData = Buffer.from(encryptedBase64, 'base64');
if (rawData.length < 16) throw new Error('加密数据长度不足');
const iv = rawData.subarray(0, 16);
const ciphertext = rawData.subarray(16);
const decipher = crypto.createDecipheriv('aes-128-cbc', Buffer.from(key, 'utf-8'), iv);
const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
// 去除PKCS7填充
const padLen = decrypted[decrypted.length - 1];
if (padLen > 0 && padLen <= 16) {
return decrypted.subarray(0, decrypted.length - padLen).toString('utf-8');
}
return decrypted.toString('utf-8');
}
async function checkCMCC(link) {
const shareID = extractShareID(link);
if (!shareID) {
return { valid: false, reason: '链接格式无效:无法提取分享ID' };
}
try {
const requestData = {
getOutLinkInfoReq: {
account: '',
linkID: shareID,
passwd: '',
caSrt: 1,
coSrt: 1,
srtDr: 0,
bNum: 1,
pCaID: 'root',
eNum: 200,
},
commonAccountInfo: {
account: '',
accountType: 1,
},
};
const jsonStr = JSON.stringify(requestData);
const encryptedData = aesCBCEncrypt(jsonStr, CMCC_AES_KEY);
const encryptedJSON = JSON.stringify(encryptedData);
const { statusCode, body } = await request(
'https://share-kd-njs.yun.139.com/yun-share/richlifeApp/devapp/IOutLink/getOutLinkInfoV6',
{
method: 'POST',
body: encryptedJSON,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json, text/plain, */*',
'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',
'hcy-cool-flag': '1',
'x-deviceinfo': '||3|12.27.0|chrome|131.0.0.0|5c7c68368f048245e1ce47f1c0f8f2d0||windows 10|1536X695|zh-CN|||',
},
}
);
if (statusCode !== 200) {
return { valid: false, reason: `API返回错误状态码: ${statusCode}` };
}
// 解密响应
const decryptedData = aesCBCDecrypt(body.trim(), CMCC_AES_KEY);
const response = JSON.parse(decryptedData);
const resultCode = response.resultCode;
const desc = response.desc;
const data = response.data;
if (resultCode === '0' && data != null) {
return { valid: true, reason: '' };
}
const failReason = desc || (resultCode ? `错误码: ${resultCode}` : '获取分享信息失败');
return { valid: false, reason: failReason };
} catch (err) {
if (err.message === '请求超时') return { valid: false, reason: '请求超时' };
return { valid: false, reason: `检测失败: ${err.message}` };
}
}
function extractShareID(shareURL) {
const match = shareURL.match(/https:\/\/(?:yun\.139\.com\/shareweb\/#\/w\/i\/|caiyun\.139\.com\/m\/i\?)([^&]+)/);
return match ? match[1] : '';
}
module.exports = { checkCMCC, extractShareID, aesCBCEncrypt, aesCBCDecrypt };