down2transmission

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

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

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