Add torrents to Deluge via Web API

Add torrents to Deluge via Web API (requires patched deluge-web and ViolentMonkey)

当前为 2020-03-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @description Add torrents to Deluge via Web API (requires patched deluge-web and ViolentMonkey)
  3. // @grant GM.xmlHttpRequest
  4. // @homepageURL https://github.com/lalbornoz/AddTorrentsDelugeTransmission
  5. // @include *
  6. // @license MIT
  7. // @name Add torrents to Deluge via Web API
  8. // @namespace https://greasyfork.org/users/467795
  9. // @supportURL https://github.com/lalbornoz/AddTorrentsDelugeTransmission
  10. // @version 1.6
  11. // ==/UserScript==
  12.  
  13. /*
  14. * Tunables
  15. */
  16. let debug = false;
  17. let delugeDownloadDir = {
  18. "": "/var/lib/deluge/downloads"
  19. };
  20. let delugeHostId = "";
  21. let delugeHttpAuthPassword = ""; // (optional)
  22. let delugeHttpAuthUsername = ""; // (optional)
  23. let delugeTorrentDirectory = "/var/lib/deluge/torrents";
  24. let delugeWebPassword = "";
  25. let delugeWebUrl = "protocol://hostname[:port]/deluge";
  26. let linkOpacity = 0.5;
  27.  
  28. // {{{ Module variables
  29. let delugeRequestId = 0;
  30. // }}}
  31.  
  32. // {{{ function basename(url)
  33. function basename(url) {
  34. let url_ = url.split("/");
  35. return url_[url_.length - 1];
  36. };
  37. // }}}
  38. // {{{ function delugeWebRequest(method, onLoadCb, params)
  39. function delugeWebRequest(method, onLoadCb, params) {
  40. let headers = {"Content-type": "application/json"};
  41. let paramsJson = JSON.stringify(params);
  42. let xhrParams = {
  43. anonymous: false,
  44. data: '\{"method":"' + method + '","params":' + paramsJson + ',"id":' + (delugeRequestId++) + '\}',
  45. headers: headers,
  46. method: "POST",
  47. onload: function (xhr) {
  48. let response = null;
  49. try {
  50. response = JSON.parse(xhr.responseText);
  51. }
  52. catch (error) {
  53. logError("Error parsing response from server as JSON: "
  54. + xhr.responseText);
  55. };
  56. if (response.error === null) {
  57. logDebug("Asynchronous `" + method
  58. + "' Web API request succeeded w/ response="
  59. + JSON.stringify(response));
  60. } else {
  61. logError("Asynchronous `" + method
  62. + "' Web API request failed: " + response.error.message
  63. + " (code " + response.error.code.toString() + ")");
  64. };
  65. onLoadCb(response, xhr);
  66. },
  67. synchronous: false,
  68. url: delugeWebUrl + "/json"
  69. };
  70. if ((delugeHttpAuthPassword !== "")
  71. && (delugeHttpAuthUsername !== "")) {
  72. xhrParams["password"] = delugeHttpAuthPassword;
  73. xhrParams["user"] = delugeHttpAuthUsername;
  74. };
  75. logDebug("POSTing asynchronous `" + method + "' Web API request to " + xhrParams["url"]
  76. + " (JSON-encoded parameters: " + paramsJson + ")");
  77. GM.xmlHttpRequest(xhrParams);
  78. };
  79. // }}}
  80. // {{{ function isMagnetLink(url)
  81. function isMagnetLink(url) {
  82. if (url.match(/^magnet:/i)) {
  83. return true;
  84. } else {
  85. return false;
  86. };
  87. };
  88. // }}}
  89. // {{{ function isTorrentLink(url)
  90. function isTorrentLink(url) {
  91. if (url.match(/\.torrent(\?.*|)$/i)) {
  92. return true;
  93. } else {
  94. return false;
  95. };
  96. };
  97. // }}}
  98. // {{{ function JavaScriptIsFuckingGarbage(SodOff)
  99. function JavaScriptIsFuckingGarbage(SodOff) {
  100. return decodeURI(SodOff).replace(/\+/g, " ");
  101. };
  102. // }}}
  103. // {{{ function JavaScriptIsFuckingRubbish()
  104. function JavaScriptIsFuckingRubbish() {
  105. if(window.Prototype) {
  106. logDebug("Prototype.js detected, deleting possibly broken {Object,Array,Hash,String}.prototype.toJSON() functions");
  107. delete Object.prototype.toJSON;
  108. delete Array.prototype.toJSON;
  109. delete Hash.prototype.toJSON;
  110. delete String.prototype.toJSON;
  111. };
  112. };
  113. // }}}
  114. // {{{ function JavaScriptIsFuckingWorthless(FuckYou)
  115. function JavaScriptIsFuckingWorthless(FuckYou) {
  116. return btoa(new Uint8Array(FuckYou).reduce(
  117. function(data, byte) {
  118. return data + String.fromCharCode(byte);
  119. }, ""));
  120. };
  121. // }}}
  122. // {{{ function logDebug(msg)
  123. function logDebug(msg) {
  124. if (debug) {
  125. console.log("[Deluge] " + msg);
  126. };
  127. };
  128. // }}}
  129. // {{{ function logError(msg)
  130. function logError(msg) {
  131. logDebug(msg);
  132. alert("[Deluge] " + msg);
  133. };
  134. // }}}
  135. // {{{ function logInfo(msg)
  136. function logInfo(msg) {
  137. logDebug(msg);
  138. alert("[Deluge] " + msg);
  139. };
  140. // }}}
  141. // {{{ function matchHostDict(dict, host)
  142. function matchHostDict(dict, host) {
  143. let hostDomain = host.split(".").slice(-2);
  144. if (host in dict) {
  145. return dict[host];
  146. } else if (hostDomain in dict) {
  147. return dict[hostDomain];
  148. } else {
  149. return dict[""];
  150. };
  151. };
  152. // }}}
  153.  
  154. // {{{ function cbClickMagnet(e)
  155. function cbClickMagnet(e) {
  156. let torrentUrl = this.href;
  157. if (e.ctrlKey) {
  158. logDebug("Ignoring " + torrentUrl + " due to <Ctrl> modifier.");
  159. } else {
  160. e.stopPropagation(); e.preventDefault();
  161. let torrentName = torrentUrl.match(/dn=([^&]+)/);
  162. if (torrentName === null) {
  163. logError("Invalid Magnet URI (missing Display Name)");
  164. } else {
  165. torrentName = JavaScriptIsFuckingGarbage(torrentName[1]);
  166. delugeWebRequest("auth.login",
  167. function (response, xhr_) {
  168. cbWebLoginResponse(response, null, delugeDownloadDir[""],
  169. torrentName, torrentUrl, null, xhr_);
  170. }, [delugeWebPassword]);
  171. };
  172. };
  173. };
  174. // }}}
  175. // {{{ function cbClickTorrent(e)
  176. function cbClickTorrent(e) {
  177. let torrentUrl = this.href;
  178. if (e.ctrlKey) {
  179. logDebug("Ignoring " + torrentUrl + " due to <Ctrl> modifier.");
  180. } else {
  181. e.stopPropagation(); e.preventDefault();
  182. let torrentUrlHost = torrentUrl.match(new RegExp("^[^:]+://(?:[^:]+:[^@]+@)?([^/:]+)"));
  183. if (torrentUrlHost === null) {
  184. logDebug("Failed to obtain hostname from BitTorrent URL " + torrentUrl);
  185. } else {
  186. torrentUrlHost = torrentUrlHost[1];
  187. let torrentDownloadDir = "";
  188. if ((torrentDownloadDir = matchHostDict(delugeDownloadDir, torrentUrlHost)) === null) {
  189. torrentDownloadDir = delugeDownloadDir[""];
  190. };
  191. logDebug("Sending asynchronous GET request for " + torrentUrl);
  192. GM.xmlHttpRequest({
  193. method: "GET",
  194. onreadystatechange: function (xhr) {
  195. cbClickResponse(xhr.response, torrentDownloadDir,
  196. basename(torrentUrl), torrentUrl,
  197. torrentUrlHost, xhr);
  198. },
  199. responseType: "arraybuffer",
  200. synchronous: false,
  201. url: torrentUrl
  202. });
  203. };
  204. };
  205. };
  206. // }}}
  207. // {{{ function cbClickResponse(torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr)
  208. function cbClickResponse(torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) {
  209. logDebug("Asynchronous GET request for " + torrentUrl
  210. + " readyState=" + xhr.readyState + " status=" + xhr.status);
  211. if (xhr.readyState === 4) {
  212. if (xhr.status === 200) {
  213. delugeWebRequest("auth.login",
  214. function (response, xhr_) {
  215. cbWebLoginResponse(response, torrent, torrentDownloadDir,
  216. torrentName, torrentUrl, torrentUrlHost, xhr_);
  217. }, [delugeWebPassword]);
  218. } else {
  219. logDebug("Asynchronous GET request for " + torrentUrl
  220. + " failed w/ status=" + xhr.status);
  221. };
  222. };
  223. };
  224. // }}}
  225. // {{{ function cbWebLoginResponse(response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr)
  226. function cbWebLoginResponse(response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) {
  227. if (response.error === null) {
  228. delugeWebRequest("web.connect",
  229. function (response_, xhr_) {
  230. cbWebConnectResponse(response_, torrent, torrentDownloadDir,
  231. torrentName, torrentUrl, xhr_);
  232. }, [delugeHostId]);
  233. };
  234. };
  235. // }}}
  236. // {{{ function cbWebConnectResponse(response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr)
  237. function cbWebConnectResponse(response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) {
  238. if (response.error === null) {
  239. delugeWebRequest("web.get_config",
  240. function (response_, xhr_) {
  241. cbWebGetConfigResponse(response_, torrent, torrentDownloadDir,
  242. torrentName, torrentUrl, xhr_);
  243. }, []);
  244. };
  245. };
  246. // }}}
  247. // {{{ function cbWebGetConfigResponse(response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr)
  248. function cbWebGetConfigResponse(response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) {
  249. if (response.error === null) {
  250. let params = [{options: {"download_location": torrentDownloadDir}}];
  251. if (isMagnetLink(torrentUrl)) {
  252. params[0]["path"] = torrentUrl;
  253. } else {
  254. params[0]["data"] = JavaScriptIsFuckingWorthless(torrent);
  255. params[0]["path"] = delugeTorrentDirectory + "/" + torrentName;
  256. };
  257. delugeWebRequest("web.add_torrents",
  258. function (response_, xhr_) {
  259. cbWebAddTorrentsResponse(response_, torrent, torrentDownloadDir,
  260. torrentName, torrentUrl, torrentUrlHost, xhr_);
  261. }, [params]);
  262. };
  263. };
  264. // }}}
  265. // {{{ function cbWebAddTorrentsResponse(response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr)
  266. function cbWebAddTorrentsResponse(response, torrent, torrentDownloadDir, torrentName, torrentUrl, torrentUrlHost, xhr) {
  267. if (response.error === null) {
  268. logInfo("Torrent `" + torrentName + "' added successfully.");
  269. };
  270. };
  271. // }}}
  272.  
  273. function main() {
  274. logDebug("Entry point");
  275. JavaScriptIsFuckingRubbish();
  276. for (let link of document.links) {
  277. if (isMagnetLink(link.href)) {
  278. link.addEventListener("click", cbClickMagnet, true);
  279. link.style.opacity = linkOpacity;
  280. logDebug("Registered Magnet link " + link.href);
  281. } else if (isTorrentLink(link.href)) {
  282. link.addEventListener("click", cbClickTorrent, true);
  283. link.style.opacity = linkOpacity;
  284. logDebug("Registered BitTorrent link " + link.href);
  285. };
  286. };
  287. };
  288.  
  289. main();
  290.  
  291. // vim:expandtab fileformat=dos sw=2 ts=2