`);
}
const sel = pageNum === 'home' ? panInfo.btn.home : panInfo.btn.main;
const tryInject = () => {
let $toolWrap = $(sel);
if (!$toolWrap.length || $('.fl-button').length) return false;
$toolWrap.prepend($button);
return true;
};
if (!tryInject()) base.listenElement(sel, tryInject);
this.setBDUSS();
this.addPageListener();
},
async getToken() {
const saveToken = (token) => {
if (token) {
base.setValue('baidu_access_token', token);
base.setValue('baidu_access_token_bduss', this.getBDUSS());
this.getCurrentUK().then((uk) => {
if (uk) base.setValue('baidu_access_token_uk', uk);
});
}
return token;
};
const waitForToken = (maxAttempts = 30) => new Promise((resolve) => {
let attempts = 0;
const interval = setInterval(() => {
const token = base.getValue('baidu_access_token');
if (token) {
clearInterval(interval);
resolve(token);
}
attempts++;
if (attempts > maxAttempts) {
clearInterval(interval);
resolve('');
}
}, 1000);
});
let stage = '';
try {
if (base.getFinalUrl) {
stage = '取授权页';
let res = await base.getFinalUrl(panInfo.pcs[3]);
if (res.includes('authorize')) {
stage = '取授权页 HTML';
let html = await base.get(panInfo.pcs[3], {}, 'text');
let bdstoken = html.match(/name="bdstoken"\s+value="([^"]+)"/)?.[1];
let client_id = html.match(/name="client_id"\s+value="([^"]+)"/)?.[1];
if (bdstoken && client_id) {
stage = '提交授权';
let data = {
grant_permissions_arr: 'netdisk',
bdstoken: bdstoken,
client_id: client_id,
response_type: 'token',
display: 'page',
grant_permissions: 'basic,netdisk'
};
await base.post(panInfo.pcs[3], base.stringify(data), {'Content-Type': 'application/x-www-form-urlencoded'});
let res2 = await base.getFinalUrl(panInfo.pcs[3]);
let t = saveToken(res2.match(/access_token=([^&]+)/)?.[1]);
if (t) return t;
console.warn('[FerryLink] 百度授权后未返回 access_token');
} else {
console.warn('[FerryLink] 百度授权页缺少 bdstoken/client_id');
}
} else {
let t = saveToken(res.match(/access_token=([^&]+)/)?.[1]);
if (t) return t;
console.warn('[FerryLink] 百度回跳地址缺少 access_token');
}
}
} catch (e) {
console.warn('[FerryLink] 百度静默授权失败(' + stage + '):' + (e && e.message || e));
}
base.deleteValue('baidu_access_token');
base.tipLoading('已打开百度授权页,正在等待授权完成…');
GM_openInTab(panInfo.pcs[3], {active: false, insert: true, setParent: true});
let token = await waitForToken();
// 授权页可能刚好在超时后才写入,再读一次
if (!token) token = base.getValue('baidu_access_token');
return saveToken(token);
},
async getCurrentUK() {
try {
let res = await Promise.race([
base.get('https://pan.baidu.com/rest/2.0/xpan/nas?method=uinfo', {"User-Agent": panInfo.ua}),
new Promise((resolve) => setTimeout(() => resolve(null), 3000))
]);
if (res && res.errno === 0 && res.user_info && res.user_info.uk) {
return String(res.user_info.uk);
}
} catch (e) {}
return '';
},
async preflightAccount() {
try {
let accessToken = base.getValue('baidu_access_token');
let storedUK = base.getValue('baidu_access_token_uk');
if (!accessToken || !storedUK) return;
let currentUK = await this.getCurrentUK();
if (!currentUK) return;
if (currentUK !== storedUK) {
base.deleteValue('baidu_access_token');
base.deleteValue('baidu_access_token_bduss');
base.deleteValue('baidu_access_token_uk');
}
} catch (e) {
message.error('百度授权失败:' + (e.message || '未知错误'));
}
},
async getPCSLink(maxRequestTime = 1) {
selectedFiles = this.getSelectedList();
let fidArray = this._getFidArray(), res;
const authFail = () => { Swal.close(); return base.renderErrorPanel('百度授权未完成:请刷新重试,仍失败请确认已登录百度', this); };
if (pageNum === 'home' || pageNum === 'main') {
if (selectedFiles.length === 0) {
Swal.close(); return base.renderDownloadPanel([], this);
}
if (!fidArray.length) {
Swal.close(); return message.error('提示:请打开文件夹后勾选文件!');
}
let currentBDUSS = this.getBDUSS();
let storedBDUSS = base.getValue('baidu_access_token_bduss');
let accessToken = base.getValue('baidu_access_token');
if (accessToken) {
let currentUK = await this.getCurrentUK();
let storedUK = base.getValue('baidu_access_token_uk');
let mismatch = false;
if (currentUK && storedUK && storedUK !== currentUK) mismatch = true;
if (!mismatch && currentBDUSS && storedBDUSS && storedBDUSS !== currentBDUSS) mismatch = true;
if (mismatch) {
base.deleteValue('baidu_access_token');
base.deleteValue('baidu_access_token_bduss');
base.deleteValue('baidu_access_token_uk');
this._linkCache = []; // 账号变了,旧 dlink 绑定的是别人的 uk,全废
accessToken = '';
} else if (currentUK && !storedUK) {
base.setValue('baidu_access_token_uk', currentUK);
} else if (currentBDUSS && !storedBDUSS) {
base.setValue('baidu_access_token_bduss', currentBDUSS);
}
}
if (!accessToken) accessToken = await this.getToken();
if (!accessToken) return authFail();
const now = Date.now();
const files = selectedFiles.filter(v => +v.isdir !== 1);
const pending = files.filter(it => {
const idx = this._linkCache.findIndex(c => c.id == it.fs_id);
if (idx === -1) return true;
if (this._linkCache[idx].expires > now) {
Object.assign(it, this._linkCache[idx].data);
return false;
}
this._linkCache.splice(idx, 1);
return true;
});
// 百度 fsids 上限 100,取 50 留余量
const BATCH = 50;
let returnedCount = 0, refreshed = false, anyMarked = false;
for (let i = 0; i < pending.length; i += BATCH) {
const batch = pending.slice(i, i + BATCH);
const fsids = encodeURIComponent('[' + batch.map(x => x.fs_id) + ']');
const url = `${panInfo.pcs[0]}&fsids=${fsids}&access_token=${accessToken}`;
try {
res = await base.get(url, {"User-Agent": panInfo.ua});
} catch (e) { res = null; }
if (res && res.errno === 112) {
Swal.close(); return message.error('提示:页面过期,请刷新重试!');
}
if (!res || res.errno !== 0) {
if (!refreshed && maxRequestTime >= 1) {
refreshed = true;
base.deleteValue('baidu_access_token');
base.deleteValue('baidu_access_token_bduss');
base.deleteValue('baidu_access_token_uk');
accessToken = await this.getToken();
if (!accessToken) return authFail();
i -= BATCH; continue;
}
const reason = res && res.errno
? `获取下载地址失败(错误码 ${res.errno})`
: '请求失败,请重试';
batch.forEach(x => { x.dlink = reason; });
anyMarked = true;
} else if (!Array.isArray(res.list)) {
// 百度会把错误说明塞进 list(如大文件要 SVIP),不重试
const text = typeof res.list === 'string' && res.list ? res.list : '该文件未返回下载链接';
batch.forEach(x => { x.dlink = text; });
anyMarked = true;
} else {
batch.forEach(x => {
const hit = res.list.find(li => li.fs_id == x.fs_id);
if (hit) {
Object.assign(x, hit);
returnedCount++;
this._linkCache.push({ id: x.fs_id, expires: now + 4 * 60 * 60 * 1000, data: hit });
} else if (res.list.length > 0) {
x.dlink = '该文件未返回下载链接(可能已失效或被禁止下载)';
anyMarked = true;
}
});
}
const hc = Swal.getHtmlContainer && Swal.getHtmlContainer();
if (hc) hc.textContent = `已获取 ${Math.min(i + BATCH, pending.length)} / ${pending.length} 个地址`;
if (i + BATCH < pending.length) await base.sleep(1000);
}
if (pending.length > 0 && returnedCount === 0 && !anyMarked) {
if (maxRequestTime >= 1) {
base.deleteValue('baidu_access_token');
base.deleteValue('baidu_access_token_bduss');
base.deleteValue('baidu_access_token_uk');
if (!(await this.getToken())) return authFail();
return this.getPCSLink(maxRequestTime - 1);
}
return message.error('提示:获取下载链接失败!请刷新网页后重试!');
}
const normalized = this.normalizeList(files);
base.renderDownloadPanel(normalized, this);
}
},
normalizeList(list) {
return list.map(v => {
let dlink = '';
if (v.dlink) {
dlink = /^https?:\/\//.test(v.dlink)
? v.dlink + '&access_token=' + base.getValue('baidu_access_token')
: v.dlink;
}
return {
filename: v.server_filename || v.filename || '',
sizeStr: base.sizeFormat(v.size),
rawSize: v.size || 0,
dlink: dlink,
ext: base.getExtension(v.server_filename || v.filename),
isFolder: +v.isdir === 1,
raw: v,
};
});
},
async sendLinkToRPC(filename, link) {
let rpc = base._rpcConfig();
if (!rpc) return 'fail';
let BDUSS = this.getBDUSS();
let url = rpc.url;
let rpcData = {
id: new Date().getTime(),
jsonrpc: '2.0',
method: 'aria2.addUri',
params: [`token:${rpc.token}`, [link], {
dir: rpc.dir,
out: base.safeRpcFilename(filename),
header: [`User-Agent: ${panInfo.ua}`, ...(BDUSS ? [`Cookie: BDUSS=${BDUSS}`] : [])],
}]
};
try {
let res = await base.post(url, rpcData, {"User-Agent": panInfo.ua}, '');
if (res.result) return 'success';
base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)'));
return 'fail';
} catch (e) {
base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启');
return 'fail';
}
},
getSelectedList() {
try {
return require('system-core:context/context.js').instanceForSystem.list.getSelected();
} catch (e) {
let panInfo = document.querySelector('.wp-s-core-pan');
return panInfo && panInfo.__vue__ ? panInfo.__vue__.selectedList : [];
}
},
detectPage() {
let path = location.pathname;
if (/^\/disk\/home/.test(path)) return 'home';
if (/^\/disk\/main/.test(path)) return 'main';
return '';
},
async initFerryLink() {
base.initDefaultConfig();
base.addFerryLinkStyle();
pageNum = this.detectPage();
panInfo = LOCAL_CONFIG.baidu;
Object.freeze && Object.freeze(panInfo);
this.addButton();
base.createTip();
base.registerMenuCommand();
this.preflightAccount();
},
async initAuthorize() {
let authPollTimer = setInterval(() => {
if (/openapi.baidu.com\/oauth\/2.0\/authorize/.test(location.href)) {
let confirmButton =
document.querySelector('#auth-allow') ||
document.querySelector('#accept') ||
document.querySelector('input[type="submit"]') ||
Array.from(document.querySelectorAll('button, a, input[type="button"]')).find(el => {
let t = (el.textContent || el.value || '').trim();
return /^(授权|允许|同意|确认|Allow|Authorize)/i.test(t);
});
if (confirmButton) {
confirmButton.click();
return;
}
}
if (/openapi.baidu.com\/oauth\/2.0\/login_success/.test(location.href)) {
if (location.href.includes('access_token')) {
let token = location.href.match(/access_token=([^]+)/)?.[1];
base.setValue('baidu_access_token', token);
this.getCurrentUK().then((uk) => {
if (uk) base.setValue('baidu_access_token_uk', uk);
});
clearInterval(authPollTimer);
window.close();
}
}
}, 200)
}
};
let ali = {
site: 'ali',
convertLinkToAria(link, filename, ua) {
filename = base.fixFilename(filename);
// 阿里 CDN 没浏览器 UA 就 403,命令也得带头
return encodeURIComponent(`aria2c "${link}" --out "${filename}" -x 16 -s 16 -k 1M --header "Referer: ${location.origin}/" --header "User-Agent: ${ua || navigator.userAgent}"`);
},
convertLinkToBC(link, filename, ua) {
return base._bc(link, filename, {
'Referer': `${location.origin}/`,
'User-Agent': ua || navigator.userAgent,
});
},
convertLinkToCurl(link, filename, ua) {
let terminal = base.settingChoice('setting_terminal_type');
filename = base.fixFilename(filename);
// -e 带 Referer:域名会迁移,写死 aliyundrive.com 在 alipan.com 上会 403
return encodeURIComponent(`${terminal !== 'wp' ? 'curl' : 'curl.exe'} -L -C - "${link}" -o "${filename}" -e "${location.origin}/" -A "${ua || navigator.userAgent}"`);
},
addPageListener() {
doc.on('click', '.fl-button', async (e) => {
if ($(e.target).closest('.g-button-menu, .menu').length) return;
Swal.showLoading();
try { await this.getPCSLink(); } catch (err) { Swal.close(); base.renderErrorPanel('获取下载链接失败:' + (err && err.message || '未知错误'), this); } finally { if (document.querySelector('.swal2-popup.swal2-loading')) Swal.close(); }
});
},
async getRealLink(d, f) {
try {
let tk = base.getStorage('token');
tk = await base.refreshAliToken(tk);
if (!tk || !tk.access_token) {
Swal.close();
return message.error('提示:Token过期或缺失,请刷新网页后重试!');
}
let authorization = `${tk.token_type} ${tk.access_token}`;
let res = await base.post(panInfo.pcs[1], {
drive_id: d,
file_id: f
}, {
authorization,
"content-type": "application/json;charset=utf-8",
"referer": "https://www.aliyundrive.com/",
"x-canary": "client=windows,app=adrive,version=v6.0.0"
});
if (res.code === 'AccessTokenInvalid') {
Swal.close();
return message.error('提示:Token过期,请刷新网页后重试!');
}
if (res.url) {
return res.url;
}
return '';
} catch (e) { return ''; }
},
addButton() {
if (!pageNum) return;
let $toolWrap;
let $button = $(`
`);
if (pageNum === 'home') {
const fallbacks = [
panInfo.btn.home,
'[class*="header--"] [class*="actions--"]',
'[class*="file-list-header"] [class*="action"]',
'header [class*="upload"]',
];
const tryInject = () => {
for (const sel of fallbacks) {
let $el = $(sel);
if ($el.length && $('.fl-button').length === 0) {
$el.append($button);
return true;
}
}
return false;
};
if (!tryInject()) {
fallbacks.forEach(sel => base.listenElement(sel, tryInject));
}
}
this.addPageListener();
},
async getPCSLink() {
selectedFiles = this.getSelectedList();
if (selectedFiles.length === 0) {
Swal.close(); return base.renderDownloadPanel([], this);
}
if (this.isOnlyFolder()) {
Swal.close(); return message.error('提示:请打开文件夹后勾选文件!');
}
let tk = base.getStorage('token');
tk = await base.refreshAliToken(tk);
if (!tk || !tk.access_token) {
Swal.close(); return message.error('提示:请先登录阿里云盘后再下载!');
}
try {
let authorization = `${tk.token_type} ${tk.access_token}`;
const dlQueue = selectedFiles.map(item => base.post(panInfo.pcs[1], {
drive_id: item.driveId,
file_id: item.fileId,
}, {
authorization,
"content-type": "application/json;charset=utf-8",
"referer": "https://www.aliyundrive.com/",
"x-canary": "client=windows,app=adrive,version=v6.0.0"
}));
(await Promise.all(dlQueue)).forEach((res, i) => {
if (res && res.url) selectedFiles[i].downloadUrl = res.url;
});
} catch (e) {
Swal.close();
const m = (e && e.message) || '';
if (/AccessTokenInvalid|token/i.test(m)) {
return message.error('提示:Token已过期,请刷新网页后重试!');
}
return message.error('提示:获取下载链接失败(' + m + '),请刷新重试!');
}
if (selectedFiles.length > 20) {
Swal.close();
return message.error('提示:单次最多可勾选 20 个文件!');
}
let noUrlSelectList = selectedFiles.filter(v => !Boolean(v.downloadUrl))
let queue = [];
noUrlSelectList.forEach((item, index) => {
queue.push(this.getRealLink(item.driveId, item.fileId));
});
const res = await Promise.all(queue);
res.forEach((val, index) => {
if (val && typeof val === 'string' && /^https?:/.test(val)) noUrlSelectList[index].downloadUrl = val;
});
let files = this.normalizeList(selectedFiles);
base.renderDownloadPanel(files, this);
},
normalizeList(list) {
return list.map(v => ({
filename: v.name || '',
sizeStr: base.sizeFormat(v.size),
rawSize: v.size || 0,
dlink: v.downloadUrl || '',
isFolder: v.type === 'folder',
raw: v,
}));
},
async sendLinkToRPC(filename, link) {
let rpc = base._rpcConfig();
if (!rpc) return 'fail';
let url = rpc.url;
const UA = navigator.userAgent;
let rpcData = {
id: new Date().getTime(),
jsonrpc: '2.0',
method: 'aria2.addUri',
params: [`token:${rpc.token}`, [link], {
dir: rpc.dir,
out: base.safeRpcFilename(filename),
header: [`Referer: ${location.origin}/`, `Origin: ${location.origin}`, `User-Agent: ${UA}`]
}]
};
try {
let res = await base.post(url, rpcData, {}, '');
if (res.result) return 'success';
base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)'));
return 'fail';
} catch (e) {
base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启');
return 'fail';
}
},
getSelectedList() {
try {
let selectedList = [];
let tableDom = document.querySelector(panInfo.dom.list);
if (tableDom) {
let reactObj = base.findReact(tableDom, 1);
if (reactObj) {
let props = reactObj.pendingProps;
if (props) {
let fileList = props.dataSource || [];
let selectedKeys = (props.selectedKeys != null) ? (Array.isArray(props.selectedKeys) ? props.selectedKeys : String(props.selectedKeys).split(',')) : [];
fileList.forEach((val) => {
if (selectedKeys.includes(val.fileId)) {
selectedList.push(Object.assign({}, val));
}
});
}
}
return selectedList;
}
let gridDom = document.querySelector(panInfo.dom.grid);
if (!gridDom) return [];
let gridReact = base.findReact(gridDom, 0);
if (!gridReact) return [];
let dataSource = (gridReact.pendingProps || {}).dataSource || [];
if (!dataSource.length) return [];
document.querySelectorAll('[class*="node-card-container"]').forEach((card) => {
let cb = card.querySelector('input[type=checkbox]');
if (!cb || !cb.checked) return;
let title = card.getAttribute('title') || '';
let hit = dataSource.find((v) => v.name === title);
if (hit) selectedList.push(Object.assign({}, hit));
});
return selectedList;
} catch (e) {
return [];
}
},
detectPage() {
let path = location.pathname;
if (/^\/(drive)/.test(path)) return 'home';
return '';
},
isOnlyFolder() {
for (let i = 0; i < selectedFiles.length; i++) {
if (selectedFiles[i].type === 'file') return false;
}
return true;
},
async initFerryLink() {
base.initDefaultConfig();
base.addFerryLinkStyle();
pageNum = this.detectPage();
panInfo = LOCAL_CONFIG.ali;
Object.freeze && Object.freeze(panInfo);
this.addButton();
base.createTip();
base.registerMenuCommand();
}
};
let tianyi = {
site: 'tianyi',
convertLinkToAria(link, filename, ua) {
filename = base.fixFilename(filename);
return encodeURIComponent(`aria2c "${link}" --out "${filename}" -x 16 -s 16 -k 1M`);
},
convertLinkToBC(link, filename, ua) {
let bc = `AA/${encodeURIComponent(filename)}/?url=${encodeURIComponent(link)}ZZ`;
return encodeURIComponent(`bc://http/${base.e(bc)}`);
},
convertLinkToCurl(link, filename, ua) {
let terminal = base.settingChoice('setting_terminal_type');
filename = base.fixFilename(filename);
return encodeURIComponent(`${terminal !== 'wp' ? 'curl' : 'curl.exe'} -L -C - "${link}" -o "${filename}"`);
},
addPageListener() {
doc.on('click', '.fl-button', async (e) => {
if ($(e.target).closest('.g-button-menu, .menu').length) return;
Swal.showLoading();
try { await this.getPCSLink(); } catch (err) { Swal.close(); base.renderErrorPanel('获取下载链接失败:' + (err && err.message || '未知错误'), this); } finally { if (document.querySelector('.swal2-popup.swal2-loading')) Swal.close(); }
});
},
_insertBeforeUpload(container, $button) {
if (container.firstChild) {
container.insertBefore($button[0], container.firstChild);
} else {
container.appendChild($button[0]);
}
},
addButton() {
if (!pageNum) return;
let $toolWrap;
let $button = $(`
下载助手
`);
if (pageNum === 'home') {
base.listenElement(panInfo.btn.home, () => {
let container = document.querySelector(panInfo.btn.home);
if (container && $('.fl-button').length === 0) {
this._insertBeforeUpload(container, $button);
}
})
}
this.addPageListener();
},
async getToken() {
let res = await base.getFinalUrl(panInfo.pcs[1], {});
let accessToken = res.match(/accessToken=(\w+)/)?.[1];
accessToken && base.setStorage('accessToken', accessToken);
return accessToken;
},
async getFileUrlByOnce(item, index, token) {
try {
if (item.downloadUrl) return {
index,
downloadUrl: item.downloadUrl
};
let time = Date.now(),
fileId = item.fileId,
o = "AccessToken=" + token + "&Timestamp=" + time + "&fileId=" + fileId,
url = panInfo.pcs[2] + '?fileId=' + fileId;
let sign = md5(o).toString();
let res = await base.get(url, {
"accept": "application/json;charset=UTF-8",
"sign-type": 1,
"accesstoken": token,
"timestamp": time,
"signature": sign
});
if (res.res_code === 0) {
return {
index,
downloadUrl: res.fileDownloadUrl
};
} else if (res.errorCode === 'InvalidSessionKey') {
return {
index,
downloadUrl: '提示:请先登录网盘!'
};
return {
index,
downloadUrl: '提示:请先[转存]文件,前往[我的网盘]中下载!'
};
} else {
return {
index,
downloadUrl: '获取下载地址失败,请刷新重试!'
};
}
} catch (e) {
return {
index,
downloadUrl: '获取下载地址失败,请刷新重试!'
};
}
},
async getPCSLink() {
selectedFiles = this.getSelectedList();
if (selectedFiles.length === 0) {
Swal.close(); return base.renderDownloadPanel([], this);
}
if (this.isOnlyFolder()) {
Swal.close(); return message.error('提示:请打开文件夹后勾选文件!');
}
let token = base.getStorage('accessToken') || await this.getToken();
if (!token) {
Swal.close(); return message.error('提示:请先登录网盘!');
}
let queue = [];
selectedFiles.forEach((item, index) => {
queue.push(this.getFileUrlByOnce(item, index, token));
});
const res = await Promise.all(queue);
res.forEach(val => {
selectedFiles[val.index].downloadUrl = val.downloadUrl;
});
let files = this.normalizeList(selectedFiles);
base.renderDownloadPanel(files, this);
},
normalizeList(list) {
return list.map(v => ({
filename: v.fileName || '',
sizeStr: base.sizeFormat(v.size),
rawSize: v.size || 0,
dlink: v.downloadUrl && /^https?:\/\//.test(v.downloadUrl) ? v.downloadUrl : '',
isFolder: !!v.isFolder,
raw: v,
}));
},
async sendLinkToRPC(filename, link) {
let rpc = base._rpcConfig();
if (!rpc) return 'fail';
let url = rpc.url;
let rpcData = {
id: new Date().getTime(),
jsonrpc: '2.0',
method: 'aria2.addUri',
params: [`token:${rpc.token}`, [link], {
dir: rpc.dir,
out: base.safeRpcFilename(filename),
header: []
}]
};
try {
let res = await base.post(url, rpcData, {}, '');
if (res.result) return 'success';
base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)'));
return 'fail';
} catch (e) {
base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启');
return 'fail';
}
},
getSelectedList() {
try {
return document.querySelector(".c-file-list").__vue__.selectedList;
} catch (e) {
let detail = document.querySelector(".info-detail");
return detail && detail.__vue__ ? [detail.__vue__.fileDetail] : [];
}
},
detectPage() {
let path = location.pathname;
if (/^\/web\/main/.test(path)) return 'home';
return '';
},
isOnlyFolder() {
for (let i = 0; i < selectedFiles.length; i++) {
if (!selectedFiles[i].isFolder) return false;
}
return true;
},
async initFerryLink() {
base.initDefaultConfig();
base.addFerryLinkStyle();
pageNum = this.detectPage();
panInfo = LOCAL_CONFIG.tianyi;
Object.freeze && Object.freeze(panInfo);
this.addButton();
this.getToken();
base.createTip();
base.registerMenuCommand();
}
};
let xunlei = {
site: 'xunlei',
_homeSelectors: [
'.FileMenu__container--yB0l1',
'.FileMenu__menus--HFKwO',
'.file-menu',
'.source-list-menu',
],
_getAuthCookie() {
let keys = ['userid', 'deviceid', 'XLA_CI', 'xl_fp_rt'];
let parts = [];
for (let k of keys) {
let v = base.getCookie(k);
if (v) parts.push(`${k}=${v}`);
}
return parts.join('; ').replace(/[\\"`$]/g, '');
},
convertLinkToAria(link, filename, ua) {
filename = base.fixFilename(filename);
let cookie = this._getAuthCookie();
let cookieStr = cookie ? ` --header "Cookie: ${cookie}"` : '';
let uaStr = ` --header "User-Agent: ${navigator.userAgent}"`;
// 迅雷多 Range 会断开,aria2 只能单线程
// 迅雷 CDN 认 cookie + UA,缺一个就断连
return encodeURIComponent(`aria2c "${link}" --out "${filename}" -x 1 -s 1${cookieStr}${uaStr}`);
},
convertLinkToBC(link, filename, ua) {
return base._bc(link, filename, {
'Cookie': this._getAuthCookie(),
'User-Agent': ua || navigator.userAgent,
});
},
convertLinkToCurl(link, filename, ua) {
let terminal = base.settingChoice('setting_terminal_type');
filename = base.fixFilename(filename);
let cookie = this._getAuthCookie();
let cookieStr = cookie ? ` -b "${cookie}"` : '';
return encodeURIComponent(`${terminal !== 'wp' ? 'curl' : 'curl.exe'} -L -C - "${link}" -o "${filename}"${cookieStr} -H "User-Agent: ${navigator.userAgent}"`);
},
addPageListener() {
doc.on('click', '.fl-button', async (e) => {
if ($(e.target).closest('.g-button-menu, .menu').length) return;
Swal.showLoading();
try { await this.getPCSLink(); } catch (err) { Swal.close(); base.renderErrorPanel('获取下载链接失败:' + (err && err.message || '未知错误'), this); } finally { if (document.querySelector('.swal2-popup.swal2-loading')) Swal.close(); }
});
},
_findHomeContainer() {
if (panInfo.btn && panInfo.btn.home) {
let el = document.querySelector(panInfo.btn.home);
if (el) return el;
}
for (let sel of this._homeSelectors) {
let el = document.querySelector(sel);
if (el) return el;
}
return null;
},
addButton() {
if (!pageNum) return;
let $toolWrap;
let $button = $(`
下载助手
`);
if (pageNum === 'home') {
this._smartListen(() => {
let container = this._findHomeContainer();
if (container && $('.fl-button').length === 0) {
$(container).prepend($button);
}
});
}
this.addPageListener();
},
_smartListen(callback) {
const checkInterval = 500;
function check() {
setTimeout(check, checkInterval);
callback();
}
check();
},
getToken() {
let credentials = {}, captcha = {};
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
if (/^credentials_/.test(key)) {
credentials = base.getStorage(key);
}
if (/^captcha_[\w]{16}/.test(key)) {
captcha = base.getStorage(key);
}
}
let deviceid = '';
try {
let lsDevice = base.getStorage('deviceid');
if (lsDevice) {
if (typeof lsDevice === 'string' && lsDevice.includes(',')) {
let match = /(\w{32})/.exec(lsDevice.split(','));
if (match) deviceid = match[1];
} else if (typeof lsDevice === 'string') {
let match = /(\w{32})/.exec(lsDevice);
if (match) deviceid = match[1];
}
}
} catch (e) {}
if (!deviceid) {
let cookieDevice = base.getCookie('peerid') || base.getCookie('deviceid');
if (cookieDevice) {
if (cookieDevice.includes('.') && cookieDevice.length > 32) {
let parts = cookieDevice.split('.');
if (parts[1] && parts[1].length >= 32) {
deviceid = parts[1].substring(0, 32);
}
}
if (!deviceid) {
let match = /([0-9a-f]{32})/i.exec(cookieDevice);
if (match) deviceid = match[1];
}
}
}
if (!deviceid) {
deviceid = Array.from({length: 32}, () =>
Math.floor(Math.random() * 16).toString(16)
).join('');
}
let token = {
credentials,
captcha,
deviceid
};
return token;
},
async getFileUrlByOnce(item, index, token) {
try {
if (item.downloadUrl) return {
index,
downloadUrl: item.downloadUrl,
mediaLink: item.mediaLink || ''
};
let headers = {
'content-type': "application/json",
'x-device-id': token.deviceid,
};
if (token.credentials && token.credentials.access_token) {
let tokenType = token.credentials.token_type || 'Bearer';
headers['Authorization'] = `${tokenType} ${token.credentials.access_token}`;
}
if (token.captcha && token.captcha.token) {
headers['x-captcha-token'] = token.captcha.token;
}
let res = await base.get(panInfo.pcs[0] + item.id, headers);
if (res.web_content_link) {
let mediaLink = '';
if (Array.isArray(res.medias) && res.medias.length > 0) {
let media = res.medias.find(m => m && m.link && m.link.url && m.media_name !== '原始画质') || res.medias[0];
if (media && media.link && media.link.url) {
mediaLink = media.link.url;
}
}
return {
index,
downloadUrl: res.web_content_link,
mediaLink
};
} else if (res.reason) {
return {
index,
downloadUrl: '获取失败:' + (res.reason || res.message || '未知错误')
};
} else {
return {
index,
downloadUrl: '获取下载地址失败,请刷新重试!'
};
}
} catch (e) {
return {
index,
downloadUrl: '获取下载地址失败,请刷新重试!'
};
}
},
async getPCSLink() {
selectedFiles = this.getSelectedList();
if (selectedFiles.length === 0) {
Swal.close(); return base.renderDownloadPanel([], this);
}
if (this.isOnlyFolder()) {
Swal.close(); return message.error('提示:请打开文件夹后勾选文件!');
}
if (pageNum === 'home') {
let queue = [];
let token = this.getToken();
if (!token.credentials || !token.credentials.access_token) {
return message.error('提示:登录凭证获取失败,请先登录网盘后刷新页面!');
}
selectedFiles.forEach((item, index) => {
queue.push(this.getFileUrlByOnce(item, index, token));
});
const res = await Promise.all(queue);
res.forEach(val => {
selectedFiles[val.index].downloadUrl = val.downloadUrl;
if (val.mediaLink) selectedFiles[val.index].mediaLink = val.mediaLink;
});
} else {
let dialog = await Swal.fire({
toast: true,
icon: 'info',
title: `提示:请将文件
[保存到网盘]后前往
[我的网盘]中下载!`,
showConfirmButton: true,
confirmButtonText: '点击保存',
position: 'top',
});
if (dialog.isConfirmed) {
let save = document.querySelector('.saveToCloud');
save && save.click();
return;
}
}
let files = this.normalizeList(selectedFiles);
base.renderDownloadPanel(files, this);
},
normalizeList(list) {
return list.map(v => ({
filename: v.name || '',
sizeStr: base.sizeFormat(+v.size),
rawSize: +v.size || 0,
dlink: v.downloadUrl && /^https?:\/\//.test(v.downloadUrl) ? v.downloadUrl : '',
mediaLink: v.mediaLink && /^https?:\/\//.test(v.mediaLink) ? v.mediaLink : '',
isFolder: v.kind === 'drive#folder',
raw: v,
}));
},
async sendLinkToRPC(filename, link) {
let rpc = base._rpcConfig();
if (!rpc) return 'fail';
let url = rpc.url;
let authCookie = this._getAuthCookie ? this._getAuthCookie() : '';
let rpcHeader = authCookie
? [`Cookie: ${authCookie}`, `User-Agent: ${navigator.userAgent}`]
: [];
let rpcData = {
id: new Date().getTime(),
jsonrpc: '2.0',
method: 'aria2.addUri',
params: [`token:${rpc.token}`, [link], {
dir: rpc.dir,
out: base.safeRpcFilename(filename),
header: rpcHeader
}]
};
try {
let res = await base.post(url, rpcData, {}, '');
if (res.result) return 'success';
base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)'));
return 'fail';
} catch (e) {
base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启');
return 'fail';
}
},
getSelectedList() {
try {
let doms = document.querySelectorAll('.SourceListItem__item--XxpOC');
if (doms.length > 0) {
let selectedList = [];
for (let dom of doms) {
let domVue = dom.__vue__;
if (domVue && domVue.info && domVue.selected) {
if (domVue.selected.includes(domVue.info.id)) {
selectedList.push(domVue.info);
}
}
}
if (selectedList.length > 0) return selectedList;
}
let allItems = document.querySelectorAll('[class*="SourceListItem__item"]');
if (allItems.length > 0) {
let selectedList = [];
for (let dom of allItems) {
let domVue = dom.__vue__;
if (domVue) {
let info = domVue.info || domVue.item || (domVue.$props && domVue.$props.info);
let selected = domVue.selected || domVue.fileSelected || (domVue.$props && domVue.$props.selected);
if (info && selected && selected.includes(info.id)) {
selectedList.push(info);
}
}
}
if (selectedList.length > 0) return selectedList;
}
let app = document.querySelector('#__nuxt') || document.querySelector('#app');
if (app && app.__vue__) {
let rootVue = app.__vue__;
let found = this._findSelectedInVueTree(rootVue, 0);
if (found && found.length > 0) return found;
}
return [];
} catch (e) {
return [];
}
},
_findSelectedInVueTree(vm, depth) {
if (depth > 8 || !vm) return null;
try {
if (vm.fileSelected && vm.listInfo && Array.isArray(vm.fileSelected) && vm.fileSelected.length > 0) {
return vm.fileSelected.map(id => vm.listInfo[id]).filter(Boolean);
}
if (vm.$children) {
for (let child of vm.$children) {
let result = this._findSelectedInVueTree(child, depth + 1);
if (result && result.length > 0) return result;
}
}
} catch (e) {}
return null;
},
detectPage() {
let path = location.pathname;
if (/^\/$/.test(path)) return 'home';
return '';
},
isOnlyFolder() {
for (let i = 0; i < selectedFiles.length; i++) {
if (selectedFiles[i].kind === 'drive#file') return false;
}
return true;
},
async initFerryLink() {
base.initDefaultConfig();
base.addFerryLinkStyle();
pageNum = this.detectPage();
panInfo = LOCAL_CONFIG.xunlei;
Object.freeze && Object.freeze(panInfo);
this.addButton();
base.createTip();
base.registerMenuCommand();
}
};
let quark = {
site: 'quark',
convertLinkToAria(link, filename, ua) {
filename = base.fixFilename(filename);
return encodeURIComponent(`aria2c "${link}" --out "${filename}" --header "User-Agent: ${panInfo.ua}" --header "Cookie: ${String(document.cookie).replace(/[\\"`$]/g, '')}"`);
},
convertLinkToBC(link, filename, ua) {
// 只带 __pus/__puus 会 412,要给完整 cookie
return base._bc(link, filename, {
'Referer': `${location.origin}/`,
'User-Agent': ua || navigator.userAgent,
'Cookie': String(document.cookie),
});
},
convertLinkToCurl(link, filename, ua) {
let terminal = base.settingChoice('setting_terminal_type');
filename = base.fixFilename(filename);
return encodeURIComponent(`${terminal !== 'wp' ? 'curl' : 'curl.exe'} -L -C - "${link}" -o "${filename}" -A "${panInfo.ua}" -b "${String(document.cookie).replace(/[\\"`$]/g, '')}"`);
},
addPageListener() {
if (this._listenerBound) return;
this._listenerBound = true;
window.addEventListener('hashchange', async (e) => {
let home = 'https://pan.quark.cn/list#/', all = 'https://pan.quark.cn/list#/list/all';
if (e.oldURL === home && e.newURL === all) return;
await base.sleep(150);
if ($('.quark-button').length > 0) return;
this.addButton();
});
doc.on('click', '.fl-button', async (e) => {
if ($(e.target).closest('.g-button-menu, .menu').length) return;
Swal.showLoading();
try { await this.getPCSLink(); } catch (err) { Swal.close(); base.renderErrorPanel('获取下载链接失败:' + (err && err.message || '未知错误'), this); } finally { if (document.querySelector('.swal2-popup.swal2-loading')) Swal.close(); }
});
},
addButton() {
if (!pageNum) return;
let $toolWrap;
let $button = $(`
`);
if (pageNum === 'home') {
base.listenElement(panInfo.btn.home, () => {
$toolWrap = $(panInfo.btn.home);
$('.fl-button').length === 0 && $toolWrap.prepend($button);
});
}
},
// 夸克不给 token 就派到限速节点,约 30KB/s
async getAccelToken() {
const now = Date.now();
const cached = base.getValue('quark_accel_token');
if (cached && cached.token && cached.expired_timestamp) {
if (cached.expired_timestamp > now + 60000) {
return cached.token;
}
}
const time = Math.floor(now / 1e3);
try {
let res = await base.post(panInfo.pcs[1], {
"conversation_id": "300000" + time,
"conversation_type": 3,
"msg_id": time + "000"
}, {"content-type": "application/json;charset=utf-8", "Cookie": String(document.cookie), "User-Agent": panInfo.ua});
if (res && res.code === 0 && res.data && res.data.token) {
base.setValue('quark_accel_token', res.data);
return res.data.token;
}
} catch (e) {}
return '';
},
async getPCSLink() {
selectedFiles = this.getSelectedList();
if (selectedFiles.length === 0) {
Swal.close(); return base.renderDownloadPanel([], this);
}
if (this.isOnlyFolder()) {
Swal.close(); return message.error('提示:请打开文件夹后勾选文件!');
}
let fids = [];
selectedFiles.forEach(val => {
fids.push(val.fid);
});
if (pageNum === 'home') {
let token = await this.getAccelToken();
let res = await base.post(panInfo.pcs[0], {
"fids": fids,
"speedup_session": "",
"token": token
}, {"content-type": "application/json;charset=utf-8", "Cookie": String(document.cookie), "user-agent": panInfo.ua});
if (res.code === 31001) {
Swal.close(); return message.error('提示:请先登录网盘!');
}
if (res.code !== 0) {
return message.error('提示:获取链接失败!');
}
let files = this.normalizeList(res.data);
base.renderDownloadPanel(files, this);
} else {
let dialog = await Swal.fire({
toast: true,
icon: 'info',
title: `提示:请将文件
[保存到网盘]后前往
[我的网盘]中下载!`,
showConfirmButton: true,
confirmButtonText: '点击保存',
position: 'top',
});
if (dialog.isConfirmed) {
let save = document.querySelector('.file-info_r');
save && save.click();
return;
}
}
},
normalizeList(list) {
return list.map(v => ({
filename: v.file_name || '',
sizeStr: base.sizeFormat(v.size),
rawSize: v.size || 0,
dlink: v.download_url || '',
isFolder: v.file === false,
raw: v,
}));
},
async sendLinkToRPC(filename, link) {
let rpc = base._rpcConfig();
if (!rpc) return 'fail';
let url = rpc.url;
let rpcData = {
id: new Date().getTime(),
jsonrpc: '2.0',
method: 'aria2.addUri',
params: [`token:${rpc.token}`, [link], {
dir: rpc.dir,
out: base.safeRpcFilename(filename),
header: [`User-Agent: ${panInfo.ua}`, `Cookie: ${String(document.cookie)}`],
'user-agent': panInfo.ua,
'referer': `${location.origin}/`
}]
};
try {
let res = await base.post(url, rpcData, {}, '');
if (res.result) return 'success';
base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)'));
return 'fail';
} catch (e) {
base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启');
return 'fail';
}
},
getSelectedList() {
try {
let selectedList = [];
let reactDom = document.getElementsByClassName('file-list')[0];
let reactObj = base.findReact(reactDom);
if (!reactObj) return [];
let props = reactObj.props;
if (props) {
let fileList = props.list || [];
let selectedKeys = props.selectedRowKeys || [];
fileList.forEach((val) => {
if (selectedKeys.includes(val.fid)) {
selectedList.push(Object.assign({}, val));
}
});
}
return selectedList;
} catch (e) {
return [];
}
},
detectPage() {
let path = location.pathname;
if (/^\/(list)/.test(path)) return 'home';
return '';
},
isOnlyFolder() {
for (let i = 0; i < selectedFiles.length; i++) {
if (selectedFiles[i].file) return false;
}
return true;
},
async initFerryLink() {
base.initDefaultConfig();
base.addFerryLinkStyle();
pageNum = this.detectPage();
panInfo = LOCAL_CONFIG.quark;
Object.freeze && Object.freeze(panInfo);
this.addButton();
this.addPageListener();
base.createTip();
base.registerMenuCommand();
}
};
let yidong = {
site: 'yidong',
convertLinkToAria(link, filename, ua) {
filename = base.fixFilename(filename);
return encodeURIComponent(`aria2c "${link}" --out "${filename}" -x 16 -s 16 -k 1M`);
},
convertLinkToBC(link, filename, ua) {
let bc = `AA/${encodeURIComponent(filename)}/?url=${encodeURIComponent(link)}ZZ`;
return encodeURIComponent(`bc://http/${base.e(bc)}`);
},
convertLinkToCurl(link, filename, ua) {
let terminal = base.settingChoice('setting_terminal_type');
filename = base.fixFilename(filename);
return encodeURIComponent(`${terminal !== 'wp' ? 'curl' : 'curl.exe'} -L -C - "${link}" -o "${filename}"`);
},
addPageListener() {
doc.on('click', '.fl-button', async (e) => {
if ($(e.target).closest('.g-button-menu, .menu').length) return;
Swal.showLoading();
try { await this.getPCSLink(); } catch (err) { Swal.close(); base.renderErrorPanel('获取下载链接失败:' + (err && err.message || '未知错误'), this); } finally { if (document.querySelector('.swal2-popup.swal2-loading')) Swal.close(); }
});
},
addButton() {
if (!pageNum) return;
let $toolWrap;
let $button = $(`
`);
if (pageNum === 'home') {
base.listenElement(panInfo.btn.home, () => {
$toolWrap = $(panInfo.btn.home);
$('.fl-button').length === 0 && $toolWrap.prepend($button);
})
}
this.addPageListener();
},
async getFileUrlByOnce(item, index) {
try {
if (item.downloadUrl) return {
index,
downloadUrl: item.downloadUrl
};
if (this.detectPage() === 'home') {
let body = {
"fileId": item.contentID
};
let time = new Date(+new Date() + 8 * 3600 * 1000).toJSON().substr(0, 19).replace('T', ' ');
let key = base.getRandomString(16);
let sign = base.getSign(undefined, body, time, key);
let res = await base.post(panInfo.pcs[0], body, {
'authorization': base.getCookie('authorization'),
'caller': 'web',
'content-type': "application/json;charset=UTF-8",
'CMS-DEVICE': 'default',
'mcloud-channel': '1000101',
'mcloud-client': '10701',
'mcloud-sign': time + "," + key + "," + sign,
'mcloud-version': '7.14.2',
'x-deviceinfo': '||9|7.17.0|edge||||windows 10||zh-CN|||',
'x-huawei-channelsrc': '10000034',
'x-inner-ntwk': '2',
'x-m4c-caller': 'PC',
'x-m4c-src': '10002',
'x-svctype': '1',
'x-yun-api-version': 'v1',
'x-yun-app-channel': '10000034',
'x-yun-channel-source': '10000034',
'x-yun-client-info': '||9|7.17.0|edge||||windows 10||zh-CN|||||',
'x-yun-module-type': '100',
'x-yun-svc-type': '1',
'x-yun-url-type': '3',
});
if (res.success) {
return {
index,
downloadUrl: res.data.url
};
} else {
return {
index,
downloadUrl: '获取下载地址失败,请刷新重试!'
};
}
}
let res = await base.post(panInfo.pcs[1], `linkId=${vueDom.linkID}&contentIds=${encodeURIComponent(item.path)}&catalogIds=`, {
'Content-Type': 'application/x-www-form-urlencoded',
});
if (res.code === 0) {
return {
index,
downloadUrl: res.data.redrUrl
};
} else {
return {
index,
downloadUrl: '获取下载地址失败,请刷新重试!'
};
}
} catch (e) {
return {
index,
downloadUrl: '获取下载地址失败,请刷新重试!'
};
}
},
async getPCSLink() {
selectedFiles = this.getSelectedList();
if (selectedFiles.length === 0) {
Swal.close(); return base.renderDownloadPanel([], this);
}
if (this.isOnlyFolder()) {
return message.error('提示:勾选的全是文件夹,脚本仅支持下载文件,请打开文件夹后勾选文件!');
}
selectedFiles = selectedFiles.filter(v => v && v.contentID);
if (selectedFiles.length === 0) {
return message.error('提示:未获取到文件标识,请刷新页面后重试!');
}
let queue = [];
selectedFiles.forEach((item, index) => {
queue.push(this.getFileUrlByOnce(item, index));
});
const res = await Promise.all(queue);
res.forEach(val => {
selectedFiles[val.index].downloadUrl = val.downloadUrl;
});
let files = this.normalizeList(selectedFiles);
base.renderDownloadPanel(files, this);
},
normalizeList(list) {
return list.map(v => ({
filename: v.contentName || v.coName || '',
sizeStr: base.sizeFormat(v.contentSize || v.coSize),
rawSize: v.contentSize || v.coSize || 0,
dlink: v.downloadUrl && /^https?:\/\//.test(v.downloadUrl) ? v.downloadUrl : '',
isFolder: !!(v.dirEtag || v.caName),
raw: v,
}));
},
async sendLinkToRPC(filename, link) {
let rpc = base._rpcConfig();
if (!rpc) return 'fail';
let url = rpc.url;
let rpcData = {
id: new Date().getTime(),
jsonrpc: '2.0',
method: 'aria2.addUri',
params: [`token:${rpc.token}`, [link], {
dir: rpc.dir,
out: base.safeRpcFilename(filename),
header: []
}]
};
try {
let res = await base.post(url, rpcData, {}, '');
if (res.result) return 'success';
base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)'));
return 'fail';
} catch (e) {
base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启');
return 'fail';
}
},
getSelectedList() {
let normalize = (item, vue) => {
if (!item || typeof item !== 'object') return null;
if (!item.owner && vue) {
try {
let root = vue.$root || vue;
let s = root && root.$data;
if (s) {
let owner = s.owner || s.account ||
(s.userInfo && (s.userInfo.account || s.userInfo.phone)) ||
(s.globalInfo && (s.globalInfo.account || s.globalInfo.phone)) || '';
if (owner) item.owner = owner;
}
} catch (e) {}
}
return item;
};
let result = [];
try {
let vue = document.querySelector(".main_file_list").__vue__;
let list = vue.selectList;
if (Array.isArray(list) && list.length) {
result = list.map(val => normalize(val.item, vue)).filter(Boolean);
if (result.length) return result;
}
} catch (e) {}
try {
let vueDom = document.querySelector(".home-page").__vue__;
let fileList = vueDom._computedWatchers.fileList.value;
let dirList = vueDom._computedWatchers.dirList.value;
let selectedFileIndex = vueDom.selectedFile;
let selectedDirIndex = vueDom.selectedDir;
let selectFileList = fileList.filter((v, i) => {
return selectedFileIndex.includes(i);
});
let selectDirList = dirList.filter((v, i) => {
return selectedDirIndex.includes(i);
});
let list = [...selectFileList, ...selectDirList];
if (list.length) {
result = list.map(v => normalize(v, vueDom)).filter(Boolean);
if (result.length) return result;
}
} catch (e) {}
try {
let containers = document.querySelectorAll('.main_file_list, [class*="file-list"], [class*="fileList"], .file-box, .home-page');
let markSelectors = ['[class*="selected"]', '[class*="checked"]', 'input[type="checkbox"]:checked'];
containers.forEach(container => {
markSelectors.forEach(sel => {
container.querySelectorAll(sel).forEach(dom => {
let vue = dom.__vue__ || (dom.parentElement && dom.parentElement.__vue__) ||
(dom.closest && dom.closest('[class*="item"]') && dom.closest('[class*="item"]').__vue__);
if (!vue) return;
let item = vue.item || vue.fileData || vue.info ||
(vue.$props && (vue.$props.item || vue.$props.file || vue.$props.data));
if (!item && vue.$data) {
item = vue.$data.item || vue.$data.fileData || vue.$data.info;
}
item = normalize(item, vue);
if (item && !result.includes(item)) result.push(item);
});
});
});
if (result.length) return result;
} catch (e) {}
return [];
},
detectPage() {
let path = location.pathname;
if (/^\/w/.test(path)) return 'home';
return '';
},
isOnlyFolder() {
for (let i = 0; i < selectedFiles.length; i++) {
let v = selectedFiles[i] || {};
if (v.kind === 'folder' || v.folder === true || v.isFolder === true || v.contentType === 'folder' || v.contentType === 'dir') continue;
return false;
}
return true;
},
async initFerryLink() {
base.initDefaultConfig();
base.addFerryLinkStyle();
pageNum = this.detectPage();
panInfo = LOCAL_CONFIG.yidong;
Object.freeze && Object.freeze(panInfo);
this.addButton();
base.createTip();
base.registerMenuCommand();
}
};
let ctfile = {
site: 'ctfile',
convertLinkToAria(link, filename, ua) {
filename = base.fixFilename(filename);
return encodeURIComponent(`aria2c "${link}" --out "${filename}" -x 16 -s 16 -k 1M`);
},
convertLinkToBC(link, filename, ua) {
let bc = `AA/${encodeURIComponent(filename)}/?url=${encodeURIComponent(link)}ZZ`;
return encodeURIComponent(`bc://http/${base.e(bc)}`);
},
convertLinkToCurl(link, filename, ua) {
let terminal = base.settingChoice('setting_terminal_type');
filename = base.fixFilename(filename);
return encodeURIComponent(`${terminal !== 'wp' ? 'curl' : 'curl.exe'} -L -C - "${link}" -o "${filename}"`);
},
addPageListener() {
doc.on('click', '.fl-button', async (e) => {
if ($(e.target).closest('.g-button-menu, .menu').length) return;
Swal.showLoading();
try { await this.getPCSLink(); } catch (err) { Swal.close(); base.renderErrorPanel('获取下载链接失败:' + (err && err.message || '未知错误'), this); } finally { if (document.querySelector('.swal2-popup.swal2-loading')) Swal.close(); }
});
},
addButton() {
if (!pageNum) return;
if (pageNum === 'drive') {
this._mountDriveBtn(); // 先进页面就挂一次,工具栏已就绪时不用等 DOM 变动
base.watchDom(() => {
if ($('.fl-button').length) return;
this._mountDriveBtn();
});
return;
}
let cls = pageNum === 'dir' ? 'ctfile-button ctfile-button-sm fl-button' : 'ctfile-button fl-button';
let $button = $(`
${BTN_ICON}下载助手
`);
let mount = pageNum === 'dir' ? '.ct-bulk-bar .ct-actions' : '.ct-download-zone .ct-actions';
base.listenElement(mount, () => {
$('.fl-button').length === 0 && $(mount).prepend($button);
});
},
_uploadBtn() {
let $hit = $('button, a, [role="button"], [class*=btn]').filter(function () {
if (this.hidden || /none/i.test(String(this.style && this.style.display))) return false;
if ($(this).closest('[hidden], [aria-hidden="true"]').length) return false;
if ($(this).find('button, a, [role="button"]').length) return false;
return /^(上传|上传文件|Upload)$/i.test($(this).text().replace(/\s+/g, ''));
}).first();
return $hit.length ? $hit : null;
},
_mountDriveBtn() {
if ($('.fl-button').length) return true; // 已经挂过,别重复插
let $up = this._uploadBtn();
if (!$up) return false; // 工具栏还没渲染出来,等 DOM 变动再挂
let up = $up[0], cs = getComputedStyle(up);
let $btn = $(ROW_BUTTON).css({
'--fl-h': cs.height === 'auto' ? '' : cs.height,
'--fl-bg': cs.backgroundColor,
'--fl-fg': cs.color,
'--fl-bd': cs.borderTopColor,
'--fl-r': cs.borderTopLeftRadius || '8px',
'--fl-fs': cs.fontSize,
'--fl-fw': cs.fontWeight,
});
// 紧贴在一起的是连体组,插到组外
let sib = up.nextElementSibling;
let compact = !!sib && (sib.getBoundingClientRect().left - up.getBoundingClientRect().right) < 2;
let anchor = compact ? up.parentElement : up;
if (!anchor) return false;
// 往上找第一对有间隙的相邻控件
let target = 0, node = up;
for (let d = 0; d < 3 && node && !target; d++) {
node = node.parentElement;
if (!node) break;
let kids = [...node.children].filter(el => el.getBoundingClientRect().width > 0);
for (let i = 1; i < kids.length; i++) {
let g = kids[i].getBoundingClientRect().left - kids[i - 1].getBoundingClientRect().right;
if (g > 2 && g < 60) { target = Math.round(g); break; }
}
}
if (!target) target = 8;
// 组自带左边距,用负 margin 抵掉,不动城通元素
let ml = parseFloat(getComputedStyle(anchor).marginLeft) || 0;
$btn.css('margin-right', clampGap(target - ml) + 'px');
anchor.before($btn[0]);
// 字体和布局可能晚一步才定,多校几次,每次都往目标值上贴
let fix = () => {
if (!$btn[0].isConnected || !up.isConnected) return;
let cur = up.getBoundingClientRect().left - $btn[0].getBoundingClientRect().right;
if (Math.abs(target - cur) >= 0.5) {
let mr = parseFloat(getComputedStyle($btn[0]).marginRight) || 0;
$btn.css('margin-right', clampGap(mr + (target - cur)) + 'px');
cur = up.getBoundingClientRect().left - $btn[0].getBoundingClientRect().right;
}
};
[0, 150, 400].forEach(t => {
if (!t && typeof requestAnimationFrame === 'function') requestAnimationFrame(fix);
else setTimeout(fix, t);
});
return true;
},
_watchHash() {
window.addEventListener('hashchange', () => {
pageNum = this.detectPage();
if (pageNum === 'drive') {
if (!$('.ctfile-button.fl-button').length) this._mountDriveBtn();
return;
}
$('.ctfile-button.fl-button').remove();
});
},
// 分享页 /f/
与目录页 /d/,提取码都在 ?p=
_shareRef() {
let m = /^\/(f|d)\/([^/?#]+)/.exec(location.pathname);
let pass = '';
try { pass = new URLSearchParams(location.search).get('p') || ''; } catch (e) { }
return { type: m ? m[1] : '', id: m ? m[2] : '', pass };
},
async getPCSLink() {
let ref = this._shareRef();
if (!ref.id) return this.detectPage() === 'drive' ? this._driveLink() : base.renderErrorPanel('仅支持文件分享页、目录页与个人网盘页', this);
return ref.type === 'd' ? this._dirLink(ref) : this._fileLink(ref);
},
// 扩展后台发的请求没有来源,得从页面上下文发
async _drivePost(path, body) {
let tk = '';
try { tk = sessionStorage.getItem('ctfile_basic_token') || ''; } catch (e) { }
if (!tk) throw new Error('读不到登录凭证,请刷新页面后重试');
let ext = /^https?:/i.test(path);
let url = ext ? String(path) : ('https://api.ctfile.com' + path);
if (ext && !/^https:\/\/(?:[\w-]+\.)*ctfile\.com(?=\/|$)/i.test(url)) throw new Error('拒绝向非城通地址发送凭证');
let headers = { 'content-type': 'application/json', 'authorization': 'Bearer ' + tk };
let pw = (typeof unsafeWindow !== 'undefined' && unsafeWindow) ? unsafeWindow : null;
if (pw && typeof pw.fetch === 'function') {
let r = await pw.fetch(url, { method: 'POST', headers: headers, body: JSON.stringify(body), credentials: 'include' });
let j = null;
try { j = await r.json(); } catch (e) { }
if (!j) throw new Error('HTTP ' + r.status);
if (r.status >= 400 && !j.code) j.code = r.status;
return j;
}
return base.post(url, body, headers);
},
// 先试 stream,拿不到再走授权链路
async _driveUrls(space, items) {
let ws = space.id || space.workspace_id || space.space_id;
let out = {};
for (let i = 0; i < items.length; i++) {
let it = items[i];
if (items.length > 1) base.tipLoading('正在获取下载链接 ' + (i + 1) + '/' + items.length + '…');
let fid = this._fid(it).replace(/^f/, '');
try {
let r = await this._drivePost('/v4/file/browse/stream', { workspace_id: ws, file_id: fid });
let f = (r && r.file) || (r && r.data && r.data.file) || {};
let url = f.downurl || f.download_url || '';
if (url && !this._deadLink(url)) {
out[this._fid(it)] = url;
if (f.headers) driveHdrs[url] = f.headers;
}
} catch (e) {
console.warn('[FerryLink] ctfile stream 失败:', fid, e && e.message);
}
}
if (Object.keys(out).length) return out;
let ids = items.map(it => this._fid(it)).join(',');
let auth = await this._drivePost('/v4/file/browse/create-download-auth', { workspace_id: ws, ids: ids });
let a = (auth && auth.data) || auth || {};
let sig = a.download_signature || '';
if (!sig) throw new Error('下载授权失败:' + String(JSON.stringify(auth)).slice(0, 120));
let list = await this._drivePost('/v4/file/browse/download-list', {
userid: a.userid, downuid: a.downuid, ids: a.ids || ids,
workspace_id: a.workspace_id || ws, expires_at: a.expires_at,
download_signature: sig, cursor: '', limit: 500
});
this._lastList = list;
let ld = (list && list.data) || list || {};
let rows = ld.results || ld.items || [];
for (let i = 0; i < rows.length; i++) {
let r = rows[i] || {};
if (rows.length > 1) base.tipLoading('正在换取下载地址 ' + (i + 1) + '/' + rows.length + '…');
let sign = r.download_sign || r.sign || '';
if (!sign) continue;
let u = await this._drivePost('/v4/file/browse/fetch-url-direct', { download_sign: sign, carrier: 0 });
let ud = (u && u.data) || u || {};
let url = ud.download_url || ud.downurl || '';
let key = String(r.fid || r.file_id || '');
if (url && !out[key] && !this._deadLink(url)) {
out[key] = url;
if (ud.headers) driveHdrs[url] = ud.headers;
}
this._lastFetch = u;
}
return out;
},
async _driveLink() {
let picked = this._driveCheckedNames();
if (!picked.length) return base.renderDownloadPanel([], this); // 没勾选就给空面板,和其余六家一致
let spaces = await this._driveSpaces();
if (!spaces.length) return base.renderErrorPanel('读不到网盘空间,请刷新页面后重试', this);
let space = spaces[0], items = [];
this._lastFetch = this._lastList = null;
for (let i = 0; i < spaces.length && i < 3; i++) {
let got = [];
try {
got = (await this._driveList(spaces[i])).filter(it => this._isDriveFile(it) && this._fid(it));
} catch (e) {
console.warn('[FerryLink] ctfile 空间取列表失败:', spaces[i], e && e.message);
}
if (got.length) { space = spaces[i]; items = got; break; }
}
let hit = items.filter(it => {
let n = this._name(it);
return n && picked.some(t => t.indexOf(n) !== -1);
});
// 页面上勾了但名字对不上时保留整页,别让面板变空
if (hit.length) items = hit;
else console.warn('[FerryLink] ctfile 勾选没对上文件名:', picked.slice(0, 3), items.map(it => this._name(it)).slice(0, 3));
if (!items.length) return base.renderErrorPanel('当前目录没有可取的文件,请先进入子文件夹', this);
if (items.length >= 100) return base.renderErrorPanel('当前目录文件过多,请进入子文件夹后再试', this);
let ids = items.map(it => this._fid(it));
let byId = {}, dl = null;
try {
dl = await this._drivePost('/v4/file/browse/direct-link', {
workspace_id: space.id || space.workspace_id || space.space_id, file_ids: ids.join(',')
});
let urls = (dl && dl.direct_urls) || (dl && dl.data && dl.data.direct_urls) || [];
urls.forEach(u => {
byId[this._fid(u, true)] = u.url || '';
if (u.headers) driveHdrs[u.url] = u.headers;
});
} catch (e) {
console.warn('[FerryLink] ctfile 直链接口失败,改走下载链路:', e && e.message);
}
// 无签名的 ctcontents 地址打不开,继续找别的路
let gotReal = Object.keys(byId).some(k => byId[k] && !this._deadLink(byId[k]));
if (!gotReal) {
let alt = {};
try { alt = await this._driveUrls(space, items); } catch (e) {
console.warn('[FerryLink] ctfile 换链失败:', e && e.message);
}
Object.keys(alt).forEach(k => {
if (!byId[k] || this._deadLink(byId[k])) byId[k] = alt[k];
});
}
let list = items.map(it => {
let fid = this._fid(it);
let link = byId[fid] || byId[fid.replace(/^f/, '')] || '';
return this._fileRow({ file_name: this._name(it), file_size: it.size || it.file_size || it.size_bytes }, link, it);
});
if (list.every(f => !f.dlink || this._deadLink(f.dlink))) {
let raw = String(JSON.stringify(this._lastFetch || this._lastList || dl || {})).slice(0, 200);
return base.renderErrorPanel('城通个人网盘取不到可用地址。接口原文:' + raw, this);
}
base.renderDownloadPanel(list, this);
},
// orderby 只认服务端枚举,值不对就整页空,所以逐个试
// 页面那条请求的参数最准,只改分页
_driveReplay(root) {
if (!driveTrace.url || !driveTrace.body) return null;
try {
let body = JSON.parse(driveTrace.body);
if (!body || typeof body !== 'object') return null;
body.start = 0;
body.pagesize = 100;
if (body.folder_id === undefined && root) body.folder_id = root;
return body;
} catch (e) {
return null;
}
},
// 根目录不是 '0',先问出真实 folder id
async _driveRoot(space) {
let ws = space.id || space.workspace_id || space.space_id;
let pick = (o) => {
let d = (o && o.data) || o || {};
let f = d.folder || d.root || d.current || (o && o.folder) || {};
let v = String(f.fid || f.id || f.folder_id || '');
return /^d?\d+$/.test(v) ? v : '';
};
try {
let v = pick(await this._drivePost('/v4/file/browse/folder-tree', { workspace_id: ws }));
if (v) return v;
} catch (e) { }
try {
let v = pick(await this._drivePost('/v4/file/browse/find-by-path', { workspace_id: ws, path: '/' }));
if (v) return v;
} catch (e) { }
return '';
},
async _driveList(space) {
if (driveTrace.url && driveTrace.body) {
try {
let b = JSON.parse(driveTrace.body);
if (b && typeof b === 'object') {
b.start = 0;
let res = await this._drivePost(driveTrace.url, b);
let items = this._driveItems(res);
if (items.length) return items;
}
} catch (e) {
console.warn('[FerryLink] ctfile 照抄页面请求失败:', e && e.message);
}
}
let root = await this._driveRoot(space);
if (!root) console.warn('[FerryLink] ctfile 换不到根目录 id,退回 0:', space);
let ws = space.id || space.workspace_id || space.space_id;
let folders = root ? [root] : [this._driveFolder(), 'd0', null];
let plan = [this._driveReplay(root)];
folders.forEach(f => {
plan.push({ orderby: 'new', folder_id: f });
plan.push({ orderby: 'date', folder_id: f });
});
plan = plan.filter(Boolean);
let last = null, err = null;
for (let i = 0; i < plan.length; i++) {
let body = Object.assign({
workspace_id: space.id || space.workspace_id || space.space_id,
folder_id: this._driveFolder(), filter: 'all', keyword: '', start: 0, pagesize: 100
}, plan[i]);
let res;
try {
res = await this._drivePost('/v4/file/browse/list', body);
} catch (e) {
err = e;
continue;
}
last = res;
if (res && res.code && res.code !== 200) {
err = new Error(res.msg || ('城通返回 ' + res.code));
continue;
}
let items = this._driveItems(res);
if (items.length) {
return items;
}
}
if (!last && err) throw err;
console.warn('[FerryLink] ctfile 列表始终为空:', space);
return [];
},
// 文件夹在城通用 fid 首字母 d 标记,没有别的字段可依赖
_isDriveFile(it) {
let raw = String((it && (it.fid || it.file_id || it.id)) || '');
if (!raw || /^d/.test(raw)) return false;
return !(it.kind === 'folder' || it.type === 'folder' || it.is_dir || it.is_folder || it.ext === 'folder');
},
async _driveSpaces() {
let kind = (location.hash.match(/#\/(public|private)/) || [])[1] || 'public';
let res = await this._drivePost('/v4/workspace/manage/list', { start: 0, pagesize: 100 });
let spaces = (res && res.workspaces) || (res && res.data && res.data.workspaces) || [];
let kof = s => s.kind || s.type || s.workspace_type || '';
let hit = spaces.filter(s => kof(s) === kind);
let rest = spaces.filter(s => kof(s) !== kind);
if (!hit.length && spaces.length) console.warn('[FerryLink] ctfile 没有 ' + kind + ' 空间,依次试:', spaces);
return hit.concat(rest);
},
_driveFolder() {
let m = location.hash.match(/#\/(?:public|private)\/(\d+)/);
return m ? m[1] : '0';
},
// 无签名地址打不开,带 cts/ctk/chk 的才是真直链
_deadLink(u) {
let v = String(u || '');
if (!v) return true;
return /ctcontents\.com/.test(v) && !/[?&](ctk|chk|cts)=/.test(v);
},
_driveCheckedNames() {
let names = [];
let push = (el) => {
let t = $(el).text().replace(/\s+/g, ' ').trim();
if (t && names.indexOf(t) === -1) names.push(t);
};
$('input[type=checkbox], input[type=radio]').filter(function () { return this.checked; }).each(function () {
let row = $(this).closest('tr, li, [role=row], [class*=row], [class*=item], [class*=card]');
if (row.length) push(row[0]);
});
$('[aria-selected="true"], .is-selected, .selected, .is-checked').each(function () { push(this); });
return names.slice(0, 40);
},
// 文件数组在 data 里,字段名各版本不一,几种写法都试
_driveItems(res) {
let d = (res && res.data) || {};
let cands = [d.results, d.files, d.items, d.list, d.rows,
res && res.results, res && res.files, res && res.items, res && res.list, res && res.rows];
for (let i = 0; i < cands.length; i++) if (Array.isArray(cands[i]) && cands[i].length) return cands[i];
return [];
},
_fid(it, raw) {
let v = it && (it.fid || it.file_id || it.id);
if (v === undefined || v === null || v === '') return '';
v = String(v);
return raw || /^f/.test(v) ? v : 'f' + v;
},
_name(it) {
return (it && (it.name || it.file_name || it.filename)) || '';
},
// 尺寸有字节数和 "37.92 MB" 两种写法
_sizeOf(v) {
if (typeof v === 'number') return v;
let m = String(v == null ? '' : v).match(/^([\d.]+)\s*([KMGT]?B)?/i);
if (!m) return 0;
let unit = { B: 1, KB: 1024, MB: 1048576, GB: 1073741824, TB: 1099511627776 };
return Math.round((parseFloat(m[1]) || 0) * (unit[(m[2] || 'B').toUpperCase()] || 1));
},
async _fileLink(ref) {
let meta = await base.get('https://webapi.ctfile.com/getfile.php?path=f&f=' + encodeURIComponent(ref.id) + '&passcode=' + encodeURIComponent(ref.pass) + '&token=false&r=' + Math.random());
let info = meta && meta.file;
if (!info || !info.file_chk) return base.renderErrorPanel('读取文件信息失败:' + ((info && info.message) || (meta && meta.msg) || '请确认提取码后重试'), this);
let link = await this._linkOf({ kind: 'file', id: ref.id, folder_id: '0', fk: '', passcode: ref.pass, type_id: '0' }, info.verifycode, {
file_id: info.file_id, userid: info.userid, file_chk: info.file_chk, start_time: info.start_time, wait_seconds: info.wait_seconds
});
if (!link) return base.renderErrorPanel('取不到下载地址:提取码可能不对,或需要验证码', this);
base.renderDownloadPanel([this._fileRow(info, link)], this);
},
async _dirLink(ref) {
let rows = await this._listing(ref);
let files = rows.filter(r => r.kind === 'file' && r.original_file);
if (!files.length) return base.renderErrorPanel('目录里没有可取的文件,请先进入子文件夹', this);
let picked = this._checkedRowIds();
if (picked.length) files = files.filter(r => picked.indexOf(r.id) !== -1);
if (!files.length) return base.renderErrorPanel('勾选的文件不在本页,或全是文件夹', this);
if (files.length > 40) return base.renderErrorPanel('一次最多取 40 个文件,当前 ' + files.length + ' 个,请勾选后再试', this);
let list = [];
for (let i = 0; i < files.length; i++) {
let r = files[i];
if (files.length > 1) base.tipLoading('正在获取下载链接 ' + (i + 1) + '/' + files.length + '…');
let hrefId = (String(r.href || '').match(/#\/f\/(.+)$/) || [])[1];
let link = hrefId ? await this._linkOf({ kind: 'file', id: hrefId, folder_id: '0', fk: '', passcode: ref.pass, type_id: '0' }, r.verifycode, r.original_file) : '';
list.push(this._fileRow({ file_name: r.name, file_size: r.size_bytes }, link, r));
}
base.renderDownloadPanel(list, this);
},
async _listing(ref) {
let rows = [];
for (let page = 0; page < 5; page++) {
let res = await base.get('https://urlapi.ctfile.com/api/v2/listing?kind=dir&id=' + encodeURIComponent(ref.id) + '&folder_id=0&fk=&passcode=' + encodeURIComponent(ref.pass) + '&type_id=0&start=' + rows.length + '&length=200');
if (res && res.code && res.code !== 200) throw new Error(res.msg || ('城通返回 ' + res.code));
let d = res && res.data;
let batch = (d && d.rows) || [];
rows = rows.concat(batch);
if (!batch.length || !d.hasMore) break;
}
return rows;
},
// 临时 id 在 href 上,凭据只能用列表返回的那份
async _linkOf(context, verifycode, original) {
let res = await base.post('https://urlapi.ctfile.com/api/v2/download', {
context: context, mb: 0, app: 0, verifycode: String(verifycode || ''), original_file: original
}, { 'content-type': 'application/json' });
let d = (res && res.data) || res || {};
return d.downurl || d.url || d.download_url || '';
},
_fileRow(info, link, raw) {
let bytes = this._sizeOf(info.file_size);
return {
filename: info.file_name || '',
sizeStr: base.sizeFormat(bytes),
rawSize: bytes,
dlink: link || '',
isFolder: false,
raw: raw || info,
};
},
_checkedRowIds() {
let ids = [];
$('tr[data-row-id] input[type=checkbox]').each(function () {
if (this.checked) ids.push($(this).closest('tr').attr('data-row-id'));
});
return ids.filter(Boolean);
},
async sendLinkToRPC(filename, link) {
let rpc = base._rpcConfig();
if (!rpc) return 'fail';
let url = rpc.url;
let rpcData = {
id: new Date().getTime(),
jsonrpc: '2.0',
method: 'aria2.addUri',
params: [`token:${rpc.token}`, [link], {
dir: rpc.dir,
out: base.safeRpcFilename(filename),
header: []
}]
};
try {
let res = await base.post(url, rpcData, {}, '');
if (res.result) return 'success';
base.panelNotice('Aria2 拒绝请求:' + ((res.error && res.error.message) || '未知错误(请检查 RPC 密钥与保存路径)'));
return 'fail';
} catch (e) {
base.panelNotice('无法连接 RPC(' + rpc.domain + ':' + rpc.port + ')' + (e && e.message ? ':' + e.message : '') + ',请确认下载器已启动且 RPC 服务已开启');
return 'fail';
}
},
detectPage() {
let m = /^\/(f|d)\//.exec(location.pathname);
if (m) return m[1] === 'd' ? 'dir' : 'file';
if (/#\/(public|private)/.test(location.hash)) return 'drive';
// 页面脚本晚一步才补上 hash,域名已经能说明是个人网盘
if (/(^|\.)drive\.ctfile\.com$/.test(location.host)) return 'drive';
return '';
},
async initFerryLink() {
base.initDefaultConfig();
base.addFerryLinkStyle();
pageNum = this.detectPage();
panInfo = {};
this.addButton();
this.addPageListener();
base.createTip();
base.registerMenuCommand();
this._watchHash();
}
};
let main = {
init() {
base.migrate();
if (/(pan|yun).baidu.com/.test(location.host)) {
baidu.initFerryLink();
}
if (/openapi.baidu.com\/oauth/.test(location.href)) {
baidu.initAuthorize()
}
if (/www.(aliyundrive|alipan).com/.test(location.host)) {
ali.initFerryLink();
}
if (/cloud.189.cn/.test(location.host)) {
tianyi.initFerryLink();
}
if (/pan.xunlei.com/.test(location.host)) {
xunlei.initFerryLink();
}
if (/pan.quark.cn/.test(location.host)) {
quark.initFerryLink();
}
if (/(yun|caiyun).139.com/.test(location.host)) {
yidong.initFerryLink();
}
if (/(^|\.)ctfile\.(com|net|cn)$|(^|\.)545c\.com$/.test(location.host)) {
ctfile.initFerryLink();
}
}
};
// document-start 时 DOM 未就绪,等 DOMContentLoaded 再初始化
const startInit = () => main.init();
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', startInit, { once: true });
} else {
startInit();
}
})();