Torn SpyParse

Parse spy reports & save them in local storage

安装此脚本?
作者推荐脚本

你可能也喜欢 Torn Bazaar Filler

安装此脚本
  1. // ==UserScript==
  2. // @name Torn SpyParse
  3. // @namespace https://github.com/SOLiNARY
  4. // @version 0.3.5.pda
  5. // @description Parse spy reports & save them in local storage
  6. // @author Ramin Quluzade, Silmaril [2665762]
  7. // @license MIT License
  8. // @match https://www.torn.com/jobs.php*
  9. // @match https://www.torn.com/companies.php*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=torn.com
  11. // @grant GM_addStyle
  12. // ==/UserScript==
  13.  
  14. (async function () {
  15. 'use strict';
  16.  
  17. const isTampermonkeyEnabled = typeof unsafeWindow !== 'undefined';
  18.  
  19. const SpyJobs = {
  20. Army: 10,
  21. LawFirm: 20,
  22. TelevisionNetwork: 30,
  23. None: 0
  24. }
  25. const SpyJobsMapping = {
  26. "Type: Army": SpyJobs.Army,
  27. "Type: Law Firm": SpyJobs.LawFirm,
  28. "Type: Television Network": SpyJobs.TelevisionNetwork
  29. }
  30. let playerJob = 20;
  31. let spyReports = {};
  32.  
  33. const viewPortWidthPx = window.innerWidth;
  34. const isMobileView = viewPortWidthPx <= 784;
  35.  
  36. const styles = `
  37. div#spy-parse-container {
  38. font-family: Verdana, Geneva, sans-serif;
  39. background-color: #CCC;
  40. text-align: center;
  41. }
  42.  
  43. #spy-parse-btn {
  44. right: 20px;
  45. z-index: 99999;
  46. }
  47.  
  48. #spy-copy-btn {
  49. right: 180px;
  50. z-index: 99999;
  51. }
  52.  
  53. .float {
  54. width: 90px;
  55. height: 50px;
  56. margin: 0 auto;
  57. padding: 0;
  58. display: inline-block;
  59. line-height: 50px;
  60. text-align: center;
  61. top: 40px;
  62. text-decoration: none;
  63. position: fixed;
  64. padding-left: 20px;
  65. padding-right: 20px;
  66. background-color: #00144e;
  67. color: #FFF;
  68. border-radius: 50px;
  69. box-shadow: 2px 2px 3px #999;
  70. font-size: 18px;
  71. }
  72.  
  73. .my-float {
  74. margin-top: 22px;
  75. }
  76.  
  77. #spy-parse-tbl {
  78. position: fixed;
  79. top: 103px;
  80. right: 40px;
  81. font-family: arial, sans-serif;
  82. font-size: xx-small;
  83. border-collapse: collapse;
  84. width: auto;
  85. }
  86.  
  87. #spy-parse-tbl tr {
  88. transition: background-color 400ms cubic-bezier(0.4, 0, 0.2, 1);;
  89. }
  90.  
  91. #spy-parse-tbl tr td[data-level], td[data-updated] {
  92. display: none;
  93. }
  94.  
  95. #spy-parse-tbl td, th {
  96. border: 1px solid #dddddd;
  97. text-align: left;
  98. padding: 8px;
  99. transition: background-color 400ms cubic-bezier(0.4, 0, 0.2, 1);;
  100. }
  101.  
  102. #spy-parse-tbl tbody tr:nth-child(even) {
  103. background-color: #d1d1d1;
  104. }
  105.  
  106. #spy-parse-tbl tbody tr:nth-child(odd) {
  107. background-color: white;
  108. }
  109. `;
  110.  
  111. if (isTampermonkeyEnabled){
  112. GM_addStyle(styles);
  113. } else {
  114. let style = document.createElement("style");
  115. style.type = "text/css";
  116. style.innerHTML = styles;
  117. while (document.head == null){
  118. await sleep(50);
  119. }
  120. document.head.appendChild(style);
  121. }
  122.  
  123. let zNode = document.createElement('div');
  124. zNode.innerHTML = `<a id="spy-copy-btn" href="#" class="float">Copy <i class="fa fa-copy my-float"></i></a><a id="spy-parse-btn" href="#" class="float">Parse <i class="fa fa-search my-float"></i></a><table style="display:none;" id="spy-parse-tbl"><thead><tr><th>Name</th><th ${isMobileView ? 'style="display: none;"' : ''}>Level</th><th>Strength</th><th>Defense</th><th>Speed</th><th>Dexterity</th><th>Total</th><th ${isMobileView ? 'style="display: none;"' : ''}>Updated</th></tr></thead><tbody></tbody>`;
  125. zNode.setAttribute('id', 'spy-parse-container');
  126. document.body.appendChild(zNode);
  127. document.getElementById("spy-parse-btn").addEventListener("click", GetSpyResult, false);
  128. document.getElementById("spy-copy-btn").addEventListener("click", CopySpyResults, false);
  129.  
  130. // if (location.pathname.startsWith("/jobs.php")) {
  131. // playerJob = SpyJobs.Army;
  132. // } else if (location.pathname.startsWith("/companies.php")) {
  133. // let jobTitleBlock = document.querySelector("#mainContainer > div.content-wrapper > div.company-wrap > div.company-details-wrap > ul.company-stats-list.company-info > li:nth-child(1) > div.details-wrap.t-first.t-first-row");
  134. // if (jobTitleBlock) {
  135. // try {
  136. // playerJob = SpyJobsMapping[jobTitleBlock.innerText];
  137. // } catch (error) {
  138. // playerJob = SpyJobs.None;
  139. // }
  140. // }
  141. // } else {
  142. // playerJob = SpyJobs.None;
  143. // }
  144. console.log("Player job: %s", playerJob);
  145. if (spyReports == undefined) {
  146. spyReports = {};
  147. }
  148.  
  149. function CopySpyResults(zEvent) {
  150. zEvent.preventDefault();
  151. let copyBtn = zEvent.target;
  152. let copyBtnHtml = copyBtn.innerHTML;
  153. const spyReportTemplate = (x) =>
  154. `
  155. Name: ${x.Name}
  156. Level: ${x.Level}
  157. You managed to get the following results:
  158. Strength: ${x.StrengthPrettified}
  159. Speed: ${x.SpeedPrettified}
  160. Dexterity: ${x.DexterityPrettified}
  161. Defense: ${x.DefensePrettified}
  162. Total: ${x.TotalPrettified}
  163. `;
  164. let spyReportsResult = "";
  165. for (let key in spyReports) {
  166. spyReportsResult += spyReportTemplate(spyReports[key]);
  167. }
  168. if (Object.entries(spyReports).length > 0) {
  169. navigator.clipboard.writeText(spyReportsResult)
  170. .then(function () {
  171. copyBtn.innerHTML = 'Copied! <i class="fa fa-copy my-float"></i>';
  172. }, function () {
  173. copyBtn.innerHTML = 'Failed! <i class="fa fa-copy my-float"></i>';
  174. });
  175. } else {
  176. copyBtn.innerHTML = 'Empty! <i class="fa fa-copy my-float"></i>';
  177. }
  178. setTimeout(() => {
  179. copyBtn.innerHTML = copyBtnHtml;
  180. }, 1000);
  181. }
  182.  
  183. function GetSpyResult(zEvent) {
  184. console.log('playerJob', playerJob);
  185. zEvent.preventDefault();
  186. let parseBtn = zEvent.target;
  187. let parseBtnHtml = parseBtn.innerHTML;
  188. try {
  189. let spyProfile = GetSpyProfile();
  190. AddSpyProfile(spyProfile);
  191. parseBtn.innerHTML = 'Parsed! <i class="fa fa-search my-float"></i>';
  192. let spyTable = document.querySelector("#spy-parse-tbl");
  193. spyTable.style.display = 'block';
  194. } catch (error) {
  195. console.error(error);
  196. parseBtn.innerHTML = 'Failed! <i class="fa fa-search my-float"></i>';
  197. }
  198. setTimeout(() => {
  199. parseBtn.innerHTML = parseBtnHtml;
  200. }, 1000);
  201. }
  202.  
  203. function GetSpyProfile() {
  204. console.log('get spy profile begin');
  205. let jobSpecialBlock, userLink, levelSpan;
  206. switch (playerJob) {
  207. default:
  208. case SpyJobs.Army:
  209. jobSpecialBlock = document.getElementsByName("jobspecial")[0];
  210. userLink = jobSpecialBlock.querySelector("div > div:nth-child(3) > div > span.desc > a");
  211. levelSpan = jobSpecialBlock.querySelector("div > div:nth-child(3) > div:nth-child(2) > span.desc");
  212. break;
  213. case SpyJobs.LawFirm:
  214. case SpyJobs.TelevisionNetwork:
  215. console.log('jobSpecialBlock1', document.getElementsByClassName("specials-cont-wrap")[0]);
  216. console.log('jobSpecialBlock2', document.getElementsByClassName("specials-cont-wrap")[0].querySelector("div.specials-confirm-cont"));
  217. jobSpecialBlock = document.getElementsByClassName("specials-cont-wrap")[0].querySelector("div.specials-confirm-cont");
  218. console.log('userLink', jobSpecialBlock.querySelector("div > div:nth-child(2) > div > span.desc > a"));
  219. userLink = jobSpecialBlock.querySelector("div > div:nth-child(2) > div > span.desc > a");
  220. console.log('levelSpan', jobSpecialBlock.querySelector("div > div:nth-child(2) > div:nth-child(2) > span.desc"));
  221. levelSpan = jobSpecialBlock.querySelector("div > div:nth-child(2) > div:nth-child(2) > span.desc");
  222. break;
  223. }
  224. console.log('constructing spy profile');
  225.  
  226. let id = Number(userLink.href.substr(userLink.href.search("XID=") + 4));
  227. let name = userLink.innerText;
  228. let level = Number(levelSpan.innerText);
  229. let strength = 0;
  230. let defense = 0;
  231. let speed = 0;
  232. let dexterity = 0;
  233. let total = 0;
  234. if (playerJob == SpyJobs.TelevisionNetwork || playerJob == SpyJobs.LawFirm) {
  235. let statOffset = playerJob == SpyJobs.TelevisionNetwork ? 1 : 0;
  236. let statsBlock = jobSpecialBlock.querySelector("div > ul");
  237. strength = Number(statsBlock.children[0 + statOffset].innerText.substr(10).replaceAll(',', ''));
  238. if (isNaN(strength)) strength = 0;
  239. defense = Number(statsBlock.children[3 + statOffset].innerText.substr(9).replaceAll(',', ''));
  240. if (isNaN(defense)) defense = 0;
  241. speed = Number(statsBlock.children[1 + statOffset].innerText.substr(7).replaceAll(',', ''));
  242. if (isNaN(speed)) speed = 0;
  243. dexterity = Number(statsBlock.children[2 + statOffset].innerText.substr(11).replaceAll(',', ''));
  244. if (isNaN(dexterity)) dexterity = 0;
  245. total = Number(statsBlock.children[4 + statOffset].innerText.substr(7).replaceAll(',', ''));
  246. if (isNaN(total)) total = 0;
  247. } else if (playerJob == SpyJobs.Army) {
  248. let statsBlock = jobSpecialBlock.querySelector("div.specials-confirm-cont > div:nth-child(5) > ul");
  249. let strengthBlock = statsBlock.querySelector("li.left.t-c-border");
  250. if (strengthBlock.innerText.search("Strength:") > -1) {
  251. strength = Number(strengthBlock.getElementsByClassName('desc')[0].innerText.replaceAll(',', ''));
  252. if (isNaN(strength)) strength = 0;
  253. }
  254. let defenseBlock = statsBlock.querySelector("li.left.t-r-border");
  255. if (defenseBlock.innerText.search("Defense:") > -1) {
  256. defense = Number(defenseBlock.getElementsByClassName('desc')[0].innerText.replaceAll(',', ''));
  257. if (isNaN(defense)) defense = 0;
  258. }
  259. let speedBlock = statsBlock.querySelector("li.left.t-l-border");
  260. if (speedBlock.innerText.search("Speed:") > -1) {
  261. speed = Number(speedBlock.getElementsByClassName('desc')[0].innerText.replaceAll(',', ''));
  262. if (isNaN(speed)) speed = 0;
  263. }
  264. let dexterityBlock = statsBlock.querySelector("li.left.b-l-border");
  265. if (dexterityBlock.innerText.search("Dexterity:") > -1) {
  266. dexterity = Number(dexterityBlock.getElementsByClassName('desc')[0].innerText.replaceAll(',', ''));
  267. if (isNaN(dexterity)) dexterity = 0;
  268. }
  269. let totalBlock = statsBlock.querySelector("li.left.b-c-border");
  270. if (totalBlock.innerText.search("Total:") > -1) {
  271. total = Number(totalBlock.getElementsByClassName('desc')[0].innerText.replaceAll(',', ''));
  272. if (isNaN(total)) total = 0;
  273. }
  274. }
  275. console.log('get spy profile end');
  276.  
  277. return new SpyReport(id, name, level, strength, defense, speed, dexterity, total, new Date());
  278. }
  279.  
  280. function AddSpyProfile(spyProfile) {
  281. console.log('add spy profile begin');
  282. const profileTemplate = (x) => `<td data-name>${x.Name}</td><td style="${isMobileView ? "display: none;" : ''}" data-level=${x.Level}>${x.Level}</td><td data-strength=${x.Strength}>${x.StrengthPrettified}</td><td data-defense=${x.Defense}>${x.DefensePrettified}</td><td data-speed=${x.Speed}>${x.SpeedPrettified}</td><td data-dexterity=${x.Dexterity}>${x.DexterityPrettified}</td><td data-total=${x.Total}>${x.TotalPrettified}</td><td style="${isMobileView ? "display: none;" : ''}" data-updated=${x.UpdatedTimeStamp}>${x.UpdatedDate}</td>`;
  283.  
  284. let spyTableBody = document.querySelector('#spy-parse-tbl tbody');
  285. let userRow = spyTableBody.querySelector(`tr[data-id="${spyProfile.Id}"]`);
  286. if (userRow == null) {
  287. let userNode = document.createElement('tr');
  288. userNode.innerHTML = profileTemplate(spyProfile);
  289. userNode.setAttribute('data-id', spyProfile.Id);
  290. spyTableBody.appendChild(userNode);
  291. FlashElement(userNode);
  292. } else {
  293. let level = userRow.querySelector('td[data-level]');
  294. let strength = userRow.querySelector('td[data-strength]');
  295. let defense = userRow.querySelector('td[data-defense]');
  296. let speed = userRow.querySelector('td[data-speed]');
  297. let dexterity = userRow.querySelector('td[data-dexterity]');
  298. let total = userRow.querySelector('td[data-total]');
  299. let updated = userRow.querySelector('td[data-updated]');
  300.  
  301. let existingSpyProfile = spyReports[spyProfile.Id];
  302. if (spyProfile.Strength < existingSpyProfile.Strength) spyProfile.Strength = existingSpyProfile.Strength;
  303. if (spyProfile.Defense < existingSpyProfile.Defense) spyProfile.Defense = existingSpyProfile.Defense;
  304. if (spyProfile.Speed < existingSpyProfile.Speed) spyProfile.Speed = existingSpyProfile.Speed;
  305. if (spyProfile.Dexterity < existingSpyProfile.Dexterity) spyProfile.Dexterity = existingSpyProfile.Dexterity;
  306. if (spyProfile.Total < existingSpyProfile.Total) spyProfile.Total = existingSpyProfile.Total;
  307. spyProfile.calculateStats();
  308.  
  309. if (Number(level.getAttribute('data-level')) !== spyProfile.Level) {
  310. level.setAttribute('data-level', spyProfile.Level);
  311. level.innerText = spyProfile.Level;
  312. FlashElement(level);
  313. }
  314. if (Number(strength.getAttribute('data-strength')) !== spyProfile.Strength) {
  315. strength.setAttribute('data-strength', spyProfile.Strength);
  316. strength.innerText = spyProfile.StrengthPrettified;
  317. FlashElement(strength);
  318. }
  319. if (Number(defense.getAttribute('data-defense')) !== spyProfile.Defense) {
  320. defense.setAttribute('data-defense', spyProfile.Defense);
  321. defense.innerText = spyProfile.DefensePrettified;
  322. FlashElement(defense);
  323. }
  324. if (Number(speed.getAttribute('data-speed')) !== spyProfile.Speed) {
  325. speed.setAttribute('data-speed', spyProfile.Speed);
  326. speed.innerText = spyProfile.SpeedPrettified;
  327. FlashElement(speed);
  328. }
  329. if (Number(dexterity.getAttribute('data-dexterity')) !== spyProfile.Dexterity) {
  330. dexterity.setAttribute('data-dexterity', spyProfile.Dexterity);
  331. dexterity.innerText = spyProfile.DexterityPrettified;
  332. FlashElement(dexterity);
  333. }
  334. if (Number(total.getAttribute('data-total')) !== spyProfile.Total) {
  335. total.setAttribute('data-total', spyProfile.Total);
  336. total.innerText = spyProfile.TotalPrettified;
  337. FlashElement(total);
  338. }
  339. if (updated.getAttribute('data-updated') !== spyProfile.UpdatedTimeStamp) {
  340. updated.setAttribute('data-updated', spyProfile.UpdatedTimeStamp);
  341. updated.innerText = spyProfile.UpdatedDate;
  342. FlashElement(updated);
  343. }
  344. }
  345. localStorage.setItem(`spy-parse-${spyProfile.Id}`, JSON.stringify(spyProfile));
  346. spyReports[spyProfile.Id] = spyProfile;
  347. console.log('get spy profile end');
  348. }
  349.  
  350. function FlashElement(element) {
  351. let previousBackgroundColor = element.style.backgroundColor;
  352. setTimeout(() => {
  353. element.style.backgroundColor = "green";
  354. }, 10);
  355. setTimeout(() => {
  356. element.style.backgroundColor = previousBackgroundColor;
  357. }, 400);
  358. }
  359.  
  360. function sleep(ms) {
  361. return new Promise(resolve => setTimeout(resolve, ms));
  362. }
  363.  
  364. class SpyReport {
  365. Id = 0;
  366. Name = '';
  367. Level = 0;
  368. Strength = 0;
  369.  
  370. get StrengthPrettified() {
  371. return this.Strength.toLocaleString('EN');
  372. }
  373.  
  374. Defense = 0;
  375.  
  376. get DefensePrettified() {
  377. return this.Defense.toLocaleString('EN');
  378. }
  379.  
  380. Speed = 0;
  381.  
  382. get SpeedPrettified() {
  383. return this.Speed.toLocaleString('EN');
  384. }
  385.  
  386. Dexterity = 0;
  387.  
  388. get DexterityPrettified() {
  389. return this.Dexterity.toLocaleString('EN');
  390. }
  391.  
  392. Total = 0;
  393.  
  394. get TotalPrettified() {
  395. return this.Total.toLocaleString('EN');
  396. }
  397.  
  398. UpdatedTimeStamp = new Date(0);
  399.  
  400. get UpdatedDate() {
  401. return this.UpdatedTimeStamp.toLocaleDateString('RU');
  402. }
  403.  
  404. constructor(id, name, level, strength, defense, speed, dexterity, total, updated) {
  405. this.Id = id;
  406. this.Name = name;
  407. this.Level = level;
  408. this.Strength = strength;
  409. this.Defense = defense;
  410. this.Speed = speed;
  411. this.Dexterity = dexterity;
  412. this.Total = total;
  413. this.UpdatedTimeStamp = updated;
  414. this.calculateStats();
  415. }
  416.  
  417. calculateStats() {
  418. let statsKnown = Number(this.Strength > 0) + Number(this.Defense > 0) + Number(this.Speed > 0) + Number(this.Dexterity > 0) + Number(this.Total > 0);
  419. if (statsKnown === 4) {
  420. if (Number(this.Strength === 0)) this.Strength = this.Total - this.Defense - this.Speed - this.Dexterity;
  421. if (Number(this.Defense === 0)) this.Defense = this.Total - this.Strength - this.Speed - this.Dexterity;
  422. if (Number(this.Speed === 0)) this.Speed = this.Total - this.Strength - this.Defense - this.Dexterity;
  423. if (Number(this.Dexterity === 0)) this.Dexterity = this.Total - this.Strength - this.Defense - this.Speed;
  424. if (Number(this.Total === 0)) this.Total = this.Strength + this.Defense + this.Speed + this.Dexterity;
  425. }
  426. }
  427. }
  428. })();