down2transmission

Add a button in torrent sites to support adding torrent to Transmission directly.

当前为 2023-10-03 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name down2transmission
  3. // @namespace https://github.com/convexshiba/
  4. // @copyright 2017, convexshiba
  5. // @author convexshiba
  6. // @icon https://media.giphy.com/media/cInsPcO4MijtwP1FMS/giphy.gif
  7. // @license https://raw.githubusercontent.com/convexshiba/down2transmission/master/LICENSE
  8. // @version 1.8
  9. // @description Add a button in torrent sites to support adding torrent to Transmission directly.
  10. // @supportURL https://github.com/convexshiba/down2transmission
  11. // @match *://ccfbits.org/*
  12. // @match *://totheglory.im/*
  13. // @match *://thepiratebay.org/*
  14. // @match *://iptorrents.com/*
  15. // @require https://code.jquery.com/jquery-3.2.1.min.js
  16. // @run-at document-end
  17. // @grant GM_xmlhttpRequest
  18. // ==/UserScript==
  19.  
  20. // Edit these before use.
  21. // http://192.168.1.1 for local access, input ddns for external access.
  22. // NO trailing slash(/).
  23.  
  24. // Authentication;
  25. // support multiple server
  26. var transmissions = [
  27. new Transmission(
  28. "Server1",
  29. "http://your.url.com",
  30. "9091",
  31. "/transmission/",
  32. "path_to_download",
  33. "user_name1",
  34. "pw1"
  35. ),
  36. new Transmission(
  37. "Server2",
  38. "http://your.server.com",
  39. "9092",
  40. "/transmission/",
  41. "path_to_download",
  42. "usename2",
  43. "pw2"
  44. ),
  45. ];
  46.  
  47. // Can be found in direct download rss.
  48. var ipt_torrent_pass = "Can be found in direct download rss";
  49.  
  50. // Below is optional
  51.  
  52. // Useful when your transmission is hidden behind an authentication layer/reverse proxy
  53. var custom_header = {
  54. // "CF-Access-Client-Id": "client_id_example",
  55. // "CF-Access-Client-Secret": "client_secret_example"
  56. }
  57.  
  58. // DO NOT EDIT BELOW.
  59.  
  60. // server_name: name of the button
  61. // url: your.server.url.com
  62. // port: port, usually 9091
  63. // rpc_bind_address: usually /transmission/
  64. // download_dir: specify a special download address for this button. Provide null if want to use default path
  65. // username: to your server
  66. // pw: password to your server
  67. function Transmission(server_name, url, port, rpc_bind_address, download_dir, username, pw) {
  68. this.name = server_name;
  69. this.url = url;
  70. this.port = port;
  71. this.rpc_bind_address = rpc_bind_address;
  72. this.username = username;
  73. this.pw = pw;
  74. this.download_dir = download_dir;
  75. this.rpc_url = function () {
  76. return "https://" + this.username + ":" + this.pw + "@" + this.url + ":" + this.port + this.rpc_bind_address + "rpc";
  77. };
  78. }
  79.  
  80. var transmissions_map = transmissions.reduce(function (transmissions_map, obj) {
  81. transmissions_map[obj.name] = obj;
  82. return transmissions_map;
  83. }, {});
  84.  
  85. console.log(transmissions_map);
  86.  
  87. var $ = window.jQuery;
  88.  
  89. var site = window.location.href;
  90. var reCCF = /ccf/i;
  91. var reTTG = /totheglory/i;
  92. var rePira = /thepiratebay.org/i;
  93. var reIpt = /iptorrents.com/i;
  94. var baseURL = document.location.origin;
  95. var target;
  96. var buttonCSS = {
  97. "background-color": "#B6B6B6",
  98. "-moz-border-radius": "2px",
  99. "-webkit-border-radius": "2px",
  100. "border-radius": "5px",
  101. display: "inline-block",
  102. cursor: "pointer",
  103. color: "#000000",
  104. "font-family": "Verdana",
  105. "font-size": "12px",
  106. padding: "3px 5px 3px 3px",
  107. margin: "3px",
  108. "text-decoration": "none",
  109. };
  110.  
  111. (function () {
  112. "use strict";
  113. transmissions.forEach(addButtonForTransmissioin);
  114. $("[id^=transmission]:not([id*=result]").click(function () {
  115. var id = $(this).attr("id");
  116. var type = $(this).data("type");
  117. var resultText = $("#" + id + "_result");
  118. var transmission = transmissions_map[$(this).data("server-name")];
  119. var torrentURL;
  120. resultText.text("Submitting to Transmission...");
  121. console.log(id + " is clicked");
  122. var request;
  123. if (type.includes("ccf-main") || type.includes("ttg-main")) {
  124. console.log("main page");
  125. var torrentPage = $(this).data("detailurl");
  126. GM_xmlhttpRequest({
  127. method: "GET",
  128. url: torrentPage,
  129. onload: function (response) {
  130. console.log("Start fetching torrent details");
  131. if (type.includes("ccf-main")) {
  132. torrentURL =
  133. baseURL +
  134. "/" +
  135. $(response.responseText).find('a[href*=".torrent"]').attr("href");
  136. }
  137.  
  138. if (type.includes("ttg-main")) {
  139. torrentURL = $(response.responseText)
  140. .find("td.heading:contains(种子链接)")
  141. .next()
  142. .children("a:first")
  143. .attr("href");
  144. }
  145. console.log("Extracted torrent url: " + torrentURL);
  146. var request = getRequest(transmission, torrentURL)
  147. console.log("request: " + request);
  148. addTorrent(transmission, $("#" + id), resultText, request);
  149. },
  150. });
  151. }
  152. if (type.includes("ccf-detail") || type.includes("ttg-detail")) {
  153. console.log("detail page");
  154. torrentURL =
  155. baseURL + "/" + $('a[class="index"]:contains("torrent")').attr("href");
  156. request = getRequest(transmission, torrentURL)
  157. addTorrent(transmission, $("#" + id), resultText, request);
  158. }
  159. if (type.includes("pira-main")) {
  160. console.log("pira-main page");
  161. torrentURL = $(this).siblings().filter('a[href^="magnet"]').attr("href");
  162. request = getRequest(transmission, torrentURL)
  163. addTorrent(transmission, $("#" + id), resultText, request);
  164. }
  165. if (type.includes("ipt-main")) {
  166. console.log("ipt-main");
  167. torrentURL = $(this).data("detailurl");
  168. request = getRequest(transmission, torrentURL)
  169. addTorrent(transmission, $("#" + id), resultText, request);
  170. }
  171. if (type.includes("ipt-detail")) {
  172. console.log("ipt-detail");
  173. torrentURL = $(this).data("detailurl");
  174. request = getRequest(transmission, torrentURL)
  175. addTorrent(transmission, $("#" + id), resultText, request);
  176. }
  177. });
  178. })();
  179.  
  180. function getRequest(transmission, torrentURL) {
  181. torrentURL = encodeURI(torrentURL)
  182. if (transmission.download_path === null) {
  183. return {
  184. arguments: { cookies: getCookie(), filename: torrentURL },
  185. method: "torrent-add",
  186. tag: 80,
  187. };
  188. } else {
  189. return {
  190. arguments: { cookies: getCookie(), filename: torrentURL, "download-dir": transmission.download_dir },
  191. method: "torrent-add",
  192. tag: 80,
  193. };
  194. }
  195. }
  196.  
  197. function addButtonForTransmissioin(transmission) {
  198. console.log("Constructed url:" + transmission.rpc_url());
  199.  
  200. var button_id_prefix = "transmission_";
  201.  
  202. if (reCCF.test(site)) {
  203. if (site.includes("browse")) {
  204. // CCF main page
  205. target = $(
  206. "table[border=1][cellpadding=5]>>> td:nth-child(2):not([class])"
  207. );
  208. target.each(function (i) {
  209. var pageURL =
  210. baseURL + "/" + $(this).find("a[title][href]").attr("href");
  211. var button = $("<a>", {
  212. id: "transmission_" + transmission.name + i,
  213. "data-detailurl": pageURL,
  214. text: transmission.name,
  215. "data-type": "ccf-main",
  216. "data-server-name": transmission.name,
  217. });
  218. var resultText = $("<a>", {
  219. id: "transmission_" + transmission.name + i + "_result",
  220. text: "",
  221. style: "padding-left:5px",
  222. "data-type": "ccf-main",
  223. "data-server-name": transmission.name,
  224. });
  225. button.css(buttonCSS);
  226. $(this).append(button);
  227. button.after(resultText);
  228. });
  229. }
  230. if (site.includes("details")) {
  231. // CCF detail page
  232. target = $('a[class="index"][href*=".torrent"]');
  233. var ccfTorrentUrl = baseURL + "/" + target.attr("href");
  234. var ccfDetailInsert = $("<a>", {
  235. id: "transmission_" + transmission.name,
  236. "data-detailurl": ccfTorrentUrl,
  237. text: transmission.name,
  238. "data-type": "ccf-detail",
  239. "data-server-name": transmission.name,
  240. });
  241. ccfDetailInsert.css(buttonCSS);
  242. target.after(ccfDetailInsert);
  243. ccfDetailInsert.after(
  244. $("<a>", {
  245. id: "transmission_" + transmission.name + "_result",
  246. text: "",
  247. style: "padding-left:5px",
  248. "data-type": "ccf-detail",
  249. "data-server-name": transmission.name,
  250. })
  251. );
  252. target.after("<br>");
  253. }
  254. }
  255.  
  256. if (reTTG.test(site)) {
  257. if (site.includes("browse")) {
  258. // TTG main page
  259. target = $("tr[id]> td:nth-child(2)");
  260. target.each(function (i) {
  261. var page = $(this).find("a[href]").attr("href");
  262. var el = $("<a>", {
  263. id: "transmission_" + transmission.name + i,
  264. "data-detailurl": baseURL + page,
  265. text: transmission.name,
  266. "data-type": "ttg-main",
  267. "data-server-name": transmission.name,
  268. });
  269. el.css(buttonCSS);
  270. $(this).append(el);
  271. el.after(
  272. $("<a>", {
  273. id: "transmission_" + transmission.name + i + "_result",
  274. text: "",
  275. style: "padding-left:5px",
  276. "data-type": "ttg-main",
  277. "data-server-name": transmission.name,
  278. })
  279. );
  280. });
  281. }
  282. if (site.includes("/t/")) {
  283. // TTG detail page
  284. target = $('a[class="index"][href*="zip"]');
  285. var ttgTorrentUrl =
  286. baseURL + "/" + $('a[class="index"][href*=".torrent"]').attr("href");
  287. var ttgDetailInsert = $("<a>", {
  288. id: "transmission_" + transmission.name,
  289. "data-detailurl": ttgTorrentUrl,
  290. text: transmission.name,
  291. "data-type": "ttg-detail",
  292. "data-server-name": transmission.name,
  293. });
  294. ttgDetailInsert.css(buttonCSS);
  295. target.after(ttgDetailInsert);
  296. ttgDetailInsert.after(
  297. $("<a>", {
  298. id: "transmission_" + transmission.name + "_result",
  299. text: "",
  300. style: "padding-left:5px",
  301. "data-type": "ttg-detail",
  302. "data-server-name": transmission.name,
  303. })
  304. );
  305. target.after("<br>");
  306. }
  307. }
  308.  
  309. if (rePira.test(site)) {
  310. if (site.includes("/search/")) {
  311. // piratebay main page
  312. target = $("#searchResult> tbody td:nth-child(2)");
  313. target.each(function (i) {
  314. var pageURL =
  315. baseURL + "/" + $(this).find("a[title][href]").attr("href");
  316. var el = $("<a>", {
  317. id: "transmission_" + transmission.name + i,
  318. "data-detailurl": pageURL,
  319. text: transmission.name,
  320. "data-type": "pira-main",
  321. "data-server-name": transmission.name,
  322. });
  323. el.css(buttonCSS);
  324. $(this).append(el);
  325. el.after(
  326. $("<a>", {
  327. id: "transmission_" + transmission.name + i + "_result",
  328. text: "",
  329. style: "padding-left:5px",
  330. "data-type": "pira-main",
  331. "data-server-name": transmission.name,
  332. })
  333. );
  334. el.before("<br>");
  335. });
  336. }
  337. }
  338.  
  339. if (reIpt.test(site)) {
  340. if (site.includes("/torrent.php")) {
  341. // torrent detail page
  342. target = $("td:has(> div.sub)");
  343. target.each(function (i) {
  344. var torrentURL =
  345. baseURL +
  346. $(this).parent().find("a:has(i.fa-download)").parent().attr("href") +
  347. "?torrent_pass=" +
  348. ipt_torrent_pass;
  349. var el = $("<a>", {
  350. id: "transmission_" + transmission.name + i,
  351. "data-detailurl": torrentURL,
  352. text: transmission.name,
  353. "data-type": "ipt-detail",
  354. "data-server-name": transmission.name,
  355. });
  356. el.css(buttonCSS);
  357. $(this).append(el);
  358. el.after(
  359. $("<a>", {
  360. id: "transmission_" + transmission.name + i + "_result",
  361. text: "",
  362. style: "padding-left:5px",
  363. "data-type": "ipt-main",
  364. "data-server-name": transmission.name,
  365. })
  366. );
  367. el.before("<br>");
  368. });
  369. } else if (site.includes("/t")) {
  370. // main page
  371. target = $("td:has(> div.sub)");
  372. target.each(function (i) {
  373. var torrentURL =
  374. baseURL +
  375. $(this).parent().find("a:has(i.fa-download)").attr("href") +
  376. "?torrent_pass=" +
  377. ipt_torrent_pass;
  378. var el = $("<a>", {
  379. id: "transmission_" + transmission.name + i,
  380. "data-detailurl": torrentURL,
  381. text: transmission.name,
  382. "data-type": "ipt-main",
  383. "data-server-name": transmission.name,
  384. });
  385. el.css(buttonCSS);
  386. $(this).append(el);
  387. el.after(
  388. $("<a>", {
  389. id: "transmission_" + transmission.name + i + "_result",
  390. text: "",
  391. style: "padding-left:5px",
  392. "data-type": "ipt-main",
  393. "data-server-name": transmission.name,
  394. })
  395. );
  396. el.before("<br>");
  397. });
  398. }
  399. }
  400. }
  401.  
  402. function addTorrent(transmission, button, result, request, sessionId, tries) {
  403. console.log("adding torrent to:", transmission);
  404. console.log("setting download path");
  405. request.arguments
  406. if (!tries) {
  407. tries = 0;
  408. }
  409. if (tries === 3) {
  410. alert(
  411. "p2transmission: Too many Error 409: Conflict.\nCheck your transmission installation"
  412. );
  413. return;
  414. }
  415. console.log("sending torrent with sessionid: ->" + sessionId);
  416. console.log("sending: " + JSON.stringify(request));
  417. GM_xmlhttpRequest({
  418. method: "POST",
  419. url: transmission.rpc_url(),
  420. data: JSON.stringify(request),
  421. headers: Object.assign({}, custom_header, {
  422. "X-Transmission-Session-Id": sessionId,
  423. }),
  424. onload: function (response) {
  425. console.log(
  426. "Got response:\n" +
  427. [response.status, response.statusText, response.responseText].join(
  428. "\n"
  429. )
  430. );
  431. var resultText;
  432. var success = false;
  433. var unclickable = false;
  434. var error = false;
  435. switch (response.status) {
  436. case 200: // status OK
  437. var rpcResponse = response.responseText;
  438. var rpcJSON = JSON.parse(rpcResponse);
  439. if (rpcJSON.result.toLowerCase() === "success") {
  440. if ("torrent-duplicate" in rpcJSON.arguments) {
  441. resultText =
  442. "Already added: " + rpcJSON.arguments["torrent-duplicate"].name;
  443. } else {
  444. resultText = "Added: " + rpcJSON.arguments["torrent-added"].name;
  445. }
  446. success = true;
  447. } else {
  448. resultText = "ERROR: " + rpcJSON.result;
  449. error = true;
  450. }
  451. unclickable = true;
  452. break;
  453. case 401:
  454. resultText = "Your username/password is not correct.";
  455. error = true;
  456. break;
  457. case 409:
  458. console.log("Setting sessionId");
  459. var headers = response.responseHeaders.split("\n");
  460. console.log(headers.join("; "));
  461. for (var i in headers) {
  462. var header = headers[i].split(":");
  463. if (header[0].toLowerCase() == "x-transmission-session-id") {
  464. sessionId = header[1].trim();
  465. console.log("Got new Session ID: (" + sessionId);
  466. addTorrent(
  467. transmission,
  468. button,
  469. result,
  470. request,
  471. sessionId,
  472. tries + 1
  473. );
  474. }
  475. }
  476. break;
  477. default:
  478. resultText = "Unknown Transmission Response";
  479. error = true;
  480. alert(
  481. "Unknown Transmission Response: " +
  482. response.status +
  483. " " +
  484. response.statusText
  485. );
  486. }
  487. console.log(resultText);
  488. result.text(resultText);
  489. if (unclickable) {
  490. button.unbind("click");
  491. button.css("cursor", "default");
  492. }
  493. if (success) {
  494. button.css("background-color", "#8FFFA6");
  495. }
  496. if (error) {
  497. button.css("background-color", "#FFBAC2");
  498. }
  499. },
  500. });
  501. }
  502.  
  503. function getCookie() {
  504. // from https://github.com/bulljit/Transmission-Add-Torrent-Bookmarkelet Thanks folks.
  505. var sCookie = "";
  506. var aCookie = document.cookie.split(/;[\s\xA0]*/);
  507. if (aCookie !== "") {
  508. for (var i = 0; i < aCookie.length; i++) {
  509. if (aCookie[i].search(/(^__utm|^__qc)/) == -1) {
  510. sCookie = sCookie + aCookie[i] + "; ";
  511. }
  512. }
  513. }
  514. sCookie = sCookie.replace(/;\s+$/, "");
  515. return sCookie;
  516. }
  517.  
  518.