您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add torrents to Deluge via Web API (requires patched deluge-web and ViolentMonkey)
// ==UserScript== // @description Add torrents to Deluge via Web API (requires patched deluge-web and ViolentMonkey) // @grant GM.xmlHttpRequest // @homepageURL https://github.com/lalbornoz/AddTorrentsDelugeTransmission // @include * // @license MIT // @name Add torrents to Deluge via Web API // @namespace https://greasyfork.org/users/467795 // @supportURL https://github.com/lalbornoz/AddTorrentsDelugeTransmission // @version 1.8 // ==/UserScript== /* * Tunables */ let debug = false; let delugeDownloadDir = { "": "/var/lib/deluge/downloads" }; let delugeHostId = ""; let delugeHttpAuthPassword = ""; // (optional) let delugeHttpAuthUsername = ""; // (optional) let delugeTorrentDirectory = "/var/lib/deluge/torrents"; let delugeWebPassword = ""; let delugeWebUrl = "protocol://hostname[:port]/deluge"; // {{{ Images let images = { "progress0": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAABmSURBVFhH7ZbBCcBACAQ1dVm+fSXxMJCQK2BXdl6KHwcV9PPGBrBE3L1TTmoWR8f0jBH5rVaNiYWn7+p5K5KZnX2JCKjaW0Q3goZE0JAIGhJBQyJojBGZ+zQyUiK6ETTWanVMjNkFC/lGCoFaJLkAAAAASUVORK5CYII=', "progress1": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAB2SURBVFhH7ZbBDYAwCEXBVVyDFXTYugJrOItKg4mNng2Q/0789MILkJSPCypAF2FmjzmxWUxep6eMyGu1bExZuPu2nj9F5rZ4GtnXjVTV04iI/P72FMGNRAMi0YBINCASDYhEo4xI3U9jRkwENxKNvlpeJ4boBO/6Rgr3rj+kAAAAAElFTkSuQmCC', "progress2": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAB3SURBVFhH7ZbRCYBQCEW1VVrDFWrYWsE1mqXyYdCj6C+4iudL8ceDCvJ+QgloIszsaUxsFoPH4Ukj8lgtG1MUrr6t51eRcZk869nm9bOmqp71iMgvtbtI3QgaJYJGiaBRImiUCBppRPI+jRExkboRNNpqeRwYogPUCkYKdeMSDAAAAABJRU5ErkJggg==', "progress3": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAB3SURBVFhH7ZaxDYBACEXBVVyDFXRYXYE1nEXlgokXra76EF4FoeEFSODzhhLQRJjZ05jYLCaPw5NG5LNaNqYoPH1bz78i87Z41nOs+3BNVT3rEZHh2lukbgSNEkGjRNAoETRKBI00InmfxoiYSN0IGm21PA4M0QW4GkYKEt+3hwAAAABJRU5ErkJggg==', "progress4": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAB3SURBVFhH7ZbRCYBQCEW1VVrDFWrYWsE1mqXyYdCj6C+4iudL8ceDCvJ+QgloIszsaUxsFoPH4Ukj8lgtG1MUrr6t51eRcZk869nm9ZeaqnrWIyKftbtI3QgaJYJGiaBRImiUCBppRPI+jRExkboRNNpqeRwYogOcKkYK3xiWdQAAAABJRU5ErkJggg==', "progress5": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAAB0SURBVFhH7ZbBDYBACATBVmyDFrRYbYE2rEXlgg+ibwNk58XmPkyA5Pi8oQYMEWb2WBObxeR1edqIvFbLxlSFp2/r+VNk3hZPkWPdf39TVU8REQkiuJFsQCQbEMkGRLIBkWy0Een7aayIieBGsjFWy+vCEF2AOkYKkHxZgwAAAABJRU5ErkJggg==', "progress6": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsIAAA7CARUoSoAAAABrSURBVFhH7ZbBDYBACATBVqxHi9V6rEXlwiVezgJ2yc4LwocJkOD3ixWgibh7ppzELJaM6SkjMq1WjImF3nf0/CuyHltmI9d+QtW+IroRNCSChkTQkAgaEkGjjEjdp5GRENGNoNFWK2NizB5kSkYKKMPFzAAAAABJRU5ErkJggg==', "progresserror0": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAABsSURBVFhH7daxDcAgDERROyOkT8n+A6WkzwoJRk4RkQHurHsVdP4CJPwerIAZ4u655RRnseWaXpmQ5WrFMbF4546Zf0Ouo+UO197PT4jeCBqFoFEIGoWgUQiaMiF1P42MIkRvBM28WrkmZvYAO4w3Clt9ChsAAAAASUVORK5CYII=', "progresserror1": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAB3SURBVFhH7ZaxDYBACEXBEewt3YLx2cLS3hVULlziRWvzIbyKn2t4AZLj84YS0ESY2WNMbBaT1+FJI/JaLRtTFHrf1vOnyLGsnkbmfSNV9TQiIr+/PUXqRtAoETRKBI0SQaNE0EgjkvfTGBETqRtBo62W14EhugDxSkYK6vuriAAAAABJRU5ErkJggg==', "progresserror2": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAB4SURBVFhH7ZYxCoBQCIa1I7Q3dguP7y0a27tC5cOgR9EWqPzfpLj4oYK8n1ABmggze5oTm8XgcXrKiDxWy8aUhatv6/lVZJtmz3rGdfmsqapnPSLyS+0ughuJBkSiAZFoQCQaEIlGGZG6T2NGTAQ3Eo22Wh4nhugA1qpGCkK2tScAAAAASUVORK5CYII=', "progresserror3": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAB3SURBVFhH7ZaxDYBACEXBEewt3YLx2cLS3hVULph40eoqIP9VEBpegAQ+b6gATYSZPc2JzWLyOD1lRD6rZWPKwtO39fwrciyrZz3zvg3XVNWzHhEZrr1FcCPRgEg0IBINiEQDItEoI1L3acyIieBGotFWy+PEEF28CkYKuDE5FAAAAABJRU5ErkJggg==', "progresserror4": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAB4SURBVFhH7ZYxCoBQCIa1I7Q3dguP7y0a27tC5cOgR9EWqPzfpLj4oYK8n1ABmggze5oTm8XgcXrKiDxWy8aUhatv6/lVZJtmz3rGdfmlpqqe9YjIZ+0ughuJBkSiAZFoQCQaEIlGGZG6T2NGTAQ3Eo22Wh4nhugAoWpGCo2Q1WAAAAAASUVORK5CYII=', "progresserror5": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAB1SURBVFhH7ZaxDYBACEXBEewt3YLx2cLS3hVULlgQrQ2Q/yp+ruEFSI7PG2rAEGFmjzWxWUxel6eNyGu1bExVePq2nj9FjmX1FJn37fc3VfUUEZEgghvJBkSyAZFsQCQbEMlGG5G+n8aKmAhuJBtjtbwuDNEFhspGCibKMf8AAAAASUVORK5CYII=', "progresserror6": 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAWCAYAAACCAs+RAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAABtSURBVFhH7ZYxDoBACATBJ9hb+v8HWdr7BZULl3g5H7BLdioIDRMgwe8XK0ATcfdMOYlZLBnTU0ZkWq0YEwu97+j5V+Ta9sxG1vOAqn1FdCNoSAQNiaAhETQkgkYZkbpPIyMhohtBo61WxsSYPWwqRgrWtBo1AAAAAElFTkSuQmCC', }; // }}} // {{{ Module variables let delugeRequestId = 0; // }}} // {{{ function basename(url) function basename(url) { let url_ = url.split("/"); return url_[url_.length - 1]; }; // }}} // {{{ function btoa2(data) function btoa2(data) { return btoa(new Uint8Array(data).reduce( function(data, byte) { return data + String.fromCharCode(byte); }, "")); }; // }}} // {{{ function decodeURI2(uri) function decodeURI2(uri) { return decodeURI(uri).replace(/\+/g, " "); }; // }}} // {{{ function deletePrototypeFunctions() function deletePrototypeFunctions() { if(window.Prototype) { logDebug("Prototype.js detected, deleting possibly broken {Object,Array,Hash,String}.prototype.toJSON() functions"); delete Object.prototype.toJSON; delete Array.prototype.toJSON; delete Hash.prototype.toJSON; delete String.prototype.toJSON; }; }; // }}} // {{{ function isMagnetLink(url) function isMagnetLink(url) { if (url.match(/^magnet:/i)) { return true; } else { return false; }; }; // }}} // {{{ function isTorrentLink(url) function isTorrentLink(url) { if (url.match(/\.torrent(\?.*|)$/i)) { return true; } else { return false; }; }; // }}} // {{{ function matchHostDict(dict, host) function matchHostDict(dict, host) { let hostDomain = host.split(".").slice(-2); if (host in dict) { return dict[host]; } else if (hostDomain in dict) { return dict[hostDomain]; } else { return dict[""]; }; }; // }}} // {{{ function delugeWebRequest(method, onLoadCb, params) function delugeWebRequest(method, onLoadCb, params) { let headers = {"Content-type": "application/json"}; let paramsJson = JSON.stringify(params); let xhrParams = { anonymous: false, data: '\{"method":"' + method + '","params":' + paramsJson + ',"id":' + (delugeRequestId++) + '\}', headers: headers, method: "POST", onload: function (xhr) { let response = null; try { response = JSON.parse(xhr.responseText); } catch (error) { logError("Error parsing response from server as JSON: " + xhr.responseText); }; if (response.error === null) { logDebug("Asynchronous `" + method + "' Web API request succeeded w/ response=" + JSON.stringify(response)); } else { logDebug("Asynchronous `" + method + "' Web API request failed: " + response.error.message + " (code " + response.error.code.toString() + ")"); }; onLoadCb(response, xhr); }, synchronous: false, url: delugeWebUrl + "/json" }; if ((delugeHttpAuthPassword !== "") && (delugeHttpAuthUsername !== "")) { xhrParams["password"] = delugeHttpAuthPassword; xhrParams["user"] = delugeHttpAuthUsername; }; logDebug("POSTing asynchronous `" + method + "' Web API request to " + xhrParams["url"] + " (JSON-encoded parameters: " + paramsJson + ")"); GM.xmlHttpRequest(xhrParams); }; // }}} // {{{ function logDebug(msg) function logDebug(msg) { if (debug) { console.log("[Deluge] " + msg); }; }; // }}} // {{{ function logError(msg) function logError(msg) { logDebug(msg); alert("[Deluge] " + msg); }; // }}} // {{{ function logInfo(msg) function logInfo(msg) { logDebug(msg); alert("[Deluge] " + msg); }; // }}} // {{{ function registerLink(cb, link, linkImage, linkNewId) function registerLink(cb, link, linkImage, linkNewId) { let linkNew = document.createElement("a"); linkNew.innerHTML = '<img src="' + linkImage + '" style="border: 0px" />'; linkNew.setAttribute("href", link.href); linkNew.setAttribute("id", linkNewId); linkNew.style.paddingLeft = "2px"; link.parentNode.insertBefore(linkNew, link.nextSibling); linkNew.addEventListener("click", cb, true); }; // }}} // {{{ function setLinkState(link, linkState, msg, error=false) function setLinkState(link, linkState, msg, error=false) { if (error === false) { link.title = msg; link.src = images["progress" + linkState.toString()]; logDebug(msg); } else { link.title = msg; link.src = images["progresserror" + linkState.toString()]; logError(msg); }; }; // }}} // {{{ function cbClickMagnet(e) function cbClickMagnet(e) { let torrentUrl = this.href, torrentName_ = torrentUrl.match(/dn=([^&]+)/); e.stopPropagation(); e.preventDefault(); if (torrentName_ === null) { setLinkState(e.target, 0, "Invalid Magnet URI (missing Display Name)", true); } else { torrentName = decodeURI2(torrentName_[1]); setLinkState(e.target, 2, "Logging into Deluge Web server..."); delugeWebRequest("auth.login", function (response, xhr_) { cbWebLoginResponse(e.target, response, null, delugeDownloadDir[""], torrentName, torrentUrl, null, xhr_); }, [delugeWebPassword]); }; }; // }}} // {{{ function cbClickTorrent(e) function cbClickTorrent(e) { let torrentUrl = this.href, torrentUrlHost_ = torrentUrl.match(new RegExp("^[^:]+://(?:[^:]+:[^@]+@)?([^/:]+)")); e.stopPropagation(); e.preventDefault(); if (torrentUrlHost_ === null) { setLinkState(e.target, 0, "Failed to obtain hostname from BitTorrent URL", true); } else { let torrentDownloadDir = "", torrentName = basename(torrentUrl), torrentUrlHost = torrentUrlHost_[1]; if ((torrentDownloadDir = matchHostDict(delugeDownloadDir, torrentUrlHost)) === null) { torrentDownloadDir = delugeDownloadDir[""]; }; setLinkState(e.target, 1, "Sending asynchronous GET request for " + torrentUrl + "..."); GM.xmlHttpRequest({ method: "GET", onreadystatechange: function (xhr) { cbClickTorrentResponse(e.target, xhr.response, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr); }, responseType: "arraybuffer", synchronous: false, url: torrentUrl }); }; }; // }}} // {{{ function cbClickTorrentResponse(link, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) function cbClickTorrentResponse(link, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) { logDebug("Asynchronous GET request for " + torrentUrl + " readyState=" + xhr.readyState + " status=" + xhr.status); if (xhr.readyState === 4) { if (xhr.status === 200) { setLinkState(link, 2, "Received torrent data, logging into Deluge Web server..."); delugeWebRequest("auth.login", function (response, xhr_) { cbWebLoginResponse(link, response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr_); }, [delugeWebPassword]); } else { setLinkState(link, 2, "Asynchronous GET request for " + torrentUrl + " failed w/ status=" + xhr.status, false); }; }; }; // }}} // {{{ function cbWebLoginResponse(link, response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) function cbWebLoginResponse(link, response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) { if (response.error === null) { setLinkState(link, 3, "Logged into Deluge Web server, sending web.connect request..."); delugeWebRequest("web.connect", function (response_, xhr_) { cbWebConnectResponse(link, response_, torrent, torrentDownloadDir, torrentName, torrentUrl, xhr_); }, [delugeHostId]); } else { setLinkState(link, 3, "web.login request failed: " + response.error.message + " (code=" + response.error.code.toString() + ")", true); }; }; // }}} // {{{ function cbWebConnectResponse(link, response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) function cbWebConnectResponse(link, response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) { if (response.error === null) { setLinkState(link, 4, "Connected to Deluge Web server, sending web.get_config request..."); delugeWebRequest("web.get_config", function (response_, xhr_) { cbWebGetConfigResponse(link, response_, torrent, torrentDownloadDir, torrentName, torrentUrl, xhr_); }, []); } else { setLinkState(link, 4, "web.connect request failed: " + response.error.message + " (code=" + response.error.code.toString() + ")", true); }; }; // }}} // {{{ function cbWebGetConfigResponse(link, response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) function cbWebGetConfigResponse(link, response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) { if (response.error === null) { let params = [{options: {"download_location": torrentDownloadDir}}]; if (isMagnetLink(torrentUrl)) { params[0]["path"] = torrentUrl; } else { params[0]["data"] = btoa2(torrent); params[0]["path"] = delugeTorrentDirectory + "/" + torrentName; }; setLinkState(link, 5, "Received web.get_config response, sending web.add_torrents request..."); delugeWebRequest("web.add_torrents", function (response_, xhr_) { cbWebAddTorrentsResponse(link, response_, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr_); }, [params]); } else { setLinkState(link, 5, "web.get_config request failed: " + response.error.message + " (code=" + response.error.code.toString() + ")", true); }; }; // }}} // {{{ function cbWebAddTorrentsResponse(link, response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) function cbWebAddTorrentsResponse(link, response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) { if (response.error === null) { setLinkState(link, 6, "Successfully added torrent"); logInfo("Torrent `" + torrentName + "' added successfully."); } else { setLinkState(link, 6, "web.add_torrents request failed: " + response.error.message + " (code=" + response.error.code.toString() + ")", true); }; }; // }}} function main() { logDebug("Entry point"); deletePrototypeFunctions(); for (let link of document.links) { if (link.getAttribute("id") === "AddTorrentsDelugeLink") { continue; } else if (isMagnetLink(link.href)) { registerLink(cbClickMagnet, link, images["progress0"], "AddTorrentsDelugeLink"); logDebug("Registered Magnet link " + link.href); } else if (isTorrentLink(link.href)) { registerLink(cbClickTorrent, link, images["progress0"], "AddTorrentsDelugeLink"); logDebug("Registered BitTorrent link " + link.href); }; }; }; main(); // vim:expandtab fileformat=dos sw=2 ts=2