tehConnection Snatch Script

Mark snatched torrents for tc

  1. // ==UserScript==
  2. // @name tehConnection Snatch Script
  3. // @namespace http://tehconnection.eu
  4. // @description Mark snatched torrents for tc
  5. // @author jonls on what.cd (adapted for TC by Dafreak)
  6. // @include http://tehconnection.eu/*
  7. // @include https://tehconnection.eu/*
  8. // @require http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_deleteValue
  12. // @grant GM_xmlhttpRequest
  13. // @grant GM_registerMenuCommand
  14. // @grant GM_addStyle
  15. // @grant GM_log
  16. // @version 0.2
  17. // @date 2010-10-27
  18. // ==/UserScript==
  19.  
  20. (function() {
  21. var DEFAULT_STYLE =
  22. '.group_snatched { color: #E5B244; } '+
  23. '.snatched { color: #35BF00; } '+
  24. '.uploaded { color: red; } '+
  25. '.leeching { color: #2B75A1; }'+
  26. '.seeding { border-bottom: 1px dotted red; }';
  27. var AUTO_UPDATE_INTERVAL = 10*60; /* seconds */
  28.  
  29. /* Throttled proxy */
  30. function Proxy(url_base, delay) {
  31. var last_req = new Date(0);
  32. var queue = [];
  33. var processing = false;
  34.  
  35. return {
  36. get: function(req) {
  37. var now = new Date();
  38. queue.push(req);
  39. if (!processing) {
  40. /* Race condition: atomic test and set would be appropriate here, to ensure thread safety (is it a problem?) */
  41. processing = true;
  42. var diff = last_req.getTime() + delay - now.getTime();
  43. if (diff > 0) {
  44. var that = this;
  45. window.setTimeout(function() { that.process_queue(); }, diff);
  46. } else {
  47. this.process_queue();
  48. }
  49. }
  50. },
  51.  
  52. process_queue: function() {
  53. var req = queue.shift();
  54. this.do_request(req);
  55. processing = (queue.length > 0);
  56. if (processing) {
  57. var that = this;
  58. window.setTimeout(function() { that.process_queue(); }, delay);
  59. }
  60. },
  61.  
  62. do_request: function(req) {
  63. last_req = new Date();
  64. var timer;
  65. var req_timed_out = false; /* We cannot abort a request, so we need keep track of whether it timed out */
  66.  
  67. /* Create timeout handler */
  68. timer = window.setTimeout(function() {
  69. /* Race condition: what if the request returns successfully now? */
  70. req_timed_out = true;
  71. if (req.error) req.error(null, 'Network timeout');
  72. }, req.timeout || 10000);
  73.  
  74. /* Do the actual request */
  75. GM_xmlhttpRequest({
  76. method: req.method || 'GET',
  77. url: url_base+req.url,
  78. headers: { 'User-Agent': navigator.userAgent, 'Accept': req.accept || 'text/xml' },
  79. onload: function(response) {
  80. window.clearTimeout(timer);
  81. if (!req_timed_out) req.callback(response);
  82. },
  83. onerror: function(response) {
  84. window.clearTimeout(timer);
  85. if (!req_timed_out && req.error) req.error(response, 'GM_xmlhttpRequest error');
  86. }
  87. });
  88. }
  89. };
  90. }
  91.  
  92. /* Simple rounding (extracted from jQuery Corner)*/
  93. $.fn.round = function(radius) {
  94. radius = radius || "10px";
  95. return this.each(function(i) {
  96. if ($.browser.mozilla && /gecko/i.test(navigator.userAgent)) {
  97. $(this).css('-moz-border-radius', radius);
  98. } else if ($.browser.safari && $.browser.version >= 3) {
  99. $(this).css('-webkit-border-radius', radius);
  100. }
  101. });
  102. };
  103. /* Global status area - feel free to reuse in your own scripts :)
  104. Requires jQuery and the round extension above. */
  105. function StatusBox(title) {
  106. /* Setup status area */
  107. var status_area = $('#greasemonkey_status_area').eq(0);
  108. if (status_area.length == 0) {
  109. status_area = $('<div id="greasemonkey_status_area"></div>').css({
  110. 'position': 'fixed',
  111. 'top': '0', 'right': '0',
  112. 'margin': '20px',
  113. 'width': '20%',
  114. 'z-index': 500
  115. });
  116. $('body').append(status_area);
  117. }
  118.  
  119. /* Create box */
  120. var box = $('<div></div>').hide();
  121. box.css({
  122. 'color': 'white',
  123. 'background-color': 'black',
  124. 'opacity': 0.5,
  125. 'margin': '0 0 10px 0',
  126. 'padding': '10px 10px 20px 10px'}).round();
  127. box.append($('<div>'+title+'</div>').css('font-weight', 'bold'));
  128.  
  129. /* Create contents area */
  130. var contents = $('<div></div>');
  131. box.append(contents);
  132.  
  133. var timer = null;
  134. var timeout = 0;
  135. var inhibit_fade = false;
  136.  
  137. function set_visible(visible) {
  138. if (visible && box.is(':hidden')) box.fadeIn(500);
  139. else if (!visible && box.is(':visible')) box.fadeOut(500);
  140. }
  141.  
  142. function clear_timer() {
  143. if (timer) {
  144. window.clearTimeout(timer);
  145. timer = null;
  146. }
  147. }
  148.  
  149. function set_timer() {
  150. if (!timer && timeout > 0) {
  151. timer = window.setTimeout(function() { clear_timer(); set_visible(false); }, timeout);
  152. }
  153. }
  154.  
  155. function update_timer(t) {
  156. clear_timer();
  157. timeout = t;
  158. if (!inhibit_fade) set_timer();
  159. }
  160.  
  161. function set_inhibit_fade(inhibit) {
  162. inhibit_fade = inhibit;
  163. if (!inhibit_fade) { set_timer(); }
  164. else clear_timer();
  165. }
  166.  
  167. /* Register event handlers */
  168. box.mouseenter(function(event) {
  169. set_inhibit_fade(true);
  170. $(this).fadeTo(500, 0.8);
  171. });
  172.  
  173. box.mouseleave(function(event) {
  174. set_inhibit_fade(false);
  175. $(this).fadeTo(500, 0.5);
  176. });
  177.  
  178. box.click(function(event) {
  179. clear_timer();
  180. set_visible(false);
  181. });
  182.  
  183. /* Append to global status area */
  184. status_area.append(box);
  185.  
  186. return {
  187. contents: function() {
  188. return contents;
  189. },
  190.  
  191. show: function(t) {
  192. t = t || 0;
  193. update_timer(t);
  194. set_visible(true);
  195. },
  196.  
  197. hide: function() {
  198. clear_timer();
  199. set_visible(false);
  200. }
  201. };
  202. }
  203.  
  204. /* Cache */
  205. function Cache(name, def_value) {
  206. var cache;
  207. return {
  208. serialize: function() {
  209. GM_setValue(name, uneval(cache));
  210. },
  211. unserialize: function() {
  212. cache = eval(GM_getValue(name, 'false'));
  213. if (!cache) cache = $.extend({}, def_value); /* clone */
  214. return cache;
  215. },
  216. clear: function() {
  217. cache = $.extend({}, def_value); /* clone */
  218. this.serialize();
  219. }
  220. };
  221. }
  222.  
  223. /* Get what.CD base URL */
  224. var whatcd_url_base = document.URL.match(/^(https:\/\/tehconnection\.eu|http:\/\/tehconnection\.eu)/)[1];
  225.  
  226. /* Create proxy */
  227. var whatcd_proxy = Proxy(whatcd_url_base, 1000);
  228.  
  229. /* Get user id of this user */
  230. var whatcd_id = (function() {
  231. var m = $('#header .username').eq(0).attr('href').match(/user\.php\?id=(\d+)/);
  232. if (m) return m[1];
  233. return null;
  234. })();
  235.  
  236. if (!whatcd_id) return; /* Exceptional condition: User ID not found */
  237.  
  238. /* Create status box */
  239. var status = StatusBox('TehConnection.Eu Snatched');
  240.  
  241. /* Cache of snatched torrents */
  242. var snatch_cache = Cache('snatch_cache', { groups: {}, torrents: {} });
  243.  
  244. /* Scan torrent table in doc and mark links as type in cache */
  245. function scan_torrent_page(doc, type) {
  246. var torrent_table = doc.find('.torrent_table').eq(0);
  247. if (torrent_table.length == 0){
  248. return 0;
  249. }
  250.  
  251. var found = 0;
  252.  
  253. var d = snatch_cache.unserialize();
  254. torrent_table.find('tr').not('.colhead').each(function(i) {
  255. /* Find group and torrent ID */
  256. var group_id;
  257. var torrent_id;
  258. var link = $(this).children('td').eq(1).children('a:last').eq(0);
  259. var m = link.attr('href').match(/torrents\.php\?id=(\d+)&torrentid=(\d+)/);
  260. if (m) {
  261. group_id = m[1];
  262. torrent_id = m[2];
  263. } else {
  264. m = link.attr('href').match(/torrents\.php\?id=(\d+)/);
  265. if (m) {
  266. group_id = m[1];
  267. link = $(this).children('td').eq(1).children('span').eq(0).children('a').eq(0);
  268. m = link.attr('href').match(/torrents\.php\?action=download&id=(\d+)/);
  269. if (m) torrent_id = m[1];
  270. }
  271.  
  272. if (!m) {
  273. status.contents().append('<div><span style="color: red;">Failed:</span> '+$(this).children('td').eq(1).text()+'</div>');
  274. status.show();
  275. }
  276. }
  277.  
  278. /* Save in cache */
  279. if (group_id && torrent_id) {
  280. if (!d.torrents[torrent_id] ||
  281. (type != 'seeding' && d.torrents[torrent_id].type != type) ||
  282. (type == 'seeding' && !d.torrents[torrent_id].seeding)) {
  283. var name = $.trim($(this).children('td').eq(1).clone().children('span, div').remove().end().text().match(/\s+([^[]+)(\s+\[|$)/)[1]);
  284. d.groups[group_id] = { name: name };
  285. if (type == 'seeding') { /* Special case seeding */
  286. if (d.torrents[torrent_id]) d.torrents[torrent_id].seeding = true;
  287. else d.torrents[torrent_id] = { type: 'unknown', seeding: true };
  288. } else {
  289. if (d.torrents[torrent_id]) d.torrents[torrent_id].type = type;
  290. else d.torrents[torrent_id] = { type: type, seeding: false };
  291. }
  292. found += 1;
  293. }
  294. }
  295. });
  296.  
  297. if (found == 0) return 0;
  298.  
  299. snatch_cache.serialize();
  300. return found;
  301. }
  302.  
  303. /* Fetch and scan all pages of type, call callback when done */
  304. function scan_all_torrent_pages(type, page_cb, finish_cb) {
  305. var page = 1;
  306. var total = 0;
  307.  
  308. function request_url() {
  309. return '/torrents.php?type='+type+'&userid='+whatcd_id+'&page='+page;
  310. }
  311.  
  312. function error_handler(response, reason) {
  313. alert("Error in loading snatches");
  314. status.contents().append('<div><span style="color: red;">Error:</span> Unable to fetch '+type+' page '+page+' ('+reason+')</div>');
  315. status.show();
  316. finish_cb(total);
  317. }
  318.  
  319. function page_handler(response) {
  320. if (response.status == 200) {
  321. var doc = $(new DOMParser().parseFromString(response.responseText, 'text/xml'));
  322. page_cb(type, page);
  323. var found = scan_torrent_page(doc, type);
  324.  
  325. total += found;
  326. if (found == 0) { finish_cb(type, total); return; } /* End of asynchronous chain */
  327.  
  328. page += 1;
  329. whatcd_proxy.get({ url: request_url(), callback: page_handler, error: error_handler });
  330. } else {
  331. error_handler(response, 'HTTP '+response.status);
  332. }
  333. }
  334.  
  335. whatcd_proxy.get({ url: request_url(), callback: page_handler, error: error_handler });
  336. }
  337.  
  338. /* Reset command */
  339. GM_registerMenuCommand('TehConnection.Eu Snatched: Reset', function() { snatch_cache.clear(); GM_setValue('last_update', '0'); location.reload(); });
  340.  
  341. /* Register menu command to enter custom style */
  342. var custom_style = GM_getValue('custom_style', DEFAULT_STYLE);
  343. GM_registerMenuCommand('TehConnection.Eu Snatched: Enter custom style...', function() {
  344. var style = window.prompt('Enter CSS style (or blank to use default)\nClasses: .group_snatched, .snatched, .uploaded, .leeching, .seeding', custom_style);
  345. if (style) {
  346. GM_setValue('custom_style', style);
  347. location.reload();
  348. } else if (style == '') {
  349. GM_deleteValue('custom_style');
  350. location.reload();
  351. }
  352. });
  353.  
  354. /* Inject CSS style */
  355. GM_addStyle(custom_style);
  356.  
  357. /* Mark all links to torrents that are snatched/uploaded/leeching */
  358. function mark_snatched_links() {
  359. var d = snatch_cache.unserialize();
  360.  
  361. /* Go through all links */
  362. $('#content').find('a').each(function(i) {
  363. var href = $(this).attr('href');
  364. if (href) {
  365. var group_id;
  366. var torrent_id;
  367.  
  368. /* Find and mark links to snatched torrents */
  369. var m = href.match(/torrents\.php\?id=(\d+)&torrentid=(\d+)/);
  370. if (m) {
  371. group_id = m[1];
  372. torrent_id = m[2];
  373. } else {
  374. m = href.match(/torrents\.php\?torrentid=(\d+)/);
  375. if (m) {
  376. torrent_id = m[1];
  377. } else {
  378. m = href.match(/torrents\.php\?id=(\d+)/);
  379. if (m) group_id = m[1];
  380. }
  381. }
  382.  
  383. /* Add classes */
  384. if (group_id && d.groups[group_id] &&
  385. (!torrent_id || !$(this).parent().parent().is('.group_torrent'))) {
  386. $(this).addClass('group_snatched');
  387. }
  388. if (torrent_id && d.torrents[torrent_id]) {
  389. $(this).addClass(d.torrents[torrent_id].type);
  390. if (d.torrents[torrent_id].seeding) $(this).addClass('seeding');
  391. }
  392.  
  393. /* Change text if text is url */
  394. if ($(this).text() == $(this).attr('href') && group_id &&
  395. d.groups[group_id] && d.groups[group_id].name) {
  396. $(this).text(d.groups[group_id].name);
  397. }
  398. }
  399. });
  400.  
  401. /* Mark links on album page in torrent table */
  402. if (/tehconnection\.eu\/torrents\.php/.test(document.URL)) {
  403. /* Parse search */
  404. var search = {};
  405. var search_list = document.location.search.substring(1).split('&');
  406. for (var i = 0; i < search_list.length; i++) {
  407. var pair = search_list[i].split('=');
  408. search[pair[0]] = pair[1];
  409. }
  410.  
  411. if (search.id) {
  412. /* Album page */
  413. $('#content .torrent_table:first tr.group_torrent').each(function(i) {
  414. /* Find torrent id */
  415. var torrent_id;
  416. $(this).children('td').eq(0).children('span').eq(0).children('a').each(function(i) {
  417. var href = $(this).attr('href');
  418. if (href) {
  419. var m = href.match(/torrents\.php\?torrentid=(\d+)/);
  420. if (m) {
  421. torrent_id = m[1];
  422. $(this).removeClass('group_snatched snatched uploaded leeching seeding');
  423. return false;
  424. }
  425. }
  426. });
  427.  
  428. if (torrent_id && d.torrents[torrent_id]) {
  429. var link = $(this).children('td').eq(0).children('a').eq(0);
  430. link.addClass(d.torrents[torrent_id].type);
  431. if (d.torrents[torrent_id].seeding) link.addClass('seeding');
  432. }
  433. });
  434. }
  435. }
  436. }
  437.  
  438. /* Mark torrent as leeching when download link is clicked */
  439. function mark_download_links() {
  440. $('#content').find('a').each(function(i) {
  441. var href = $(this).attr('href');
  442. if (href) {
  443. /* Find download links */
  444. var m = href.match(/torrents\.php\?action=download&id=(\d+)/);
  445. if (m) {
  446. var torrent_id = m[1];
  447. $(this).click(function(event) {
  448. var d = snatch_cache.unserialize();
  449. d.torrents[torrent_id] = { type: 'leeching', seeding: false };
  450. snatch_cache.serialize();
  451. mark_snatched_links();
  452. });
  453. }
  454. }
  455. });
  456. }
  457.  
  458. /* Scan current page */
  459. if (/tehconnection\.eu\/torrents\.php/.test(document.URL)) {
  460. /* Parse search */
  461. var search = {};
  462. var search_list = document.location.search.substring(1).split('&');
  463. for (var i = 0; i < search_list.length; i++) {
  464. var pair = search_list[i].split('=');
  465. search[pair[0]] = pair[1];
  466. }
  467.  
  468. if ((search.type == 'snatched' || search.type == 'uploaded'
  469. || search.type == 'seeding' || search.type == 'leeching') && search.userid == whatcd_id) {
  470. var scan_status = $('<div>Scanning current page... <span></span></div>');
  471. status.contents().append(scan_status);
  472. status.show();
  473.  
  474. /* Scan current page */
  475. var found = scan_torrent_page($(document), search.type);
  476.  
  477. scan_status.children('span').text('Done ('+((found > 0) ? (found+' updates found') : 'no updates found')+')')
  478. status.show(5000);
  479. }
  480. }
  481.  
  482. /* Mark links */
  483. mark_download_links();
  484. mark_snatched_links();
  485.  
  486. /* Auto update */
  487. var now = new Date();
  488. var last_update = parseInt(GM_getValue('last_update', '0'));
  489. if (last_update + AUTO_UPDATE_INTERVAL*1000 < now.getTime()) {
  490. GM_setValue('last_update', now.getTime().toString());
  491. var jobs = 4;
  492. var total_found = {};
  493.  
  494. /* Show auto update status */
  495. if (last_update == 0) {
  496. var update_status = {
  497. snatched: $('<div>Updating snatched: <span>Initializing...</span></div>'),
  498. uploaded: $('<div>Updating uploaded: <span>Initializing...</span></div>'),
  499. leeching: $('<div>Updating leeching: <span>Initializing...</span></div>'),
  500. seeding: $('<div>Updating seeding: <span>Initializing...</span></div>')
  501. };
  502.  
  503. for (var type in update_status) status.contents().append(update_status[type]);
  504. status.show();
  505. }
  506.  
  507. function scan_page_handler(type, page) {
  508. if (last_update == 0) {
  509. update_status[type].children('span').text('Page '+page+'...');
  510. status.show();
  511. }
  512. }
  513.  
  514. function scan_finished_handler(type, found) {
  515. if (last_update == 0) {
  516. update_status[type].children('span').text('Done ('+((found > 0) ? (found+' updates found') : 'no updates found')+')');
  517. }
  518.  
  519. jobs -= 1;
  520. total_found[type] = found;
  521.  
  522. if (jobs == 0) {
  523. mark_snatched_links();
  524. if (last_update == 0) {
  525. var total = [];
  526. for (var type in total_found) if (total_found[type] > 0) total.push(type+': '+total_found[type]);
  527. status.contents().append('<div>Auto update done</div>');
  528. status.show(5000);
  529. }
  530. }
  531. }
  532.  
  533. /* Rescan all types of torrent lists */
  534. scan_all_torrent_pages('snatched', scan_page_handler, scan_finished_handler);
  535. scan_all_torrent_pages('uploaded', scan_page_handler, scan_finished_handler);
  536. scan_all_torrent_pages('leeching', scan_page_handler, scan_finished_handler);
  537. scan_all_torrent_pages('seeding', scan_page_handler, scan_finished_handler);
  538. }
  539. })();