Greasy Fork is available in English.
监听天翼云盘、夸克网盘和115网盘转存成功事件,根据转存路径触发qilin-strm自动化,支持设置延时触发。本脚本在SmartStrm助手基础上修改,感谢作者的辛勤开发。
// ==UserScript==
// @name qilin-strm转存助手
// @namespace https://github.com/linzxcw/qilin-strm
// @license AGPL
// @icon https://raw.githubusercontent.com/linzxcw/qilin-strm/refs/heads/main/public/logo.png
// @version 1.0内测版
// @description 监听天翼云盘、夸克网盘和115网盘转存成功事件,根据转存路径触发qilin-strm自动化,支持设置延时触发。本脚本在SmartStrm助手基础上修改,感谢作者的辛勤开发。
// @author qilinzhu
// @match https://cloud.189.cn/web/share?code=*
// @match https://pan.quark.cn/s/*
// @match https://115cdn.com/s/*
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @connect *
// ==/UserScript==
(function () {
'use strict';
let webhook = GM_getValue('webhook', '');
let delay = parseInt(GM_getValue('delay', '0'), 10);
let tianyiMountPath = GM_getValue('tianyi_mountpath', '');
let quarkMountPath = GM_getValue('quark_mountpath', '');
let one15MountPath = GM_getValue('115_mountpath', ''); // 115网盘挂载路径
// 天翼云盘转存信息
let cloud189SaveInfo = {
fileName: '',
isFolder: false
};
// 夸克网盘转存信息
let quarkSaveInfo = {
fileName: '',
isFolder: false
};
// 添加自定义样式
function addCustomStyles() {
const style = document.createElement('style');
style.textContent = `
/* 模态框样式 */
.custom-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
.modal-content {
background: white;
border-radius: 12px;
padding: 30px;
width: 90%;
max-width: 500px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
animation: modalFadeIn 0.3s ease-out;
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
margin-bottom: 25px;
text-align: center;
}
.modal-title {
font-size: 20px;
font-weight: 600;
color: #333;
margin: 0;
}
.modal-body {
margin-bottom: 25px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
font-weight: 500;
color: #333;
margin-bottom: 8px;
text-align: left;
}
.form-input {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
transition: border-color 0.3s ease;
}
.form-input:focus {
outline: none;
border-color: #4CAF50;
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
}
.input-group {
display: flex;
align-items: center;
gap: 10px;
}
.input-group .form-input {
flex: 1;
}
.input-group .input-suffix {
color: #666;
white-space: nowrap;
}
.modal-footer {
display: flex;
gap: 10px;
justify-content: flex-end;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary {
background: #4CAF50;
color: white;
}
.btn-primary:hover {
background: #45a049;
transform: translateY(-1px);
}
.btn-secondary {
background: #f1f1f1;
color: #333;
}
.btn-secondary:hover {
background: #e0e0e0;
transform: translateY(-1px);
}
/* 提示框样式 */
.toast {
position: fixed;
top: 20px;
right: 20px;
padding: 16px 20px;
border-radius: 8px;
color: white;
font-size: 14px;
font-weight: 500;
z-index: 10000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
animation: toastSlideIn 0.3s ease-out, toastFadeOut 0.3s ease-in 2.7s;
max-width: 400px;
word-wrap: break-word;
}
@keyframes toastSlideIn {
from {
opacity: 0;
transform: translateX(100%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes toastFadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
transform: translateX(100%);
}
}
.toast-success {
background: #4CAF50;
}
.toast-error {
background: #f44336;
}
/* 验证错误提示 */
.error-message {
color: #f44336;
font-size: 12px;
margin-top: 5px;
text-align: left;
}
`;
document.head.appendChild(style);
}
// 显示设置模态框
function showSettingsModal() {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'custom-modal';
modal.innerHTML = `
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">qilin-strm转存助手设置</h2>
</div>
<div class="modal-body">
<div class="form-group">
<label class="form-label" for="webhook-input">Webhook 地址:</label>
<input type="text" id="webhook-input" class="form-input" placeholder="http://192.168.31.8:5266/webhook/9dfb573e" value="${webhook || ''}">
<div id="webhook-error" class="error-message" style="display: none;"></div>
</div>
<div class="form-group">
<label class="form-label" for="delay-input">执行延时:</label>
<div class="input-group">
<input type="number" id="delay-input" class="form-input" min="0" title="0为不延时" value="${delay}">
<span class="input-suffix">秒</span>
</div>
<div id="delay-error" class="error-message" style="display: none;"></div>
</div>
<div class="form-group">
<label class="form-label" for="tianyi-mountpath-input">天翼云盘在openlist的挂载路径:</label>
<input type="text" id="tianyi-mountpath-input" class="form-input" placeholder="例如:/天翼云盘" value="${tianyiMountPath || ''}">
</div>
<div class="form-group">
<label class="form-label" for="quark-mountpath-input">夸克网盘在openlist的挂载路径:</label>
<input type="text" id="quark-mountpath-input" class="form-input" placeholder="例如:/夸克网盘" value="${quarkMountPath || ''}">
</div>
<div class="form-group">
<label class="form-label" for="115-mountpath-input">115网盘在openlist的挂载路径:</label>
<input type="text" id="115-mountpath-input" class="form-input" placeholder="例如:/115网盘" value="${one15MountPath || ''}">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="cancel-btn">取消</button>
<button class="btn btn-primary" id="save-btn">保存</button>
</div>
</div>
`;
document.body.appendChild(modal);
// 保存按钮事件
document.getElementById('save-btn').addEventListener('click', function() {
const webhookInput = document.getElementById('webhook-input').value;
const delayInput = document.getElementById('delay-input').value;
const tianyiMountPathInput = document.getElementById('tianyi-mountpath-input').value;
const quarkMountPathInput = document.getElementById('quark-mountpath-input').value;
const one15MountPathInput = document.getElementById('115-mountpath-input').value;
// 验证
let isValid = true;
if (!webhookInput) {
document.getElementById('webhook-error').textContent = 'Webhook 地址不能为空!';
document.getElementById('webhook-error').style.display = 'block';
isValid = false;
} else {
document.getElementById('webhook-error').style.display = 'none';
}
if (delayInput === '' || isNaN(parseInt(delayInput, 10)) || parseInt(delayInput, 10) < 0) {
document.getElementById('delay-error').textContent = '延时应为非负整数!';
document.getElementById('delay-error').style.display = 'block';
isValid = false;
} else {
document.getElementById('delay-error').style.display = 'none';
}
if (isValid) {
webhook = webhookInput;
delay = parseInt(delayInput, 10);
tianyiMountPath = tianyiMountPathInput;
quarkMountPath = quarkMountPathInput;
one15MountPath = one15MountPathInput;
GM_setValue('webhook', webhook);
GM_setValue('delay', delay);
GM_setValue('tianyi_mountpath', tianyiMountPath);
GM_setValue('quark_mountpath', quarkMountPath);
GM_setValue('115_mountpath', one15MountPath);
// 关闭模态框
document.body.removeChild(modal);
// 显示成功提示
showToast('设置已保存', 'success');
}
});
// 取消按钮事件
document.getElementById('cancel-btn').addEventListener('click', function() {
document.body.removeChild(modal);
});
// 点击外部关闭
modal.addEventListener('click', function(e) {
if (e.target === modal) {
document.body.removeChild(modal);
}
});
}
// 显示提示框
function showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
document.body.appendChild(toast);
// 3秒后移除
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
}, 3000);
}
// 注册菜单命令
GM_registerMenuCommand('参数设置', showSettingsModal);
// 页面加载完成后再检查
window.addEventListener('load', async () => {
// 添加自定义样式
addCustomStyles();
if (!webhook) {
// 首次运行时显示设置
setTimeout(showSettingsModal, 1000);
}
// 根据当前域名判断处理逻辑
if (window.location.hostname === 'cloud.189.cn') {
initCloud189();
} else if (window.location.hostname === 'pan.quark.cn') {
initQuark();
} else if (window.location.hostname === '115cdn.com') {
init115();
}
console.log('qilin-strm转存助手已启动');
console.log('当前设置:', {
webhook: webhook,
delay: delay,
tianyiMountPath: tianyiMountPath,
quarkMountPath: quarkMountPath
});
});
// 天翼云盘处理逻辑
function initCloud189() {
let saveSuccess = false;
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
this._url = url;
originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function (body) {
// 拦截 createBatchTask.action 请求,获取文件信息
if (this._url.includes('api/open/batch/createBatchTask.action')) {
try {
console.log('检测到 createBatchTask.action 请求');
console.log('请求体类型:', typeof body);
console.log('请求体:', body);
// 尝试从请求体中提取 taskInfos
if (!cloud189SaveInfo.fileName && body) {
try {
if (typeof body === 'string') {
// 尝试解析表单数据
const formData = new URLSearchParams(body);
const taskInfosStr = formData.get('taskInfos');
console.log('从请求体获取 taskInfos:', taskInfosStr);
if (taskInfosStr) {
const taskInfos = JSON.parse(decodeURIComponent(taskInfosStr));
if (taskInfos && taskInfos.length > 0) {
const firstTask = taskInfos[0];
cloud189SaveInfo.fileName = firstTask.fileName || '';
cloud189SaveInfo.isFolder = firstTask.isFolder === 1;
console.log('天翼云盘转存信息(从请求体):', cloud189SaveInfo);
}
}
} else {
console.log('请求体不是字符串,类型:', typeof body);
}
} catch (e) {
console.error('从请求体解析 taskInfos 失败:', e);
}
}
// 尝试从请求体中直接解析JSON
if (!cloud189SaveInfo.fileName && body && typeof body === 'string') {
try {
const jsonData = JSON.parse(body);
console.log('尝试解析JSON请求体:', jsonData);
if (jsonData.taskInfos && jsonData.taskInfos.length > 0) {
const firstTask = jsonData.taskInfos[0];
cloud189SaveInfo.fileName = firstTask.fileName || '';
cloud189SaveInfo.isFolder = firstTask.isFolder === 1;
console.log('天翼云盘转存信息(从JSON):', cloud189SaveInfo);
}
} catch (e) {
console.log('请求体不是JSON格式:', e.message);
}
}
} catch (e) {
console.error('解析天翼云盘转存请求失败:', e);
}
}
this.addEventListener('load', function () {
try {
if (this._url.includes('api/open/batch/checkBatchTask.action')) {
const response = JSON.parse(this.responseText);
// taskStatus=4 任务成功
if (response.taskStatus === 4) {
saveSuccess = true;
console.log('检测到天翼云盘转存成功');
}
}
// 监听系统发出的 getLastSavePath.action 请求,但仅在转存成功后才处理
else if (this._url.includes('api/open/getLastSavePath.action') && saveSuccess) {
const response = JSON.parse(this.responseText);
if (response.code === 'success') {
const savepath = response.data.filePath;
sendWebhook(savepath, 'cloud189', cloud189SaveInfo);
saveSuccess = false;
// 重置转存信息
cloud189SaveInfo = { fileName: '', isFolder: false };
}
}
} catch (e) {
console.error('qilin-strm转存助手解析响应出错:', e);
}
});
originalSend.apply(this, arguments);
};
}
// 夸克网盘处理逻辑
function initQuark() {
const originalOpen = XMLHttpRequest.prototype.open;
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
this._url = url;
originalOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function (body) {
// 拦截转存请求,获取文件信息
if (this._url.includes('drive-pc.quark.cn/1/clouddrive/share') && body) {
try {
console.log('检测到夸克网盘转存请求');
console.log('请求体类型:', typeof body);
console.log('请求体:', body);
// 尝试从请求体中提取文件信息
if (typeof body === 'string') {
try {
// 尝试解析JSON
const jsonData = JSON.parse(body);
console.log('解析夸克网盘转存请求:', jsonData);
// 根据夸克网盘API格式提取文件信息
if (jsonData.items && jsonData.items.length > 0) {
const firstItem = jsonData.items[0];
quarkSaveInfo.fileName = firstItem.name || '';
quarkSaveInfo.isFolder = firstItem.type === 'folder';
console.log('夸克网盘转存信息:', quarkSaveInfo);
}
} catch (e) {
console.log('请求体不是JSON格式:', e.message);
}
}
} catch (e) {
console.error('解析夸克网盘转存请求失败:', e);
}
}
this.addEventListener('load', function () {
try {
if (this._url.includes('drive-pc.quark.cn/1/clouddrive/task')) {
const response = JSON.parse(this.responseText);
// status=2 任务成功
if (response.data && response.data.status === 2) {
console.log('检测到夸克网盘转存成功');
// 从页面元素获取保存路径
setTimeout(() => {
let pathElement = document.querySelector('.path-name');
if (pathElement) {
const savepath = pathElement.title.replace('全部文件', '').trim();
console.log('从页面元素获取保存路径:', savepath);
sendWebhook(savepath, 'quark', quarkSaveInfo);
// 重置转存信息
quarkSaveInfo = { fileName: '', isFolder: false };
} else {
console.log('未找到路径元素,无法获取保存路径');
}
}, 500);
}
}
} catch (e) {
console.error('qilin-strm转存助手解析响应出错:', e);
}
});
originalSend.apply(this, arguments);
};
}
// 115网盘处理逻辑
function init115() {
console.log('初始化115网盘处理逻辑(DOM监控模式)');
let lastSendTime = 0; // 上次发送webhook的时间
const debounceTime = 1000; // 防抖时间:1秒
// 使用MutationObserver监控DOM变化
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
// 检查是否有新的节点添加
if (mutation.type === 'childList') {
// 检查页面中是否出现转存成功的元素
const h3Elements = document.querySelectorAll('h3');
let successElement = null;
for (const h3 of h3Elements) {
if (h3.textContent.trim() === '转存成功') {
successElement = h3;
break;
}
}
if (successElement) {
const currentTime = Date.now();
// 检查是否在防抖时间内已经发送过
if (currentTime - lastSendTime < debounceTime) {
console.log('⏱️ 在防抖时间内,跳过此次webhook发送');
return;
}
console.log('✅ 检测到115网盘转存成功(DOM监控)');
console.log('转存成功元素:', successElement);
// 更新上次发送时间
lastSendTime = currentTime;
// 尝试获取保存目录ID
try {
// 尝试新的存储格式
let savepath = '';
let found = false;
// 遍历localStorage,查找以file_picker_last_save_path_开头的键
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key && key.startsWith('file_picker_last_save_path_')) {
console.log('找到新的存储键:', key);
const value = localStorage.getItem(key);
console.log('存储值:', value);
try {
const dirInfo = JSON.parse(value);
if (dirInfo.name) {
savepath = dirInfo.name;
console.log('从新格式获取到保存路径:', savepath);
found = true;
break;
}
} catch (e) {
console.error('解析新格式失败:', e);
}
}
}
if (found) {
// 找到新格式,直接发送webhook
console.log('使用新格式的保存路径:', savepath);
sendWebhook(savepath, 'open115');
} else {
console.error('未找到保存目录信息');
}
} catch (e) {
console.error('获取保存目录失败:', e);
}
// 停止观察,避免重复触发
observer.disconnect();
}
}
});
});
// 开始观察整个文档的变化
observer.observe(document.body, {
childList: true,
subtree: true
});
console.log('115网盘DOM监控已启动');
}
// 发送webhook通知
function sendWebhook(savepath, driver, fileInfo) {
const currentWebhook = GM_getValue('webhook', '');
const currentDelay = parseInt(GM_getValue('delay', '0'), 10);
const currentTianyiMountPath = GM_getValue('tianyi_mountpath', '');
const currentQuarkMountPath = GM_getValue('quark_mountpath', '');
const current115MountPath = GM_getValue('115_mountpath', '');
if (isNaN(currentDelay)) currentDelay = 0;
if (!currentWebhook) {
console.error('Webhook地址未设置');
return;
}
// 构建请求数据(不使用event和data,直接平铺)
let requestData = {
driver: driver,
delay: currentDelay
};
// 处理天翼云盘特殊逻辑(始终发送openlist_path)
if (driver === 'cloud189' && fileInfo && fileInfo.fileName) {
// 获取挂载路径,如果没有设置则为空
let mountPath = currentTianyiMountPath || '';
// 拼接 openlist_path
let openlistPath = '';
if (mountPath) {
// 如果设置了挂载路径,使用挂载路径
openlistPath = mountPath;
// 确保路径格式正确
if (openlistPath.endsWith('/')) {
openlistPath = openlistPath.slice(0, -1);
}
if (savepath && !savepath.startsWith('/')) {
savepath = '/' + savepath;
}
if (fileInfo.isFolder) {
// 如果是文件夹
openlistPath = openlistPath + savepath + '/' + fileInfo.fileName;
} else {
// 如果是文件,拼接路径到文件所在目录
openlistPath = openlistPath + savepath;
}
} else {
// 如果没有设置挂载路径,直接使用 savepath + filename
if (savepath && !savepath.startsWith('/')) {
savepath = '/' + savepath;
}
if (fileInfo.isFolder) {
openlistPath = savepath + '/' + fileInfo.fileName;
} else {
openlistPath = savepath;
}
}
requestData.openlist_path = openlistPath;
console.log('生成的openlist_path:', openlistPath);
} else if (driver === 'quark') {
// 处理夸克网盘特殊逻辑(始终发送openlist_path)
// 获取挂载路径,如果没有设置则为空
let mountPath = currentQuarkMountPath || '';
// 拼接 openlist_path
let openlistPath = '';
if (mountPath) {
// 如果设置了挂载路径,使用挂载路径
openlistPath = mountPath;
// 确保路径格式正确
if (openlistPath.endsWith('/')) {
openlistPath = openlistPath.slice(0, -1);
}
if (savepath && !savepath.startsWith('/')) {
savepath = '/' + savepath;
}
if (fileInfo && fileInfo.fileName && fileInfo.isFolder) {
// 如果有文件信息且是文件夹
openlistPath = openlistPath + savepath + '/' + fileInfo.fileName;
} else {
// 如果没有文件信息或不是文件夹,直接使用保存路径
openlistPath = openlistPath + savepath;
}
} else {
// 如果没有设置挂载路径
if (savepath && !savepath.startsWith('/')) {
savepath = '/' + savepath;
}
if (fileInfo && fileInfo.fileName && fileInfo.isFolder) {
// 如果有文件信息且是文件夹
openlistPath = savepath + '/' + fileInfo.fileName;
} else {
// 如果没有文件信息或不是文件夹,直接使用保存路径
openlistPath = savepath;
}
}
requestData.openlist_path = openlistPath;
console.log('生成的openlist_path:', openlistPath);
} else if (driver === 'open115') {
// 115网盘,使用挂载路径和openlist_path
driver = '115'; // 修正driver名称
requestData.driver = driver;
// 获取挂载路径,如果没有设置则为空
let mountPath = current115MountPath || '';
// 拼接 openlist_path
let openlistPath = '';
if (mountPath) {
// 如果设置了挂载路径,使用挂载路径
openlistPath = mountPath;
// 确保路径格式正确
if (openlistPath.endsWith('/')) {
openlistPath = openlistPath.slice(0, -1);
}
if (savepath && !savepath.startsWith('/')) {
savepath = '/' + savepath;
}
openlistPath = openlistPath + savepath;
} else {
// 如果没有设置挂载路径,直接使用保存路径
if (savepath && !savepath.startsWith('/')) {
savepath = '/' + savepath;
}
openlistPath = savepath;
}
requestData.openlist_path = openlistPath;
console.log('生成的openlist_path:', openlistPath);
// 如果有文件信息,添加到请求中
if (fileInfo && fileInfo.fileName) {
requestData.filename = fileInfo.fileName;
requestData.is_folder = fileInfo.isFolder;
console.log('发送文件信息:', fileInfo);
} else {
console.log('没有文件信息,只发送路径:', savepath);
}
} else {
// 其他情况,使用原路径
requestData.savepath = savepath;
console.log('没有文件信息,只发送路径:', savepath);
}
GM_xmlhttpRequest({
method: 'POST',
url: currentWebhook,
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(requestData),
onload: function (response) {
const data = JSON.parse(response.responseText);
console.log('Webhook响应:', data);
if (data.success) {
console.log('Webhook发送成功:', data.message);
let message = `Webhook发送成功: ${data.message || ''}`;
// 检查是否有task信息
if (data.task) {
message += ` [${data.task.name}] ${data.task.storage_path}`;
}
showToast(message, 'success');
} else {
console.error('Webhook发送成功,但触发失败:', data.message);
showToast(`Webhook发送成功,但触发失败: ${data.message || ''}`, 'error');
}
},
onerror: function (error) {
console.error('Webhook发送失败:', error);
showToast(`Webhook发送失败: ${error.responseText || error.statusText || '未知错误'}`, 'error');
}
});
}
})();