down2transmission

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

当前为 2022-09-27 提交的版本,查看 最新版本

  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.6
  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. if (transmission.download_path === null) {
  174. return {
  175. arguments: { cookies: getCookie(), filename: torrentURL },
  176. method: "torrent-add",
  177. tag: 80,
  178. };
  179. } else {
  180. return {
  181. arguments: { cookies: getCookie(), filename: torrentURL, "download-dir": transmission.download_dir },
  182. method: "torrent-add",
  183. tag: 80,
  184. };
  185. }
  186. }
  187.  
  188. function addButtonForTransmissioin(transmission) {
  189. console.log("Constructed url:" + transmission.rpc_url());
  190.  
  191. var button_id_prefix = "transmission_";
  192.  
  193. if (reCCF.test(site)) {
  194. if (site.includes("browse")) {
  195. // CCF main page
  196. target = $(
  197. "table[border=1][cellpadding=5]>>> td:nth-child(2):not([class])"
  198. );
  199. target.each(function (i) {
  200. var pageURL =
  201. baseURL + "/" + $(this).find("a[title][href]").attr("href");
  202. var button = $("<a>", {
  203. id: "transmission_" + transmission.name + i,
  204. "data-detailurl": pageURL,
  205. text: transmission.name,
  206. "data-type": "ccf-main",
  207. "data-server-name": transmission.name,
  208. });
  209. var resultText = $("<a>", {
  210. id: "transmission_" + transmission.name + i + "_result",
  211. text: "",
  212. style: "padding-left:5px",
  213. "data-type": "ccf-main",
  214. "data-server-name": transmission.name,
  215. });
  216. button.css(buttonCSS);
  217. $(this).append(button);
  218. button.after(resultText);
  219. });
  220. }
  221. if (site.includes("details")) {
  222. // CCF detail page
  223. target = $('a[class="index"][href*=".torrent"]');
  224. var ccfTorrentUrl = baseURL + "/" + target.attr("href");
  225. var ccfDetailInsert = $("<a>", {
  226. id: "transmission_" + transmission.name,
  227. "data-detailurl": ccfTorrentUrl,
  228. text: transmission.name,
  229. "data-type": "ccf-detail",
  230. "data-server-name": transmission.name,
  231. });
  232. ccfDetailInsert.css(buttonCSS);
  233. target.after(ccfDetailInsert);
  234. ccfDetailInsert.after(
  235. $("<a>", {
  236. id: "transmission_" + transmission.name + "_result",
  237. text: "",
  238. style: "padding-left:5px",
  239. "data-type": "ccf-detail",
  240. "data-server-name": transmission.name,
  241. })
  242. );
  243. target.after("<br>");
  244. }
  245. }
  246.  
  247. if (reTTG.test(site)) {
  248. if (site.includes("browse")) {
  249. // TTG main page
  250. target = $("tr[id]> td:nth-child(2)");
  251. target.each(function (i) {
  252. var page = $(this).find("a[href]").attr("href");
  253. var el = $("<a>", {
  254. id: "transmission_" + transmission.name + i,
  255. "data-detailurl": baseURL + page,
  256. text: transmission.name,
  257. "data-type": "ttg-main",
  258. "data-server-name": transmission.name,
  259. });
  260. el.css(buttonCSS);
  261. $(this).append(el);
  262. el.after(
  263. $("<a>", {
  264. id: "transmission_" + transmission.name + i + "_result",
  265. text: "",
  266. style: "padding-left:5px",
  267. "data-type": "ttg-main",
  268. "data-server-name": transmission.name,
  269. })
  270. );
  271. });
  272. }
  273. if (site.includes("/t/")) {
  274. // TTG detail page
  275. target = $('a[class="index"][href*="zip"]');
  276. var ttgTorrentUrl =
  277. baseURL + "/" + $('a[class="index"][href*=".torrent"]').attr("href");
  278. var ttgDetailInsert = $("<a>", {
  279. id: "transmission_" + transmission.name,
  280. "data-detailurl": ttgTorrentUrl,
  281. text: transmission.name,
  282. "data-type": "ttg-detail",
  283. "data-server-name": transmission.name,
  284. });
  285. ttgDetailInsert.css(buttonCSS);
  286. target.after(ttgDetailInsert);
  287. ttgDetailInsert.after(
  288. $("<a>", {
  289. id: "transmission_" + transmission.name + "_result",
  290. text: "",
  291. style: "padding-left:5px",
  292. "data-type": "ttg-detail",
  293. "data-server-name": transmission.name,
  294. })
  295. );
  296. target.after("<br>");
  297. }
  298. }
  299.  
  300. if (rePira.test(site)) {
  301. if (site.includes("/search/")) {
  302. // piratebay main page
  303. target = $("#searchResult> tbody td:nth-child(2)");
  304. target.each(function (i) {
  305. var pageURL =
  306. baseURL + "/" + $(this).find("a[title][href]").attr("href");
  307. var el = $("<a>", {
  308. id: "transmission_" + transmission.name + i,
  309. "data-detailurl": pageURL,
  310. text: transmission.name,
  311. "data-type": "pira-main",
  312. "data-server-name": transmission.name,
  313. });
  314. el.css(buttonCSS);
  315. $(this).append(el);
  316. el.after(
  317. $("<a>", {
  318. id: "transmission_" + transmission.name + i + "_result",
  319. text: "",
  320. style: "padding-left:5px",
  321. "data-type": "pira-main",
  322. "data-server-name": transmission.name,
  323. })
  324. );
  325. el.before("<br>");
  326. });
  327. }
  328. }
  329.  
  330. if (reIpt.test(site)) {
  331. if (site.includes("/torrent.php")) {
  332. // torrent detail page
  333. target = $("td:has(> div.sub)");
  334. target.each(function (i) {
  335. var torrentURL =
  336. baseURL +
  337. $(this).parent().find("a:has(i.fa-download)").parent().attr("href") +
  338. "?torrent_pass=" +
  339. ipt_torrent_pass;
  340. var el = $("<a>", {
  341. id: "transmission_" + transmission.name + i,
  342. "data-detailurl": torrentURL,
  343. text: transmission.name,
  344. "data-type": "ipt-detail",
  345. "data-server-name": transmission.name,
  346. });
  347. el.css(buttonCSS);
  348. $(this).append(el);
  349. el.after(
  350. $("<a>", {
  351. id: "transmission_" + transmission.name + i + "_result",
  352. text: "",
  353. style: "padding-left:5px",
  354. "data-type": "ipt-main",
  355. "data-server-name": transmission.name,
  356. })
  357. );
  358. el.before("<br>");
  359. });
  360. } else if (site.includes("/t")) {
  361. // main page
  362. target = $("td:has(> div.sub)");
  363. target.each(function (i) {
  364. var torrentURL =
  365. baseURL +
  366. $(this).parent().find("a:has(i.fa-download)").attr("href") +
  367. "?torrent_pass=" +
  368. ipt_torrent_pass;
  369. var el = $("<a>", {
  370. id: "transmission_" + transmission.name + i,
  371. "data-detailurl": torrentURL,
  372. text: transmission.name,
  373. "data-type": "ipt-main",
  374. "data-server-name": transmission.name,
  375. });
  376. el.css(buttonCSS);
  377. $(this).append(el);
  378. el.after(
  379. $("<a>", {
  380. id: "transmission_" + transmission.name + i + "_result",
  381. text: "",
  382. style: "padding-left:5px",
  383. "data-type": "ipt-main",
  384. "data-server-name": transmission.name,
  385. })
  386. );
  387. el.before("<br>");
  388. });
  389. }
  390. }
  391. }
  392.  
  393. function addTorrent(transmission, button, result, request, sessionId, tries) {
  394. console.log("adding torrent to:", transmission);
  395. console.log("setting download path");
  396. request.arguments
  397. if (!tries) {
  398. tries = 0;
  399. }
  400. if (tries === 3) {
  401. alert(
  402. "p2transmission: Too many Error 409: Conflict.\nCheck your transmission installation"
  403. );
  404. return;
  405. }
  406. console.log("sending torrent with sessionid: (" + sessionId);
  407. console.log("sending: " + JSON.stringify(request));
  408. GM_xmlhttpRequest({
  409. method: "POST",
  410. url: transmission.rpc_url(),
  411. data: JSON.stringify(request),
  412. headers: {
  413. "X-Transmission-Session-Id": sessionId,
  414. },
  415. onload: function (response) {
  416. console.log(
  417. "Got response:\n" +
  418. [response.status, response.statusText, response.responseText].join(
  419. "\n"
  420. )
  421. );
  422. var resultText;
  423. var success = false;
  424. var unclickable = false;
  425. var error = false;
  426. switch (response.status) {
  427. case 200: // status OK
  428. var rpcResponse = response.responseText;
  429. var rpcJSON = JSON.parse(rpcResponse);
  430. if (rpcJSON.result.toLowerCase() === "success") {
  431. if ("torrent-duplicate" in rpcJSON.arguments) {
  432. resultText =
  433. "Already added: " + rpcJSON.arguments["torrent-duplicate"].name;
  434. } else {
  435. resultText = "Added: " + rpcJSON.arguments["torrent-added"].name;
  436. }
  437. success = true;
  438. } else {
  439. resultText = "ERROR: " + rpcJSON.result;
  440. error = true;
  441. }
  442. unclickable = true;
  443. break;
  444. case 401:
  445. resultText = "Your username/password is not correct.";
  446. error = true;
  447. break;
  448. case 409:
  449. console.log("Setting sessionId");
  450. var headers = response.responseHeaders.split("\n");
  451. console.log(headers.join("; "));
  452. for (var i in headers) {
  453. var header = headers[i].split(":");
  454. if (header[0].toLowerCase() == "x-transmission-session-id") {
  455. sessionId = header[1].trim();
  456. console.log("Got new Session ID: (" + sessionId);
  457. addTorrent(
  458. transmission,
  459. button,
  460. result,
  461. request,
  462. sessionId,
  463. tries + 1
  464. );
  465. }
  466. }
  467. break;
  468. default:
  469. resultText = "Unknown Transmission Response";
  470. error = true;
  471. alert(
  472. "Unknown Transmission Response: " +
  473. response.status +
  474. " " +
  475. response.statusText
  476. );
  477. }
  478. console.log(resultText);
  479. result.text(resultText);
  480. if (unclickable) {
  481. button.unbind("click");
  482. button.css("cursor", "default");
  483. }
  484. if (success) {
  485. button.css("background-color", "#8FFFA6");
  486. }
  487. if (error) {
  488. button.css("background-color", "#FFBAC2");
  489. }
  490. },
  491. });
  492. }
  493.  
  494. function getCookie() {
  495. // from https://github.com/bulljit/Transmission-Add-Torrent-Bookmarkelet Thanks folks.
  496. var sCookie = "";
  497. var aCookie = document.cookie.split(/;[\s\xA0]*/);
  498. if (aCookie !== "") {
  499. for (var i = 0; i < aCookie.length; i++) {
  500. if (aCookie[i].search(/(^__utm|^__qc)/) == -1) {
  501. sCookie = sCookie + aCookie[i] + "; ";
  502. }
  503. }
  504. }
  505. sCookie = sCookie.replace(/;\s+$/, "");
  506. return sCookie;
  507. }
  508.