Advertisement
Guest User

Untitled

a guest
Mar 28th, 2015
237
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 23.30 KB | None | 0 0
  1. // ==UserScript==
  2. // @name Torn Stats
  3. // @namespace vinkuun.tornStats
  4. // @author Vinkuun [1791283]
  5. // @description Hit counter, armoury usage tracker, submission of battle stats to tornstats.com, integration of spies submitted to tornstats.com
  6. // @include *.torn.com/index.php
  7. // @include *.tornstats.com/apiHome.php
  8. // @include *.torn.com/profiles.php?XID=*
  9. // @include *.torn.com/factions.php?step=your
  10. // @include *.tornstats.com/apiChainEnter.php
  11. // @version 6.2.1
  12. // @grant GM_getResourceText
  13. // @require http://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js
  14. // @require http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.2/moment.min.js
  15. // @require http://cdnjs.cloudflare.com/ajax/libs/q.js/1.0.1/q.min.js
  16. // @require https://raw.githubusercontent.com/xdan/datetimepicker/master/jquery.datetimepicker.js
  17. // @resource datetimepicker_css https://raw.githubusercontent.com/xdan/datetimepicker/master/jquery.datetimepicker.css
  18. // ==/UserScript==
  19.  
  20. try {
  21.  
  22. this.$ = this.jQuery = jQuery.noConflict(true);
  23.  
  24. // only run if logged in or if in an iframe
  25. if (!(window === window.top && $('#player-stats').length === 0)) {
  26.  
  27. (function() {
  28. 'use strict';
  29.  
  30. var DATE_FORMAT = 'DD/MM/YY hh:mm:ss a';
  31.  
  32. var IS_HTTPS = location.protocol === 'https:';
  33.  
  34. ///////////////////////////////////////////////////////////////////////////////////////////////////
  35. // Utilities
  36. ///////////////////////////////////////////////////////////////////////////////////////////////////
  37. /**
  38. * Adds CSS to the HEAD of the document
  39. * @param {string} css
  40. */
  41. function addCss(css) {
  42. var head = document.head,
  43. style = document.createElement('style');
  44.  
  45. style.type = 'text/css';
  46. style.appendChild(document.createTextNode(css));
  47.  
  48. head.appendChild(style);
  49. }
  50.  
  51. /**
  52. * Creates a button in Torn-style
  53. * @param {String} text The text of the button
  54. * @return {jQuery-Object}
  55. */
  56. function createButton(text) {
  57. return $('<div>', {'class': 'btn-wrap silver vinkuun-button'}).append($('<div>', {'class': 'btn', text: text}));
  58. }
  59.  
  60. /**
  61. * Creates a horizontal delimiter
  62. * @return {jQuery-Object}
  63. */
  64. function createHR() {
  65. return $('<hr>', { class: 'delimiter-999 m-top10' });
  66. }
  67.  
  68. /**
  69. * Creates an info ui in torn style
  70. * @param {string} msg
  71. * @return {jQuery-Object}
  72. */
  73. function createInfoBox(msg) {
  74. return $('<div class="info-msg-cont border-round m-top10"><div class="info-msg border-round">' +
  75. '<i class="info-icon"></i><div class="delimiter"><div class="msg right-round">' +
  76. msg +
  77. '</div></div>' +
  78. '</div></div>');
  79. }
  80.  
  81. /**
  82. * Creates an active infinite progress indicator; call .trigger('activate') or .trigger('deactivate')
  83. * @return {jQuery-Object}
  84. */
  85. function createProgressIndicator() {
  86. return $('<div class="vinkuun-progressIndicator active"></div>').bind('enable', function() {
  87. $(this).addClass('active');
  88. }).bind('disable', function() {
  89. $(this).removeClass('active');
  90. });
  91. }
  92.  
  93. ///////////////////////////////////////////////////////////////////////////////////////////////////
  94. // Battle Stats Tracker
  95. ///////////////////////////////////////////////////////////////////////////////////////////////////
  96. var BattleStatsTracker = (function() {
  97. function addTracker() {
  98. var battleStatsBlockTitle = $('div.title:contains("Battle Stats")');
  99. var battleStatsBlockContent = battleStatsBlockTitle.next('div').find('div');
  100.  
  101. // add hr after the stats
  102. battleStatsBlockContent.append($('<hr>', { style: 'border: 1px solid #ccc; margin: 5px 0' }));
  103.  
  104. if (IS_HTTPS) {
  105. battleStatsBlockContent.append('Battle Stats cannot be submitted when using HTTPS.');
  106. } else {
  107. addCss(
  108. '#vinkuun-tornStats-BattleStats iframe { margin-top: 5px; width: 100%; height: 250px; border: 1px solid #ccc; border-radius: 5px }'
  109. );
  110.  
  111. // add an ID to the battle stats block
  112. battleStatsBlockTitle.parent().attr('id', 'vinkuun-tornStats-BattleStats');
  113.  
  114. var battleStatsIframe = $('<iframe>', {
  115. src: 'http://www.tornstats.com/apiHome.php',
  116. text: 'You do not have iframes enabled.'
  117. });
  118.  
  119. var submitButton = createButton('Post your Battle Stats to Torn Stats').css('display', 'block').on('click', function(event) {
  120. var currentBattleStats = [];
  121.  
  122. battleStatsBlockContent.find('ul span.desc').slice(0, 4).each(function() {
  123. currentBattleStats.push(this.textContent.trim());
  124. });
  125.  
  126. submitButton.addClass('disable');
  127.  
  128. // post message to iframe that you want to update stats
  129. battleStatsIframe[0].contentWindow.postMessage(currentBattleStats, '*');
  130. });
  131.  
  132. // add the iframe and the status element after the battle stats
  133. battleStatsBlockContent
  134. .append(submitButton)
  135. .append(battleStatsIframe);
  136.  
  137. // listen for a message from the battle stats iframe
  138. window.addEventListener('message', function(e) {
  139. var responseText = '';
  140.  
  141. if (typeof e.data === 'string') {
  142. responseText = e.data;
  143. } else {
  144. // build response text
  145. if (e.data.deltaStrength) {
  146. responseText += e.data.deltaStrength + ' Strength, ';
  147. }
  148.  
  149. if (e.data.deltaDefense) {
  150. responseText += e.data.deltaDefense + ' Defense, ';
  151. }
  152.  
  153. if (e.data.deltaDexterity) {
  154. responseText += e.data.deltaDexterity + ' Dexterity, ';
  155. }
  156.  
  157. if (e.data.deltaSpeed) {
  158. responseText += e.data.deltaSpeed + ' Speed, ';
  159. }
  160.  
  161. // battle stats changed if the responseText isn't empty
  162. if (responseText.length !== 0) {
  163. responseText = 'You gained <b>' + responseText.slice(0, responseText.length - 2) + '</b>';
  164. } else {
  165. responseText = 'You <b>gained no stats</b>';
  166. }
  167.  
  168. responseText += ' since your last update ' + e.data.age + '.';
  169. }
  170.  
  171. submitButton.replaceWith($('<span>', {html: responseText}));
  172. });
  173. }
  174. }
  175.  
  176. function addSubmissionListener() {
  177. window.addEventListener('message', function(e) {
  178. var stats = e.data;
  179.  
  180. $.ajax({
  181. type: 'POST',
  182. url: 'http://www.tornstats.com/apiUpdate.php',
  183. dataType: 'json',
  184. data: {
  185. strength: stats[0],
  186. defense: stats[1],
  187. speed: stats[2],
  188. dexterity: stats[3]
  189. },
  190. success: function(response) {
  191. window.parent.postMessage(response, '*');
  192. window.location.reload();
  193. },
  194. error: function (xhr, ajaxOptions, thrownError) {
  195. window.parent.postMessage('An error occured.', '*');
  196. }
  197. });
  198. });
  199. }
  200.  
  201. return {
  202. addTracker: addTracker,
  203. addSubmissionListener: addSubmissionListener
  204. };
  205. }());
  206.  
  207. ///////////////////////////////////////////////////////////////////////////////////////////////////
  208. // Spies
  209. ///////////////////////////////////////////////////////////////////////////////////////////////////
  210. var Spies = (function() {
  211. function addLatestInformation() {
  212. addCss('.profile-container-description { height: auto !important }');
  213.  
  214. var id = window.location.href.match(/XID=(\d+)/)[1];
  215.  
  216. $('head').append($('<script>', {src: 'http://www.tornstats.com/apiSpy.php?userid=' + id}));
  217. }
  218.  
  219. return {
  220. addLatestInformation: addLatestInformation
  221. };
  222. }());
  223.  
  224. ///////////////////////////////////////////////////////////////////////////////////////////////////
  225. // Faction log collector
  226. ///////////////////////////////////////////////////////////////////////////////////////////////////
  227. var LogCollector = (function() {
  228. function getEntries(type, offset) {
  229. return Q($.ajax({
  230. type: 'post',
  231. url: 'factions.php',
  232. data: {
  233. step: 'mainnews',
  234. type: type,
  235. start: offset
  236. }
  237. })).then(function(data) {
  238. return JSON.parse(data).list;
  239. });
  240. }
  241.  
  242. /**
  243. * Collects logs for "Main News", "Attacking", "Donations" and "Armoury"
  244. * @param {Integer} type type of the log (between 1 and 4)
  245. * @param {Integer} offset skip x logs (x % 10 === 0)
  246. * @param {moment-Object} startDate
  247. * @param {moment-Object} endDate
  248. * @param {function} progressHandler
  249. * @return {Array} Found logs
  250. */
  251. function collect(type, offset, startDate, endDate, progressHandler) {
  252. return _collect(type, offset, startDate, endDate, progressHandler, [], 0);
  253. }
  254.  
  255. function _collect(type, offset, startDate, endDate, progressHandler, logs, numOfRequests) {
  256. return Q.Promise(function(resolve) {
  257. if (numOfRequests === 30) { // wait 10 seconds after 30 requests have been made
  258. numOfRequests = 0;
  259. progressHandler('Zzzz ... the script is sleeping now for 10 seconds to avoid a temporary block caused by too many requests per minute.');
  260. setTimeout(resolve, 10 * 1000);
  261. } else {
  262. resolve();
  263. }
  264. }).then(function () {
  265. return getEntries(type, offset).then(function(entries) {
  266. progressHandler('(current date ' + entries[0].date + ' ' + entries[0].time + ')');
  267.  
  268. var stopCollecting = entries.some(function(entry) {
  269. var date = moment(entry.date + ' ' + entry.time, DATE_FORMAT);
  270. if (!date.isAfter(startDate)) {
  271. // stop collecting as the current event happened before the time frame
  272. return true;
  273. } else {
  274. if (!date.isAfter(endDate)) {
  275. logs.push(entry);
  276. }
  277.  
  278. return false;
  279. }
  280. });
  281.  
  282. if (!stopCollecting) {
  283. return _collect(type, offset + 10, startDate, endDate, progressHandler, logs, ++numOfRequests);
  284. }
  285.  
  286. return logs;
  287. });
  288. });
  289. }
  290.  
  291. return {
  292. collect: collect
  293. };
  294. }());
  295.  
  296. ///////////////////////////////////////////////////////////////////////////////////////////////////
  297. // Attack log analysis
  298. ///////////////////////////////////////////////////////////////////////////////////////////////////
  299. var AttackLog = (function(LogCollector) {
  300. function collectData(start, end, statusLabel) {
  301. return LogCollector.collect(2, 0, moment(start, DATE_FORMAT), moment(end, DATE_FORMAT), function(text) {
  302. statusLabel.text(text);
  303. });
  304. }
  305.  
  306. function createUI() {
  307. var ui = $('<div>', { class: 'm-top10 vinkuun-attacklog' }).appendTo($('#factions'));
  308.  
  309. ui.append(createInfoBox('Submit the attack log (displayed in the "Attacking"-tab above) to <a href="http://tornstats.com">Torn Stats</a> to find out what happened during a specific time frame; i.e. this will count the hits of all faction members. The result can be analysed in your faction\'s <a href="http://tornstats.com/chainbase.php?action=view">Chain Base</a>.'));
  310.  
  311. if (IS_HTTPS) {
  312. ui.append(createInfoBox('This feature is not available when using HTTPS.'));
  313. } else {
  314. var iframe = $('<iframe>', {src:'http://tornstats.com/apiChainEnter.php', text: 'Please enable iframes to use this feature.'}).hide().appendTo(ui);
  315.  
  316. var header = $('<div>', { class: 'title-black top-round m-top10', text: 'Attack log submission' }).appendTo(ui);
  317. var content = $('<div>', { class: 'cont-gray10 bottom-round' }).appendTo(ui);
  318.  
  319. var storedStartDate = localStorage['tornstats.startDate'] || '';
  320. var storedEndDate = localStorage['tornstats.endDate'] || '';
  321.  
  322. var startDateField = $('<input>', {type: 'text', value: storedStartDate});
  323. var endDateField = $('<input>', {type: 'text', value: storedEndDate});
  324.  
  325. startDateField.datetimepicker({format: 'd/m/y h:m:s A'});
  326. endDateField.datetimepicker({format: 'd/m/y h:m:s A'});
  327.  
  328. var statusWrapper = $('<p>');
  329. var progressIndicator = createProgressIndicator().appendTo(statusWrapper);
  330. var stepLabel = $('<span>').appendTo(statusWrapper);
  331. var statusLabel = $('<span>').appendTo(statusWrapper);
  332.  
  333. var analyseButton = createButton('Submit to Torn Stats').on('click', function() {
  334. var $this = $(this);
  335.  
  336. if (!$this.hasClass('disable') && isInputValid(startDateField, endDateField)) {
  337. localStorage['tornstats.startDate'] = startDateField.val();
  338. localStorage['tornstats.endDate'] = endDateField.val();
  339.  
  340. $(this).addClass('disable');
  341.  
  342. statusWrapper.show();
  343. progressIndicator.trigger('enable');
  344. stepLabel.text('Collecting data...');
  345.  
  346. collectData(startDateField.val(), endDateField.val(), statusLabel).then(function(logs) {
  347. statusLabel.text('');
  348. stepLabel.text('Transforming collected data...');
  349.  
  350. return transformLogs(logs);
  351. }).then(function(logs) {
  352. stepLabel.text('Submitting logs to Torn Stats...');
  353.  
  354. submitLogs(logs, iframe);
  355. });
  356. }
  357. }).addClass('disable'); // button will be enabled by a message from the iframe
  358.  
  359. stepLabel.text('Initializing: loading data from Torn Stats');
  360.  
  361. content
  362. .append($('<label>', {'text': 'Start date'}).append(startDateField))
  363. .append($('<label>', {'text': 'End date'}).append(endDateField))
  364. .append(analyseButton)
  365. .append(statusWrapper);
  366.  
  367. // event listener for messages sent from the iframe
  368. window.addEventListener('message', function(e) {
  369. progressIndicator.trigger('disable');
  370.  
  371. if (e.data.type === 'dataSubmitted' || e.data.type === 'iframeReady') {
  372. analyseButton.removeClass('disable');
  373.  
  374. if (e.data.type === 'dataSubmitted') {
  375. stepLabel.text(e.data.message);
  376. } else if (e.data.type === 'iframeReady') {
  377. iframe.hide();
  378. stepLabel.text('Ready.');
  379. }
  380. } else if (e.data.type === 'loginRequired' || e.data.type === 'noPermission') {
  381. analyseButton.addClass('disable');
  382.  
  383. if (e.data.type === 'loginRequired') {
  384. iframe.show();
  385. iframe.css('height', '300px');
  386. stepLabel.text('You need to log in before you can use this feature.');
  387. } else {
  388. stepLabel.text(e.data.message);
  389. }
  390. }
  391. }, false);
  392. }
  393.  
  394. return ui;
  395. }
  396.  
  397. function isInputValid(startDateField, endDateField) {
  398. var startDate = moment(startDateField.val(), DATE_FORMAT);
  399.  
  400. if (!startDate.isValid()) {
  401. startDateField.addClass('has-error');
  402. return false;
  403. } else {
  404. startDateField.removeClass('has-error');
  405. }
  406.  
  407. var endDate = moment(endDateField.val(), DATE_FORMAT);
  408.  
  409. if (!endDate.isValid()) {
  410. endDateField.addClass('has-error');
  411. return false;
  412. } else {
  413. endDateField.removeClass('has-error');
  414. }
  415.  
  416. if (!startDate.isBefore(endDate)) {
  417. endDateField.addClass('has-error');
  418. startDateField.addClass('has-error');
  419. return false;
  420. } else {
  421. startDateField.removeClass('has-error');
  422. endDateField.removeClass('has-error');
  423. }
  424.  
  425. return true;
  426. }
  427.  
  428. /**
  429. * Transforms logs into a tornstats-readable format
  430. * @param {array} logs raw attack logs
  431. * @return {array} logs in tornstats-format
  432. */
  433. function transformLogs(logs) {
  434. return Q.Promise(function(resolve, reject) {
  435. var tornstatsLogs = [];
  436.  
  437. for (var i = 0; i < logs.length; i++) {
  438. tornstatsLogs.push(transformLogEntry(logs[i]));
  439. }
  440.  
  441. resolve(tornstatsLogs);
  442. });
  443. }
  444.  
  445. var ACTION = {
  446. hospitalized: 0,
  447. mugged: 1,
  448. attacked: 2,
  449. other: 3
  450. };
  451. var ACTION_EXTRA = {
  452. nothing: 0,
  453. loss: 1,
  454. stalemate: 2,
  455. retal: 3
  456. };
  457.  
  458. /**
  459. * Transforms a log entry from torn-format into a tornstats-format
  460. * @param {object} entry log entry in torn-format
  461. * @return {object} log entry in tornstats-format
  462. */
  463. function transformLogEntry(entry) {
  464. var rawEventText = $('<div>').html(entry.info).text().trim();
  465.  
  466. var match,
  467. attacker,
  468. defender = '',
  469. action,
  470. extra = ACTION_EXTRA.nothing,
  471. bonus = 0;
  472.  
  473. if ((match = rawEventText.match(/^Chain #(\d+) achieved thanks to (\S+)!/)) !== null) {
  474. attacker = match[2];
  475. bonus = parseInt(match[1], 10);
  476. action = ACTION.other;
  477. } else if ((match = rawEventText.match(/^(\S+) (\S+) (\S+)(.*)$/)) !== null) {
  478. attacker = match[1];
  479. action = ACTION[match[2]] !== undefined ? ACTION[match[2]] : ACTION.other;
  480. defender = match[3];
  481.  
  482. if (match[4].indexOf('lost') !== -1) {
  483. extra = ACTION_EXTRA.loss;
  484. } else if (match[4].indexOf('stalemated') !== -1) {
  485. extra = ACTION_EXTRA.stalemate;
  486. } else if (match[4].indexOf('Retaliation') !== -1) {
  487. extra = ACTION_EXTRA.retal;
  488. }
  489. }
  490.  
  491. return {
  492. news: rawEventText,
  493. date: entry.date,
  494. time: entry.time,
  495. timestamp: moment(entry.date + ' ' + entry.time, DATE_FORMAT).unix(),
  496. attacker: attacker,
  497. defender: defender,
  498. action: action,
  499. extra: extra,
  500. bonus: bonus
  501. };
  502. }
  503.  
  504. function submitLogs(logs, iframe) {
  505. iframe[0].contentWindow.postMessage(logs, '*');
  506. }
  507.  
  508. function prepareIFrame() {
  509. if ($('legend:contains("Login")').length !== 0) { // iframe required login
  510. window.parent.postMessage({type: 'loginRequired'}, '*');
  511. } else if($('div.container:contains("You do not have the proper rank")').length !== 0) {
  512. window.parent.postMessage({type: 'noPermission', message: $('div.container').text()}, '*');
  513. } else {
  514. if ($('p').length == 1) { // form has been submitted => notify embedding window about the status
  515. window.parent.postMessage({type: 'dataSubmitted', message: $('p').text()}, '*');
  516. } else { // iframe was loaded
  517. window.parent.postMessage({type: 'iframeReady'}, '*');
  518. }
  519.  
  520. // submit attack logs if a message was sent to the iframe
  521. window.addEventListener('message', function(e) {
  522. var requests = [];
  523. var result = {
  524. entered: 0,
  525. duplicate: 0,
  526. error: 0
  527. };
  528.  
  529. // split data in chunks of 500 logs
  530. for (var i = 0, chunkSize = 500; i < e.data.length; i += chunkSize) {
  531. var chunk = JSON.stringify(e.data.slice(i, i+chunkSize));
  532.  
  533. requests.push(Q($.ajax({
  534. type: 'POST',
  535. url: '/apiChainEnter2.php',
  536. contentType: "application/json",
  537. dataType: 'json',
  538. processData: false,
  539. data: chunk
  540. })).then(function(response) {
  541. result.entered += response.entered;
  542. result.duplicate += response.duplicate;
  543. result.error += response.error;
  544. }));
  545. }
  546.  
  547. Q.all(requests).done(function() {
  548. var message = result.entered + ' logs have been entered into the database and ' + result.duplicate + ' logs were already entered! There was an error with ' + result.error + ' logs.';
  549. window.parent.postMessage({type: 'dataSubmitted', message: message}, '*');
  550. }, function(error) {
  551. var message = 'An unexpected error occured while submitting the logs to Torn Stats: ' + JSON.stringify(error);
  552. window.parent.postMessage({type: 'dataSubmitted', message: message}, '*');
  553. });
  554. }, false);
  555. }
  556. }
  557.  
  558. return {
  559. createUI: createUI,
  560. prepareIFrame: prepareIFrame
  561. }
  562. }(LogCollector));
  563.  
  564. /**
  565. * Executes the provided watchers after the faction page has been loaded
  566. * @param {array} watchers
  567. */
  568. function watchFactionPage(watchers) {
  569. var page = $("#faction-main");
  570.  
  571. new MutationObserver(function(mutations) {
  572. mutations.forEach(function(mutation) {
  573. if (mutation.addedNodes.length === 19) {
  574. watchers.forEach(function(watcher) {
  575. watcher(page);
  576. });
  577. }
  578. });
  579. }).observe(page[0], { childList: true });
  580. }
  581.  
  582. ///////////////////////////////////////////////////////////////////////////////////////////////////
  583. // Main
  584. ///////////////////////////////////////////////////////////////////////////////////////////////////
  585.  
  586. var currentPage = window.location.href;
  587.  
  588. if (currentPage.indexOf('torn.com/') !== -1) {
  589. addCss(
  590. '.vinkuun-button { cursor: pointer }' +
  591. '.vinkuun-button div.btn { padding: 0 10px 0 7px !important }' +
  592. '.vinkuun-progressIndicator { border: 4px solid #178FE5; border-color: rgba(127, 129, 131, 0.4) rgba(127, 129, 131, 0.6) rgba(127, 129, 131, 0.8) rgba(127, 129, 131, 1); border-radius: 150px; display: inline-block; width: 8px; height: 8px; margin: 2px; vertical-align: middle }' +
  593. '.vinkuun-progressIndicator.active { animation: spin 1s infinite linear; border-color: rgba(66, 129, 192, 0.4) rgba(66, 129, 192, 0.6) rgba(66, 129, 192, 0.8) rgba(66, 129, 192, 1) }' +
  594. '@keyframes spin { 0% { transform: rotate(0deg) } 100% { transform: rotate(360deg) } }' +
  595. 'input.has-error { border: 1px solid rgba(191, 56, 56, 1) !important; background-color: rgba(255, 210, 210, 1) !important }'
  596. );
  597.  
  598. if (currentPage.indexOf('torn.com/index.php') !== -1) {
  599. BattleStatsTracker.addTracker();
  600. } else if (currentPage.indexOf('torn.com/profiles.php') !== -1) {
  601. Spies.addLatestInformation();
  602. } else if(currentPage.indexOf('torn.com/factions.php?step=your') !== -1) {
  603. addCss(
  604. '.vinkuun-attacklog .hint { border: 1px solid #aaa; border-left: none; border-right: none; padding: 10px; margin-bottom: 5px }' +
  605. '.vinkuun-attacklog input[type="text"] { padding: 4px 6px; margin: -2px 4px 0; line-height: 14px; border: 1px solid #CCC; border-radius: 5px; vertical-align: middle }' +
  606. '.vinkuun-attacklog label { margin: 0 5px 0 0; font-weight: bold }' +
  607. '.vinkuun-attacklog iframe { width: 100%; margin-top: 10px }'
  608. );
  609.  
  610. addCss(GM_getResourceText('datetimepicker_css'));
  611.  
  612. watchFactionPage([function(page) {
  613. page.append(createHR());
  614.  
  615. page.append(AttackLog.createUI());
  616. }]);
  617. }
  618. } else {
  619. if (currentPage.indexOf('tornstats.com/apiHome.php') !== -1) {
  620. BattleStatsTracker.addSubmissionListener();
  621. } else if (currentPage.indexOf('tornstats.com/apiChainEnter.php') !== -1) {
  622. AttackLog.prepareIFrame();
  623. }
  624. }
  625. }());
  626.  
  627. } // iframe/logged in
  628.  
  629. } catch (err) {
  630. alert('Torn Stats Userscript encountered an error: ' + err);
  631. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement