Torn Helper

Adds extra information to different pages all around Torn.

当前为 2017-09-04 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Torn Helper
  3. // @namespace Jebster.Torn
  4. // @author Jeggy
  5. // @description Adds extra information to different pages all around Torn.
  6. // @include *.torn.com/profiles.php?XID=*
  7. // @version 0.3.2
  8. // @require http://code.jquery.com/jquery-2.2.4.min.js
  9. // @require http://code.jquery.com/ui/1.12.1/jquery-ui.min.js
  10. // @resource jquery-ui http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.3/themes/black-tie/jquery-ui.min.css
  11. // @resource jquery-base http://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css
  12. // @grant GM_addStyle
  13. // @grant GM_getResourceText
  14. // ==/UserScript==
  15. // debugger;
  16.  
  17. GM_addStyle(GM_getResourceText('jquery-base'));
  18. GM_addStyle(GM_getResourceText('jquery-ui'));
  19.  
  20. String.prototype.format = function() {
  21. var formatted = this;
  22. for (var i = 0; i < arguments.length; i++) {
  23. var regexp = new RegExp('\\{'+i+'\\}', 'gi');
  24. formatted = formatted.replace(regexp, arguments[i]);
  25. }
  26. return formatted;
  27. };
  28.  
  29. var data = {};
  30.  
  31. (function() {
  32. 'use strict';
  33.  
  34. $( 'head' ).append( '<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css">' );
  35.  
  36. var site = window.location.pathname;
  37.  
  38. loadData();
  39. saveOwnData();
  40.  
  41. loadAttackLog();
  42.  
  43. if(site.indexOf('profiles.php') > 0) profileView();
  44.  
  45. })();
  46.  
  47. function loadAttackLog(){
  48. var selections = '';
  49. var now = new Date().getTime();
  50.  
  51. if('attackLogLoad' in data){
  52. if(data.attackLogLoad < now - (12*60*60*1000)) // More than 12 hours ago
  53. selections = 'attacksfull';
  54. else if(data.attackLogLoad < now - (2*60*1000)) // More than 2 minutes ago
  55. selection = 'attacks';
  56. }else // First time
  57. selections = 'attacksfull';
  58.  
  59. var url = 'https://api.torn.com/user/'+data.me.id+'?selections='+selections+'&key='+data.apikey;
  60. if(selections !== ''){
  61. data.attackLogLoad = now;
  62.  
  63. apiCall(url, function(d) {
  64. if(d.error) getApiKey();
  65. else{
  66. for(var p in d.attacks){
  67. if (d.attacks.hasOwnProperty(p)) {
  68. var attack = d.attacks[p];
  69. var defender_id = attack.defender_id;
  70. if(!(defender_id in data)) data[defender_id] = {};
  71.  
  72. if(attack.attacker_id == data.me.id){
  73. // My attack
  74. if(!('attacks' in data[defender_id])) data[defender_id].attacks = {};
  75. data[defender_id].attacks[p] = attack;
  76. }else if('attacker_id' in attack && attack.attacker_id in data){
  77. // I'm being attacked
  78. if(!('defends' in data[attack.attacker_id])) data[attack.attacker_id].defends = {};
  79. data[attack.attacker_id].defends[p] = attack;
  80. }
  81. }
  82. }
  83.  
  84. save();
  85. }
  86. });
  87. }
  88. }
  89.  
  90. function profileView(){
  91. var userid = getParameterByName('XID');
  92. var userData = data[userid];
  93. var content = '';
  94. content += profileViewSelectionPopUp();
  95. content += '<div id="compareStats">';
  96. content += 'Loading...';
  97. content += '<br />';
  98. content += '</div>';
  99.  
  100. var acrdHtml = accordion('Torn Helper', content);
  101.  
  102. var afterWait = function () {
  103. $(acrdHtml).insertAfter($('.profile-wrapper + .m-top10')[0]);
  104. var compareFunc = function(){
  105. $('#compareStats').replaceWith(compareTemplate(userid, data.me.userid));
  106. };
  107. apiUserStats(userid, compareFunc);
  108. apiUserStats(data.me.userid, compareFunc);
  109. };
  110. var wait = function () {
  111. var loaded = $('.profile-wrapper + .medals-wrapper + .m-top10').length > 0;
  112.  
  113. if (loaded) afterWait();
  114. else setTimeout(wait, 5);
  115. };
  116.  
  117. wait();
  118. }
  119.  
  120. // Only supports one accordion on the page at the moment.
  121. function accordion(title, block){
  122. var css = '<style>'+
  123. 'button.Jaccordion {'+
  124. 'cursor: pointer;'+
  125. 'padding: 18px;'+
  126. 'width: 100%;'+
  127. 'text-align: left;'+
  128. 'transition: 0.4s;'+
  129. '}'+
  130. 'div.Jpanel {'+
  131. 'position:relative;'+
  132. 'max-height: 0;'+
  133. 'overflow: hidden;'+
  134. 'transition: 0.8s ease-in-out;'+
  135. 'opacity: 0;'+
  136. '}'+
  137. 'div.Jshow {'+
  138. 'opacity: 1;'+
  139. 'max-height: 1000px;'+
  140. 'width: auto;'+
  141. '}'+
  142. 'div.JAccordionIconShow {'+
  143. 'opacity: 1;'+
  144. 'max-height: 30px;'+
  145. 'width: auto;'+
  146. '}'+
  147. '.JProfileViewAccordion{'+
  148. 'width: 0;'+
  149. 'float: right;'+
  150. 'max-height: 0;'+
  151. 'overflow: hidden;'+
  152. 'opacity: 0;'+
  153. 'padding-right: 7px;'+
  154. '}'+
  155. '.JAccordionIcon{'+
  156. 'padding: 6px 5px 6px 25px;'+
  157. 'cursor: pointer;'+
  158. '}'+
  159. '</style>';
  160.  
  161. var script = '<script>$(".JAccordionIcon").click(function() {'+
  162. '$(".Jpanel").toggleClass("Jshow");'+
  163. '$(".JProfileViewAccordion").toggleClass("JshJAccordionIconShowow");'+
  164. '$(".JProfileViewAccordion").toggleClass("JAccordionIconShow");'+
  165. '});</script>';
  166.  
  167. var show = data.profileview.show;
  168.  
  169. var html = '<div class="Jaccordion profile-wrapper medals-wrapper m-top10">'+
  170. '<div class="menu-header">'+title+
  171. '<div class="JProfileViewAccordion '+(show ? '' : 'JAccordionIconShow')+'"><i class="fa fa-plus-square JAccordionIcon" aria-hidden="true" /></div>'+
  172. '<div class="JProfileViewAccordion '+(show ? 'JAccordionIconShow' : '')+'"><i class="fa fa-minus-square JAccordionIcon" aria-hidden="true" /></div>'+
  173. '</div>'+
  174. '<div class="Jpanel '+(show ? 'Jshow' : '')+'">'+
  175. block+
  176. '</div>'+
  177. '</div>';
  178.  
  179. $(document).on('click','.JAccordionIcon', function(){
  180. data.profileview.show = !data.profileview.show;
  181. save();
  182. });
  183.  
  184. return css+script+html;
  185. }
  186.  
  187. function profileViewSelectionPopUp(){
  188. var possibilities = possibleStats();
  189. var settings = allSettings();
  190.  
  191. var popupHtml = '<div>';
  192. var categories = {};
  193. for(var p in possibilities){
  194. var o = possibilities[p];
  195. var checked = inArray(p, data.profileview.display) ? 'checked' : '';
  196.  
  197. if(o.category){
  198. if(!categories[o.category]) categories[o.category] = {display: o.category, html: ''};
  199. }else{
  200. if(!categories.others) categories.others = {display: 'Others', html: ''};
  201. }
  202.  
  203. var cat = o.category ? o.category : 'others';
  204.  
  205. var html = '<div style="';
  206. // TODO: Extract this css into a css rule
  207. html += 'border-radius: 2px; border: 1px solid gray; background-color: lightgray; margin: 3px;';
  208. html += 'padding: 3px 10px 3px 5px; display: inline-block; cursor: pointer;';
  209. html += '">';
  210. html += '<input type="checkbox" name="view'+p+'" id="view'+p+'" '+checked+'>';
  211. html += '<label id="JC'+p+'" for="view'+p+'">'+o.display+'</label>';
  212. html += '</div>';
  213. categories[cat].html += html;
  214. }
  215.  
  216. popupHtml += '<fieldset style="border:1px solid black; padding: 10px; margin: 10px 0;">';
  217. popupHtml += '<legend><b>Settings</b></legend>';
  218. for(var setting in settings){
  219. if(!settings.hasOwnProperty(setting)) continue;
  220. popupHtml += '<label for="setting'+setting+'">'+settings[setting].display+'</label>: ';
  221. switch(settings[setting].type){
  222. case 'checkbox':
  223. popupHtml += '<input type="'+settings[setting].type+'" name="setting'+setting+'" id="setting'+setting+'" '+(data.settings && data.settings.versusMine ? 'checked' : '')+'>';
  224. break;
  225. default:
  226. // TODO: Implement text!!!!
  227. }
  228. popupHtml += '<br>';
  229. }
  230. popupHtml += '</fieldset>';
  231.  
  232. for(var category in categories){
  233. if (categories.hasOwnProperty(category)) {
  234. popupHtml += '<fieldset style="border:1px solid black; padding: 10px; margin: 10px 0;">';
  235. popupHtml += '<legend onclick=\'document.getElementById("JC'+category+'").style.display = document.getElementById("JC'+category+'").style.display == "none" ? "block" : "none";\'';
  236. popupHtml += '><b>'+categories[category].display+'</b></legend>';
  237. popupHtml += categories[category].html;
  238. popupHtml += '</fieldset>';
  239. }
  240. }
  241.  
  242. popupHtml += '</div>';
  243. var popup = popupWindow(
  244. {
  245. element: '#editProfileView',
  246. title: 'Edit Profile view',
  247. width: 640
  248. },
  249. popupHtml,
  250. [
  251. {
  252. 'display': 'Close',
  253. 'close': true
  254. },
  255. {
  256. 'display': 'Save',
  257. 'callback': function(){
  258. var selected = [];
  259. for(var p in possibleStats()){
  260. var checked = $("#view"+p).is(':checked');
  261. if(checked) selected.push(p);
  262. }
  263. var settings = {};
  264. var all = allSettings();
  265. for(var setting in all){
  266. switch(all[setting].type){
  267. case 'checkbox':
  268. settings[setting] = $('#setting'+setting).is(':checked');
  269. break;
  270. default:
  271. settings[setting] = $('#setting'+setting).val();
  272. }
  273. }
  274.  
  275. data.profileview.display = selected;
  276. data.settings = settings;
  277. save();
  278. location.reload();
  279. }
  280. }
  281. ]
  282. );
  283.  
  284. var button = '<div style="float:right;right:0;position:absolute;">' +
  285. '<button id="editProfileView" style="';
  286. button += 'background-color: #282828;';
  287. button += 'border: none;';
  288. button += 'border-radius: 5px;';
  289. button += 'color: white;';
  290. button += 'padding: 5px 5px 5px 7px;';
  291. button += 'text-align: center;';
  292. button += 'text-decoration: none;';
  293. button += 'display: inline-block;';
  294. button += 'font-size: 16px;';
  295. button += 'margin: 4px 2px;';
  296. button += 'cursor: pointer;';
  297. button += '"><i class="fa fa-pencil-square-o" aria-hidden="true" /></button></div>';
  298.  
  299. return button+popup;
  300. }
  301.  
  302. /**
  303. * Example usage:
  304. * popupWindow(
  305. * { 'element': '#myDialogButton', 'title': 'Hello World' },
  306. * '<h3>Something</h3><input type="text" id="myfield" />',
  307. * [{'display': 'Save',
  308. * 'close': false, // default: true
  309. * callback: function(){var something = $("#myfield").val();}
  310. * }]);
  311. */
  312. function popupWindow(e, content, buttons){
  313. var script = '<script>$("'+e.element+'").click(function(){$("#dialog-message").dialog({'+
  314. 'modal: true,'+
  315. 'draggable: true,'+
  316. 'create: function(){$(this).css("maxHeight", $(window).height()-240);},'+
  317. 'resizable: true,'+
  318. 'position: [\'center\'],'+
  319. 'show: \'blind\','+
  320. 'hide: \'blind\','+
  321. 'width: '+(e.width ? e.width : 400)+','+
  322. 'buttons: [';
  323. var test = '';
  324. for(var i = 0; i < buttons.length; i++){
  325. script += '{';
  326. script += 'text: \''+buttons[i].display+'\',';
  327. script += 'id: \'dialogButton'+i+'\'';
  328. script += '},';
  329.  
  330. // this only supports up to 10 buttons.
  331. $(document).on('click','#dialogButton'+i, function(){
  332. var pressed = this.id.substr(this.id.length -1);
  333. if(buttons[pressed].callback)
  334. buttons[pressed].callback();
  335.  
  336. if(buttons[pressed].close)
  337. $("a.ui-dialog-titlebar-close")[0].click();
  338. });
  339. }
  340. script = script.slice(0, -1);
  341.  
  342. script += ']}';
  343. script += ');});'+test;
  344.  
  345. script += '</script>';
  346.  
  347. var html = '<div id="dialog-message" title="'+e.title+'" style="display: none; max-height: 80%;">';
  348. html += content;
  349. html += '</div>';
  350. return script+html;
  351. }
  352.  
  353. function inArray(c, a){
  354. // Somehow $.inArray is not working? ?
  355. for(var i = 0; i < a.length; i++){
  356. if(c == a[i]) return true;
  357. }
  358. return false;
  359. }
  360.  
  361. function getUserValue(userid, property){
  362. var user = data[userid];
  363. if(user){
  364. if($.isArray(property)){
  365. for(var i = 0; i < property.length; i++){
  366. user = user[property[i]];
  367. }
  368. return user;
  369. }else{
  370. var userData = user[property];
  371. if(userData){
  372. return userData;
  373. }
  374. }
  375. }
  376. return -1;
  377. }
  378.  
  379. function setUserValue(userid, property, value){
  380. if(data[userid] === undefined) data[userid] = {};
  381. data[userid][property] = value;
  382. }
  383.  
  384. function save(){
  385. localStorage.setItem('jebster.torn', JSON.stringify(data));
  386. }
  387.  
  388. function saveOwnData(){
  389. if(!('me' in data) || !('id' in data.me) || data.me.id < 1){
  390. var url = 'https://api.torn.com/user/'+data.me.id+'?selections=basic&key='+data.apikey;
  391. apiCall(url, function(d) {
  392. id = d.player_id;
  393. data.me = {'id': id};
  394. save();
  395. });
  396. }
  397. }
  398.  
  399. function loadData(){
  400. data = localStorage.getItem('jebster.torn');
  401. if(data === undefined || data === null){
  402. // Default settings
  403. data = {
  404. profileview:{
  405. show: true,
  406. display: ['xantaken','logins','refills','useractivity']
  407. }
  408. };
  409. }else{
  410. data = JSON.parse(data);
  411. }
  412.  
  413. if(data.apikey === undefined || data.apikey === ''){
  414. getApiKey();
  415. }
  416. }
  417.  
  418. var asked = false;
  419. function getApiKey(){
  420. if(asked) return; asked = true;
  421.  
  422.  
  423. var button = '<button id="JApiKeyBtn" style="';
  424. button += 'background-color: #282828;';
  425. button += 'border: none;';
  426. button += 'border-radius: 0 8px 8px 0;';
  427. button += 'color: white;';
  428. button += 'padding: 5px 5px 5px 6px;';
  429. button += 'text-align: center;';
  430. button += 'text-decoration: none;';
  431. button += 'display: inline-block;';
  432. button += 'font-size: 16px;';
  433. button += 'margin: 4px 0px;';
  434. button += 'cursor: pointer;';
  435. button += '"><i class="fa fa-floppy-o" aria-hidden="true"></i></button>';
  436.  
  437. var input = '<input type="text" id="JApiKeyInput" style="';
  438. input += 'border-radius: 8px 0 0 8px;';
  439. input += 'margin: 4px 0px;';
  440. input += 'padding: 5px;';
  441. input += 'font-size: 16px;';
  442. input += '" placeholder="ApiKey"></input>';
  443.  
  444. var block = '<div class="profile-wrapper medals-wrapper m-top10">';
  445. block += '<div class="menu-header">Torn Helper</div>';
  446. block += '<div class="profile-container"><div class="profile-container-description">';
  447. block += 'In order to use this script you need to enter your Torn Api Key, which you can '+
  448. 'get on your <a href="http://www.torn.com/preferences.php">preferences page</a> and under the \'API Key\' tab.<br />';
  449. block += input;
  450. block += button;
  451. block += '</div></div></div>';
  452.  
  453. $(block).insertAfter('.content-title');
  454.  
  455. $('#JApiKeyBtn').click(function(){
  456. var key = $("#JApiKeyInput").val();
  457. if(!('me' in data)) data.me = {};
  458. data.apikey = key;
  459. save();
  460. location.reload();
  461. });
  462.  
  463. }
  464.  
  465. function apiUserStats(userid, cb){
  466. var lastRequest = getUserValue(userid, 'lastRequest');
  467. var now = new Date();
  468. if(lastRequest === 0 || lastRequest < now.getTime() - (2*60*60*1000)){ // Every 2hours
  469. var selections = 'personalstats,basic,crimes';
  470. var url = 'https://api.torn.com/user/'+userid+'?selections='+selections+'&key='+data.apikey;
  471. apiCall(url, function(data) {
  472. if(data.error) getApiKey();
  473. else{
  474. setUserValue(userid, 'stats', data.personalstats);
  475. setUserValue(userid, 'lastRequest', now.getTime());
  476. setUserValue(userid, 'username', data.name);
  477. setUserValue(userid, 'gender', data.gender);
  478. setUserValue(userid, 'crimes', data.criminalrecord);
  479. save();
  480. cb(data);
  481. }
  482. });
  483. }else{
  484. cb(data[userid].stats);
  485. }
  486. }
  487.  
  488. function compareTemplate(user1Id, user2Id){
  489. var versusMe = data.settings && data.settings.versusMine;
  490.  
  491. var css = '<style>'+
  492. '.tornHelper{' +
  493. 'min-width:200px;' +
  494. '}' +
  495. '</style>';
  496.  
  497. var html = css+'<div class="profile-container basic-info"><ul class="basic-list">';
  498. html += '<li>';
  499. html += '<div class="user-information-section left"><span class="bold"></span></div>';
  500. html += '<div class="'+(versusMe ? 'user-information-section left' : '')+' tornHelper"><span class="bold">'+getUserValue(user1Id, 'username')+'</span></div>';
  501. html += versusMe ? '<div class="tornHelper"><span class="bold">'+getUserValue(user2Id, 'username')+' (You)</span></div>' : '';
  502. html += '</li>';
  503.  
  504. var stats = possibleStats();
  505. for(var i = 0; i < data.profileview.display.length; i++){
  506. var display = stats[data.profileview.display[i]];
  507. if(stats[data.profileview.display[i]]){
  508. var user1Value = 0, user2Value = 0;
  509. if(display.apiname){
  510. user1Value = getUserValue(user1Id, display.apiname);
  511. user2Value = getUserValue(user2Id, display.apiname);
  512. }else if(display.total){
  513. for(var j = 0; j < display.total.length; j++){
  514. user1Value += getUserValue(user1Id, display.total[j]);
  515. user2Value += getUserValue(user2Id, display.total[j]);
  516. }
  517. }else if(display.custom){
  518. user1Value = display.custom(user1Id);
  519. user2Value = display.custom(user2Id);
  520. }
  521. user1Value = user1Value ? user1Value : 0;
  522. user2Value = user2Value ? user2Value : 0;
  523.  
  524. if(display.format){
  525. user1Value = display.format(user1Value);
  526. user2Value = display.format(user2Value);
  527. }else{
  528. user1Value = formatNumber(user1Value);
  529. user2Value = formatNumber(user2Value);
  530. }
  531.  
  532. html += '<li>';
  533. html += '<div class="user-information-section left"><span class="bold">';
  534. html += display.display;
  535. if(display.tooltip){
  536. html += ' <i class="fa fa-question-circle" aria-hidden="true" title="'+display.tooltip+'" style="cursor: pointer;" />';
  537. }
  538. html += '</span></div>';
  539. html += '<div class="'+(versusMe ? 'user-information-section left' : '')+' tornHelper">';
  540. html += user1Value +'</div>';
  541. html += versusMe ? '<div class="tornHelper">'+user2Value+'</div>' : '';
  542. html += '</li>';
  543. }
  544. }
  545. html += '</ul><hr />';
  546.  
  547. var f = function(type, id){
  548. var attacks = {hosp: {display: 'Hosped', times: 0, other: 0}, mug: {display: 'Mugged', times: 0},
  549. left: {display: 'Left', times: 0}, lost: {display: 'Lost', times: 0}};
  550. if(type in data[id]){
  551. for(var p in data[id][type]){
  552. var attack = data[id][type][p];
  553. switch(attack.result){
  554. case 'Mug':
  555. attacks.mug.times++;
  556. break;
  557. case 'Hospitalize':
  558. attacks.hosp.times++;
  559. break;
  560. case 'Leave':
  561. attacks.left.times++;
  562. break;
  563. case 'Lose':
  564. attacks.lost.times++;
  565. break;
  566. }
  567. }
  568. }
  569. return attacks;
  570. };
  571. var attacks = f('attacks', user1Id);
  572. var defends = f('defends', user1Id);
  573.  
  574. var ahtml = '<ul class="basic-list">';
  575. var lis = '';
  576. var anyAttacks = false;
  577. for(var type in attacks){
  578. if(attacks[type].times > 0 || (defends[type].times > 0 && versusMe)){
  579. lis += '<li>';
  580. lis += '<div class="user-information-section left width112"><span class="bold">'+attacks[type].display+'</span></div>';
  581. lis += versusMe ? '<div class="user-information-section left tornHelper">'+defends[type].times+'</div>' : '';
  582. lis += '<div class="tornHelper">'+attacks[type].times+'</div>';
  583. lis += '</li>';
  584. anyAttacks = true;
  585. }
  586. }
  587. if(anyAttacks){
  588. var g = getUserValue(user1Id, 'gender');
  589. var gender = g == 'Male' ? 'him' : g == 'Female' ? 'her' : 'them';
  590. ahtml += '<li>';
  591. ahtml += '<div class="user-information-section left"><span class="bold">Attacks</span></div>';
  592. ahtml += versusMe ? '<div class="user-information-section left tornHelper"><span class="bold">Towards you</span></div>' : '';
  593. ahtml += '<div class="tornHelper"><span class="bold">'+(versusMe ? 'Towards '+gender : 'You made') +'</span></div>';
  594. ahtml += '</li>';
  595. }
  596. ahtml += lis;
  597. ahtml += '</ul></div>';
  598.  
  599. html += !anyAttacks ? 'You haven\'t attacked '+getUserValue(user1Id, 'username')+' before.' : '';
  600. html += ahtml;
  601. return html;
  602. }
  603.  
  604. function possibleStats(){
  605. return {
  606. attackswon:{apiname:['stats','attackswon'], display: 'Attacks won', category: 'Attacking'},
  607. attackslost:{apiname:['stats','attackslost'], display: 'Attacks lost', category: 'Attacking'},
  608. attacksdraw:{apiname:['stats','attacksdraw'], display: 'Attacks Draw', category: 'Attacking'},
  609. attacksassisted:{apiname:['stats','attacksassisted'], display: 'Attacks assisted', category: 'Attacking'},
  610. totalattacks:{total:[['stats','attackswon'],['stats','attackslost'],['stats','attacksdraw'],['stats','attacksassisted'],['stats','yourunaway']], display: 'Total attacks', category: 'Attacking', format:formatNumber},
  611. defendswon:{apiname:['stats','defendswon'], display: 'Defends won', category: 'Attacking'},
  612. defendslost:{apiname:['stats','defendslost'], display: 'Defends lost', category: 'Attacking'},
  613. defendsstalemated:{apiname:['stats','defendsstalemated'], display: 'Defends stalemated', category: 'Attacking'},
  614. yourunaway:{apiname:['stats','yourunaway'], display: 'Run Aways', category: 'Attacking'},
  615. theyrunaway:{apiname:['stats','theyrunaway'], display: 'Other Ran Away', category: 'Attacking'},
  616. bestkillstreak:{apiname:['stats','bestkillstreak'], display: 'Best Kill Streak', category: 'Attacking'},
  617. attackcriticalhits:{apiname:['stats','attackcriticalhits'], display: 'Attack Critical Hits', category: 'Attacking'},
  618. attackhits:{apiname:['stats','attackhits'], display: 'Attack Hits', category: 'Attacking'},
  619. attackmisses:{apiname:['stats','attackmisses'], display: 'Attack Misses', category: 'Attacking'},
  620. roundsfired:{apiname:['stats','roundsfired'], display: 'Rounds Fired', category: 'Attacking'},
  621. attacksstealthed:{apiname:['stats','attacksstealthed'], display: 'Attacks Stealthed', category: 'Attacking'},
  622. moneymugged:{apiname:['stats','moneymugged'], display: 'Money Mugged', category: 'Attacking', format: formatMoney},
  623. largestmug:{apiname:['stats','largestmug'], display: 'Largest Mug', category: 'Attacking', format: formatMoney},
  624. highestbeaten:{apiname:['stats','highestbeaten'], display: 'Highest Level Beaten', category: 'Attacking'},
  625. respectforfaction:{apiname:['stats','respectforfaction'], display: 'Respect For Faction', category: 'Attacking'},
  626.  
  627. itemsbought:{apiname:['stats','itemsbought'], display: 'Items Bought', category: 'Trading'},
  628. auctionswon:{apiname:['stats','auctionswon'], display: 'Auctions Won', category: 'Trading'},
  629. auctionsells:{apiname:['stats','auctionsells'], display: 'Auction Sells', category: 'Trading'},
  630. itemssent:{apiname:['stats','itemssent'], display: 'Items Sent', category: 'Trading'},
  631. trades:{apiname:['stats','trades'], display: 'Trades', category: 'Trading'},
  632. weaponsbought:{apiname:['stats','weaponsbought'], display: 'Weapons Bought', category: 'Trading'},
  633. pointssold:{apiname:['stats','pointssold'], display: 'Points Sold', category: 'Trading'},
  634. pointsbought:{apiname:['stats','pointsbought'], display: 'Points Bought', category: 'Trading'},
  635. bazaarcustomers:{apiname:['stats','bazaarcustomers'], display: 'Bazaar Customers', category: 'Trading'},
  636. bazaarsales:{apiname:['stats','bazaarsales'], display: 'Bazaar Sales', category: 'Trading'},
  637. bazaarprofit:{apiname:['stats','bazaarprofit'], display: 'Bazaar Profit', category: 'Trading', format: formatMoney},
  638. jailed:{apiname:['stats','jailed'], display: 'Jailed', category: 'Jail'},
  639. peoplebusted:{apiname:['stats','peoplebusted'], display: 'People Busted', category: 'Jail'},
  640. failedbusts:{apiname:['stats','failedbusts'], display: 'Failed Busts', category: 'Jail'},
  641. peoplebought:{apiname:['stats','peoplebought'], display: 'People Bought out of Jail', category: 'Jail'},
  642. peopleboughtspent:{apiname:['stats','peopleboughtspent'], display: 'Money Spent on buying people out of jail', category: 'Jail', format: formatMoney}, // TODO: Some shorter display text
  643. hospital:{apiname:['stats','hospital'], display: 'Hospital', category: 'Hospital'}, // TODO:
  644. medicalitemsused:{apiname:['stats','medicalitemsused'], display: 'Medical Items Used', category: 'Hospital'},
  645. bloodwithdrawn:{apiname:['stats','bloodwithdrawn'], display: 'Blood Withdrawn', category: 'Hospital'},
  646. revives:{apiname:['stats','revives'], display: 'Revives', category: 'Hospital'},
  647. revivesreceived:{apiname:['stats','revivesreceived'], display: 'Revives Received', category: 'Hospital'},
  648. medstolen:{apiname:['stats','medstolen'], display: 'Medical Items Stolen', category: 'Hospital'},
  649. heahits:{apiname:['stats','heahits'], display: 'Heavy artillery', category: 'Finishing Hits'},
  650. machits:{apiname:['stats','machits'], display: 'Machine guns', category: 'Finishing Hits'},
  651. rifhits:{apiname:['stats','rifhits'], display: 'Rifles', category: 'Finishing Hits'},
  652. smghits:{apiname:['stats','smghits'], display: 'Sub machine guns', category: 'Finishing Hits'},
  653. shohits:{apiname:['stats','shohits'], display: 'Shotguns', category: 'Finishing Hits'},
  654. pishits:{apiname:['stats','pishits'], display: 'Pistols', category: 'Finishing Hits'},
  655. grehits:{apiname:['stats','grehits'], display: 'Temporary weapons', category: 'Finishing Hits'},
  656. piehits:{apiname:['stats','piehits'], display: 'Piercing weapons', category: 'Finishing Hits'},
  657. slahits:{apiname:['stats','slahits'], display: 'Slashing weapons', category: 'Finishing Hits'},
  658. axehits:{apiname:['stats','axehits'], display: 'Clubbed weapons', category: 'Finishing Hits'},
  659. chahits:{apiname:['stats','chahits'], display: 'Mechanical weapons', category: 'Finishing Hits'},
  660. mailssent:{apiname:['stats','mailssent'], display: 'Mail Sent', category: 'Communication'},
  661. friendmailssent:{apiname:['stats','friendmailssent'], display: 'Friend Mail Sent', category: 'Communication'},
  662. factionmailssent:{apiname:['stats','factionmailssent'], display: 'Faction Mail Sent', category: 'Communication'},
  663. companymailssent:{apiname:['stats','companymailssent'], display: 'Company Mail Sent', category: 'Communication'},
  664. spousemailssent:{apiname:['stats','spousemailssent'], display: 'Spouse Mail Sent', category: 'Communication'},
  665. classifiedadsplaced:{apiname:['stats','classifiedadsplaced'], display: 'Classified Newspaper Ads Placed', category: 'Communication'},
  666. personalsplaced:{apiname:['stats','personalsplaced'], display: 'Personal Placed', category: 'Communication'},
  667. bountiesplaced:{apiname:['stats','bountiesplaced'], display: 'Bounties Placed', category: 'Bounties'},
  668. totalbountyspent:{apiname:['stats','totalbountyspent'], display: 'Total Bounty Money Spent', category: 'Bounties', format: formatMoney},
  669. bountiescollected:{apiname:['stats','bountiescollected'], display: 'Bounties Collected', category: 'Bounties'},
  670. totalbountyreward:{apiname:['stats','totalbountyreward'], display: 'Total Bounty Money Gained', category: 'Bounties', format: formatMoney},
  671. bountiesreceived:{apiname:['stats','bountiesreceived'], display: 'Bounties Received', category: 'Bounties'},
  672. cityfinds:{apiname:['stats','cityfinds'], display: 'City Finds', category: 'Items'},
  673. itemsdumped:{apiname:['stats','itemsdumped'], display: 'Items Dumped', category: 'Items'},
  674. dumpsearches:{apiname:['stats','dumpsearches'], display: 'Dump Searches', category: 'Items'},
  675. dumpfinds:{apiname:['stats','dumpfinds'], display: 'Dump Finds', category: 'Items'},
  676. traveltimes:{apiname:['stats','traveltimes'], display: 'Travel Times', category: 'Travel'},
  677. itemsboughtabroad:{apiname:['stats','itemsboughtabroad'], display: 'Items Bought Abroad', category: 'Travel'},
  678. argtravel:{apiname:['stats','argtravel'], display: 'Argentina Traveled', category: 'Travel'},
  679. mextravel:{apiname:['stats','mextravel'], display: 'Mexico Traveled', category: 'Travel'},
  680. dubtravel:{apiname:['stats','dubtravel'], display: 'Dubai Traveled', category: 'Travel'},
  681. hawtravel:{apiname:['stats','hawtravel'], display: 'Hawaii Traveled', category: 'Travel'},
  682. japtravel:{apiname:['stats','japtravel'], display: 'Japan Traveled', category: 'Travel'},
  683. lontravel:{apiname:['stats','lontravel'], display: 'London Traveled', category: 'Travel'},
  684. soutravel:{apiname:['stats','soutravel'], display: 'South Africa Traveled', category: 'Travel'},
  685. switravel:{apiname:['stats','switravel'], display: 'Switzerland Traveled', category: 'Travel'},
  686. chitravel:{apiname:['stats','chitravel'], display: 'China Traveled', category: 'Travel'},
  687. cantravel:{apiname:['stats','cantravel'], display: 'Canada Traveled', category: 'Travel'},
  688. caytravel:{apiname:['stats','caytravel'], display: 'Cayman Islands Traveled', category: 'Travel'},
  689. drugsused:{apiname:['stats','drugsused'], display: 'Drug Used', category: 'Drugs'},
  690. overdosed:{apiname:['stats','overdosed'], display: 'Drug Overses', category: 'Drugs'},
  691. cantaken:{apiname:['stats','cantaken'], display: 'Canabis Taken', category: 'Drugs'},
  692. exttaken:{apiname:['stats','exttaken'], display: 'Ecstasy Taken', category: 'Drugs'},
  693. kettaken:{apiname:['stats','kettaken'], display: 'Ketamine Taken', category: 'Drugs'},
  694. lsdtaken:{apiname:['stats','lsdtaken'], display: 'LSD Taken', category: 'Drugs'},
  695. opitaken:{apiname:['stats','opitaken'], display: 'Opium Taken', category: 'Drugs'},
  696. shrtaken:{apiname:['stats','shrtaken'], display: 'Shrooms Taken', category: 'Drugs'},
  697. spetaken:{apiname:['stats','spetaken'], display: 'Speed Taken', category: 'Drugs'},
  698. pcptaken:{apiname:['stats','pcptaken'], display: 'PCP Taken', category: 'Drugs'},
  699. xantaken:{apiname:['stats','xantaken'], display: 'Xanax Taken', category: 'Drugs'},
  700. victaken:{apiname:['stats','victaken'], display: 'Vicodin Taken', category: 'Drugs'},
  701. networth:{apiname:['stats','networth'], display: 'Networth', format: formatMoney},
  702. logins:{apiname:['stats','logins'], display: 'Logins'},
  703. useractivity:{apiname:['stats','useractivity'], display: 'Time Played', format: formatSeconds},
  704. meritsbought:{apiname:['stats','meritsbought'], display: 'Merits Bought'},
  705. refills:{apiname:['stats','refills'], display: 'Refills'},
  706. trainsreceived:{apiname:['stats','trainsreceived'], display: 'Trains Received'},
  707. spydone:{apiname:['stats','spydone'], display: 'Spies Done'},
  708. statenhancersused:{apiname:['stats','statenhancersused'], display: 'Stat Enhancers Used'},
  709. virusescoded:{apiname:['stats','virusescoded'], display: 'Viruses Coded'},
  710. daysbeendonator:{apiname:['stats','daysbeendonator'], display: 'Days Been Donator'},
  711. missionscompleted:{apiname:['stats','missionscompleted'], display: 'Missions Completed', category: 'Missions'},
  712. contractscompleted:{apiname:['stats','contractscompleted'], display: 'Contracts Completed', category: 'Missions'},
  713. dukecontractscompleted:{apiname:['stats','dukecontractscompleted'], display: 'Duke Contracts Completed', category: 'Missions'},
  714. missioncreditsearned:{apiname:['stats','missioncreditsearned'], display: 'Mission Credits Earned', category: 'Missions'},
  715. sellingillegalproducts:{apiname:['crimes','selling_illegal_products'], display: 'Illegal Products Sold', category: 'Crimes'},
  716. theft:{apiname:['crimes','theft'], display: 'Theft', category: 'Crimes'},
  717. auto_theft:{apiname:['crimes','auto_theft'], display: 'Auto Theft', category: 'Crimes'},
  718. drug_deals:{apiname:['crimes','drug_deals'], display: 'Drug Deals', category: 'Crimes'},
  719. computer_crimes:{apiname:['crimes','computer_crimes'], display: 'Computer Crimes', category: 'Crimes'},
  720. murder:{apiname:['crimes','murder'], display: 'Murder', category: 'Crimes'},
  721. fraud_crimes:{apiname:['crimes','fraud_crimes'], display: 'Fraud Crimes', category: 'Crimes'},
  722. other:{apiname:['crimes','other'], display: 'Other Crimes', category: 'Crimes'},
  723. total:{apiname:['crimes','total'], display: 'Total Crimes', category: 'Crimes'},
  724. awards:{display: 'Awards', custom: function(id){return getAwards(id);}, tooltip: 'Your award count only updates when you visit your own profile.'},
  725. point:{display:'Point', custom: function(id){
  726. var x = getUserValue(id, ['stats', 'attackswon'])+getUserValue(id,['stats','attackslost']);
  727. x += getUserValue(id,['stats','attacksdraw'])+getUserValue(id,['stats','yourunaway']);
  728. var y = getUserValue(id, ['stats','xantaken']);
  729. return (y*250) - (x*25);
  730. }, category: 'Requests'},
  731. };
  732. }
  733.  
  734. function allSettings(){
  735. return {
  736. versusMine: {display: 'Show my stats', type:'checkbox'}
  737. };
  738. }
  739.  
  740. function apiCall(url, cb){
  741. console.log('Torn helper: making request \''+url+'\'');
  742. $.ajax({
  743. url: url,
  744. type: 'GET',
  745. success: function(data) {
  746. cb(data);
  747. }
  748. });
  749. }
  750.  
  751. function getAwards(id){
  752. var element = $(".profile-container.basic-info.bottom-round ul:nth-child(2) li:nth-child(8)");
  753. var content = element.first().contents().filter(function(){
  754. return this.nodeType == 3;
  755. });
  756. var value = parseInt(content.text().trim());
  757.  
  758. if(!id){
  759. if(data.me.awards)
  760. return data.me.awards;
  761. return -1;
  762. }
  763. if(id == data.me.id){
  764. data.me.awards = value;
  765. save();
  766. }
  767. return value;
  768. }
  769.  
  770. function removeFirstAndLastLine(text){
  771. var lines = text.split('\n');
  772. lines.splice(0,1);
  773. lines.splice(-1,1);
  774. var newtext = lines.join('\n');
  775. }
  776.  
  777. function formatSeconds(s){
  778. var minutes = Math.floor(s/60)%60;
  779. var hours = Math.floor(s/(60*60))%24;
  780. var days = Math.floor(s/(60*60*24));
  781. var seconds = s%60;
  782.  
  783. return '{0}d {1}h {2}m {3}s'.format(days, hours, minutes, seconds);
  784. }
  785.  
  786. function formatNumber(n){
  787. return n.toString().replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1,");
  788. }
  789.  
  790. function formatMoney(m){
  791. return '$'+formatNumber(m);
  792. }
  793.  
  794. // Taken from: http://stackoverflow.com/a/15724300/1832471
  795. function getCookieValue(name) {
  796. var nameEQ = name + "=";
  797. var ca = document.cookie.split(';');
  798. for(var i=0;i < ca.length;i++) {
  799. var c = ca[i];
  800. while (c.charAt(0)==' ') c = c.substring(1,c.length);
  801. if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
  802. }
  803. return null;
  804. }
  805.  
  806. // Taken from: http://stackoverflow.com/a/901144/1832471
  807. function getParameterByName(name, url) {
  808. if (!url) {
  809. url = window.location.href;
  810. }
  811. name = name.replace(/[\[\]]/g, "\\$&");
  812. var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
  813. results = regex.exec(url);
  814. if (!results) return null;
  815. if (!results[2]) return '';
  816. return decodeURIComponent(results[2].replace(/\+/g, " "));
  817. }
  818.