· 5 years ago · Feb 25, 2021, 02:58 PM
1let ownFaction = false;
2let member_info_added = false;
3
4requireDatabase().then(() => {
5 addXHRListener((event) => {
6 const { page, json, xhr } = event.detail;
7
8 const params = new URLSearchParams(xhr.requestBody);
9 const step = params.get("step");
10 if (page === "factions") {
11 if (step === "mainnews" && parseInt(params.get("type")) === 4 && settings.pages.faction.armory) {
12 newstabLoaded("armory").then(shortenArmoryNews);
13 } else if (step === "getMoneyDepositors") {
14 loadGiveToUser();
15 } else if (step === "crimesInitiate") {
16 // || (step === "crimesPlane" && json.success)
17 setTimeout(loadCrimes, 250);
18 } else if (step === "crimes") {
19 loadCrimes();
20 }
21 }
22 });
23
24 requireContent().then(() => {
25 console.log("TT - Faction", subpage(), getSearchParameters().get("step"));
26
27 if (getSearchParameters().get("step") === "your") {
28 ownFaction = true;
29
30 switch (subpage()) {
31 case "main":
32 loadMain();
33 break;
34 case "info":
35 loadInfo();
36 break;
37 case "crimes":
38 loadCrimes();
39 break;
40 case "upgrades":
41 loadUpgrades();
42 break;
43 case "armoury":
44 loadArmory();
45 break;
46 case "controls":
47 loadControls();
48 break;
49 default:
50 break;
51 }
52
53 // Main page
54 doc.find(".faction-tabs li[data-case=main]").addEventListener("click", loadMain);
55
56 // Info page
57 doc.find(".faction-tabs li[data-case=info]").addEventListener("click", loadInfo);
58
59 // Crimes page
60 doc.find(".faction-tabs li[data-case=crimes]").addEventListener("click", loadCrimes);
61
62 // Upgrades page
63 doc.find(".faction-tabs li[data-case=upgrades]").addEventListener("click", loadUpgrades);
64
65 // Armory page
66 doc.find(".faction-tabs li[data-case=armoury]").addEventListener("click", loadArmory);
67
68 // Controls page
69 doc.find(".faction-tabs li[data-case=controls]").addEventListener("click", loadControls);
70 } else {
71 // noinspection EqualityComparisonWithCoercionJS
72 ownFaction = userdata.faction ? getSearchParameters().get("ID") == userdata.faction.faction_id : false;
73
74 loadInfo();
75 }
76 });
77});
78
79function loadMain() {
80 subpageLoaded("main").then(() => {
81 fullInfoBox("main");
82 foldFactionDesc();
83
84 if (ownFaction && settings.scripts.stats_estimate.global && settings.scripts.stats_estimate.faction_wars) observeWarlist();
85 displayWarOverTimes();
86
87 let armoryObserver = new MutationObserver(() => {
88 if (doc.find("div#factionNewsSearchBar div[class*='newsHeader'] button[class*='active']").innerText.includes("Armory")) shortenArmoryNews();
89 });
90 armoryObserver.observe(doc.find("div#factionNewsSearchBar ul.news-list"), {childList: true});
91 });
92}
93
94function loadInfo() {
95 subpageLoaded("info").then(() => {
96 fullInfoBox("info");
97 foldFactionDesc();
98
99 if (ownFaction) {
100 if (settings.pages.faction.armory_worth) armoryWorth();
101 }
102 });
103
104 if (settings.scripts.stats_estimate.global && settings.scripts.stats_estimate.faction_wars) observeWarlist();
105 if (!ownFaction) displayWarOverTimes();
106
107 requirePlayerList(".members-list .table-body").then(async () => {
108 await showUserInfo();
109
110 // Player list filter
111 let list = doc.find(".members-list .table-body");
112 let title = list.previousElementSibling;
113
114 addFilterToTable(list, title);
115 });
116}
117
118function loadCrimes() {
119 if (doc.find(".faction-crimes-wrap.tt-modified")) return;
120
121 subpageLoaded("crimes").then(() => {
122 if (doc.find(".faction-crimes-wrap.tt-modified")) return;
123
124 if (settings.pages.faction.oc_time && Object.keys(oc).length > 0) {
125 ocTimes(oc, settings.format);
126 } else if (Object.keys(oc).length === 0) {
127 console.log("NO DATA (might be no API access)");
128 }
129
130 if (settings.pages.faction.oc_advanced) {
131 openOCs();
132 showAvailablePlayers();
133 showRecommendedNNB();
134 showNNB();
135 highlightOwnOC();
136 }
137
138 doc.find(".faction-crimes-wrap").classList.add("tt-modified");
139 });
140}
141
142function loadUpgrades() {
143 upgradesInfoListener();
144}
145
146function loadArmory() {
147 if (settings.pages.items.drug_details) drugInfo();
148
149 armoryTabsLoaded().then(() => {
150 armoryFilter();
151
152 if (settings.pages.items.highlight_bloodbags !== "none") highlightBloodBags();
153 });
154}
155
156function loadControls() {
157 const btnGiveToUser = doc.find(".control-tabs > li[aria-controls='option-give-to-user']");
158
159 if (btnGiveToUser) {
160 btnGiveToUser.addEventListener("click", () => {
161 if (doc.find(".control-tabs > li[aria-controls='option-give-to-user']").getAttribute("aria-selected")) {
162 loadGiveToUser();
163 }
164 });
165 if (btnGiveToUser.getAttribute("aria-selected")) {
166 loadGiveToUser();
167 }
168 }
169}
170
171function loadGiveToUser() {
172 if (settings.pages.faction.banking_tools) {
173 requirePlayerList(".user-info-list-wrap.money-depositors").then(showFactionBalance);
174 requireElement("#money-user").then(suggestBalance);
175 }
176}
177
178function ocTimes(oc, format) {
179 let crimes = doc.findAll(".organize-wrap .crimes-list>li");
180 for (let crime of crimes) {
181 let crime_id = crime.find(".details-wrap").getAttribute("data-crime");
182
183 let finish_time;
184 let span = doc.new({ type: "span", class: "tt-oc-time" });
185
186 if (oc[crime_id]) {
187 finish_time = oc[crime_id].time_ready;
188 // noinspection JSUnusedLocalSymbols
189 let [day, month, year, hours, minutes, seconds] = dateParts(new Date(finish_time * 1000));
190
191 span.innerText = `${formatTime([hours, minutes, 0], format.time)} | ${formatDate([day, month, 0], format.date)}`;
192 } else {
193 span.innerText = "N/A";
194 }
195
196 crime.find(".status").appendChild(span);
197 }
198}
199
200function shortenArmoryNews() {
201 requireElement("div#factionNewsSearchBar ul.news-list > li").then(() => {
202 let db = {};
203 let tt_modified = [...doc.findAll("div#factionNewsSearchBar ul.news-list > li.tt-modified")];
204 let all_news;
205
206 if (tt_modified.length) {
207 all_news = [...doc.findAll("div#factionNewsSearchBar ul.news-list > li:not([class*='tt-modified'])")].concat(tt_modified[tt_modified.length - 1]);
208 } else {
209 all_news = [...doc.findAll("div#factionNewsSearchBar ul.news-list > li:not([class*='tt-modified'])")]
210 }
211 for (let news of all_news) {
212 let info = news.find(".info").innerText;
213
214 if (info in db) {
215 db[info].count++;
216 db[info].first_date = news.find(".date").innerText;
217 news.remove();
218 } else {
219 db[info] = {
220 count: 1,
221 username: news.find("span.info a").innerText,
222 link: news.find("span.info a").getAttribute("href"),
223 last_date: news.find("span.date").innerText,
224 };
225 news.remove();
226 }
227 }
228
229 console.log("db", db);
230
231 for (let key in db) {
232 let li = doc.new({ type: "li", class: "tt-modified" });
233 let date = doc.new({ type: "span", class: "date" });
234 let info = doc.new({ type: "span", class: "info" });
235 let a = doc.new({ type: "a", text: db[key].username, attributes: { href: db[key].link } });
236 info.appendChild(a);
237
238 if (db[key].first_date) {
239 let upper_time = db[key].first_date.slice(0, db[key].first_date.length - (db[key].first_date.indexOf("\n") + 4));
240 let upper_date = db[key].first_date.slice(db[key].first_date.indexOf("\n"), db[key].first_date.length - 3);
241 let lower_time = db[key].last_date.slice(0, db[key].last_date.length - (db[key].last_date.indexOf("\n") + 4));
242 let lower_date = db[key].last_date.slice(db[key].last_date.indexOf("\n"), db[key].last_date.length - 3);
243
244 let upper_date_span = doc.new({type: "span", class: "left-date", html: `${upper_time}${upper_date}`});
245 let separator = doc.new({type: "span", class: "separator", text: "-"});
246 let lower_date_span = doc.new({type: "span", class: "right-date", html: `${lower_time}${lower_date}`});
247
248 if (upper_time !== lower_time || upper_date !== lower_date) {
249 date.appendChild(upper_date_span);
250 date.appendChild(separator);
251 }
252 date.appendChild(lower_date_span);
253 } else {
254 date.innerText = db[key].last_date;
255 }
256
257 let keywords = ["used", "filled", "loaned", "retrieved", "returned", "deposited", "gave"];
258 let inner_span = doc.new("span");
259
260 for (let keyword of keywords) {
261 if (key.includes(keyword)) {
262 if (key.includes("one")) {
263 let amount_span = doc.new({
264 type: "span",
265 text: " " + db[key].count + "x",
266 attributes: { style: "font-weight: 600" },
267 });
268 inner_span.innerHTML += ` ${keyword}`;
269 inner_span.appendChild(amount_span);
270 inner_span.innerHTML += key.split(" one")[1];
271 } else if (key.includes(" x ")) {
272 let amount_span = doc.new({
273 type: "span",
274 text: " " + db[key].count + "x",
275 attributes: { style: "font-weight: 600" },
276 });
277 inner_span.innerHTML += ` ${keyword}`;
278 inner_span.appendChild(amount_span);
279 inner_span.innerHTML += key.split(" 1 x")[1];
280 } else {
281 inner_span.innerText = ` ${keyword}` + key.split(keyword)[1];
282 }
283 break;
284 }
285 }
286
287 info.appendChild(inner_span);
288 li.appendChild(date);
289 li.appendChild(info);
290 doc.find("div#factionNewsSearchBar ul.news-list").appendChild(li);
291 }
292 all_news = null;
293 })
294}
295
296function subpage() {
297 let hash = window.location.hash.replace("#/", "");
298 if (hash === "" || hash.includes("war/")) {
299 return "main";
300 }
301
302 if (getHashParameters().has("tab")) {
303 return getHashParameters().get("tab");
304 }
305
306 return "";
307}
308
309function subpageLoaded(page) {
310 switch (page) {
311 case "crimes":
312 return requireElement("#faction-crimes .organize-wrap ul.crimes-list li");
313 case "main":
314 return requireElement("#faction-main div[data-title='announcement']+div .ajax-placeholder", { invert: true });
315 case "info":
316 return requireElement("#faction-info .ajax-placeholder", { invert: true });
317 case "upgrades":
318 return requireElement("#faction-upgrades > .ajax-placeholder", { invert: true });
319 default:
320 return Promise.resolve();
321 }
322}
323
324function newstabLoaded(tab) {
325 return new Promise((resolve) => {
326 let checker = setInterval(() => {
327 if (tab === "armory" && doc.find("#tab4-4 .news-list li:not(.last)")) {
328 resolve(true);
329 return clearInterval(checker);
330 }
331 }, 25);
332 });
333}
334
335function openOCs() {
336 for (let crime of doc.findAll(".organize-wrap .crimes-list > li")) {
337 if (crime.find(".status .br") || crime.find(".status .bold").innerText.trim() !== "Ready") {
338 continue;
339 }
340
341 let all_players_ready = true;
342 for (let player of crime.findAll(".details-list>li")) {
343 if (player.find(".member").innerText === "Member") continue;
344
345 if (player.find(".stat").innerText !== "Okay") {
346 all_players_ready = false;
347 break;
348 }
349 }
350
351 if (all_players_ready) {
352 crime.classList.add("active");
353 }
354 }
355}
356
357function showNNB() {
358 if (shouldDisable()) return;
359
360 fetchApi_v2("tornstats", { section: "api.php", action: "crimes" })
361 .then((result) => {
362 // Populate active crimes
363 let crimes = doc.findAll(".organize-wrap .crimes-list>li");
364 for (let crime of crimes) {
365 for (let player of crime.findAll(".details-list>li")) {
366 player.find(".level").classList.add("torntools-modified");
367 if (mobile) {
368 player.find(".member").classList.add("torntools-modified");
369 player.find(".stat").classList.add("torntools-modified");
370 player.find(".member").classList.add("torntools-mobile");
371 player.find(".level").classList.add("torntools-mobile");
372 player.find(".stat").classList.add("torntools-mobile");
373 }
374
375 if (player.find(".member").innerText === "Member") {
376 let col = doc.new({
377 type: "li",
378 class: `tt-nnb ${mobile ? "torntools-mobile" : ""}`,
379 text: mobile ? "NNB" : "TornStats NNB",
380 });
381 player.find(".stat").parentElement.insertBefore(col, player.find(".stat"));
382
383 continue;
384 }
385
386 let player_id = player.find(".h").getAttribute("href").split("XID=")[1];
387 let nnb = result.members[player_id] ? result.members[player_id].natural_nerve : "N/A";
388
389 let col = doc.new({ type: "li", class: `tt-nnb ${mobile ? "torntools-mobile" : ""}`, text: nnb });
390 player.find(".stat").parentElement.insertBefore(col, player.find(".stat"));
391 }
392 }
393
394 // Populate new crime selection
395 for (let player of doc.findAll(".plans-list .item")) {
396 player.find(".offences").classList.add("torntools-modified");
397 if (mobile) {
398 player.find(".member").classList.add("torntools-modified");
399 player.find(".level").classList.add("torntools-modified");
400 player.find(".act").classList.add("torntools-modified");
401 player.find(".member").classList.add("torntools-mobile");
402 player.find(".level").classList.add("torntools-mobile");
403 player.find(".act").classList.add("torntools-mobile");
404 player.find(".offences").classList.add("torntools-mobile");
405 }
406
407 if (player.find(".member").innerText.trim() === "Member") {
408 let col = doc.new({
409 type: "li",
410 class: `tt-nnb short ${mobile ? "torntools-mobile" : ""}`,
411 text: mobile ? "NNB" : "TornStats NNB",
412 });
413 player.find(".act").parentElement.insertBefore(col, player.find(".act"));
414
415 continue;
416 }
417
418 let player_id = player.find(".h").getAttribute("href").split("XID=")[1];
419 let nnb = result.members[player_id] ? result.members[player_id].natural_nerve : "N/A";
420
421 let col = doc.new({ type: "li", class: `tt-nnb short ${mobile ? "torntools-mobile" : ""}`, text: nnb });
422 player.find(".act").parentElement.insertBefore(col, player.find(".act"));
423 }
424
425 doc.findAll(".doctorn-faction-nnb-value").forEach((node) => node.style.setProperty("display", "none", "important"));
426 })
427 .catch((err) => {
428 console.log("ERROR", err);
429 });
430}
431
432function fullInfoBox(page) {
433 let info_box, facDescription;
434 if (getSearchParameters().get("step") === "profile") {
435 info_box = doc.find("#factions div[data-title='description']").nextElementSibling;
436 } else if (page === "main") {
437 info_box = doc.find("div[data-title='announcement']").nextElementSibling;
438 facDescription = info_box;
439 } else if (page === "info") {
440 info_box = doc.find("#faction-info .faction-info-wrap.faction-description .faction-info");
441 }
442
443 if (!facDescription) facDescription = info_box.parentElement.find("div.faction-description");
444
445 let title = info_box.previousElementSibling;
446
447 if (title.classList.contains("tt-modified")) return;
448
449 title.classList.add("title");
450 title.classList.add("tt-modified");
451
452 let key;
453 if (page === "main") {
454 key = "announcements_page_full";
455 } else if (page === "info") {
456 key = "info_page_full";
457 }
458
459 let options_div = doc.new({ type: "div", class: "tt-options" });
460
461 let setting_div = doc.new({ type: "div", class: "tt-checkbox-wrap in-title" });
462 let checkbox = doc.new({ type: "input", attributes: { type: "checkbox" } });
463 let text = doc.new({ type: "div", text: "Show full page" });
464
465 if (settings.pages.faction[key]) {
466 checkbox.checked = true;
467 facDescription.classList.toggle("tt-force-full");
468 }
469
470 setting_div.appendChild(checkbox);
471 setting_div.appendChild(text);
472 options_div.appendChild(setting_div);
473 title.appendChild(options_div);
474
475 checkbox.onclick = () => {
476 facDescription.classList.toggle("tt-force-full");
477
478 ttStorage.change({ settings: { pages: { faction: { [key]: checkbox.checked } } } });
479 };
480}
481
482function upgradesInfoListener() {
483 subpageLoaded("upgrades").then(() => {
484 let upgrades_info_listener = new MutationObserver((mutations) => {
485 for (let mutation of mutations) {
486 if (mutation.type === "childList") {
487 if (mutation.addedNodes[0]) {
488 for (let added_node of mutation.addedNodes) {
489 if (added_node.classList && added_node.classList.contains("confirm") && added_node.classList.length >= 3) {
490 let available_respect = parseInt(doc.find(".residue-respect").innerText.replace(/,/g, ""));
491 let required_respect;
492 let needed_respect;
493
494 for (let text of added_node.findAll(".text")) {
495 if (text.innerText.indexOf("Requires:") > -1) {
496 required_respect = parseInt(text.innerText.trim().split("Requires: ")[1].split(" respect")[0].replace(/,/g, ""));
497
498 needed_respect = required_respect - available_respect;
499 if (needed_respect < 0) needed_respect = 0;
500
501 let span = doc.new({
502 type: "span",
503 text: ` (${numberWithCommas(needed_respect)} respect to go)`,
504 });
505 text.appendChild(span);
506 }
507 }
508 }
509 }
510 }
511 }
512 }
513 });
514 upgrades_info_listener.observe(doc.find(".skill-tree"), { childList: true, subtree: true });
515 });
516}
517
518function armoryWorth() {
519 fetchApi_v2("torn", { section: "faction", selections: "weapons,armor,temporary,medical,drugs,boosters,cesium,currency" })
520 .then((result) => {
521 console.log("result", result);
522
523 let total = 0;
524 let lists = ["weapons", "armor", "temporary", "medical", "drugs", "boosters"];
525
526 for (let type of lists) {
527 if (result[type]) {
528 for (let item of result[type]) {
529 total += itemlist.items[item.ID].market_value * item.quantity;
530 }
531 }
532 }
533
534 // Cesium
535 if (result.cesium) {
536 }
537
538 // Points
539 total += result.points * torndata.pawnshop.points_value;
540
541 const li = doc.new({ type: "li" });
542 const span = doc.new({ type: "span", text: "Armory value: ", class: "bold" });
543 const spanValue = doc.new({ type: "span", text: `$${numberWithCommas(total, false)}` });
544 li.appendChild(span);
545 li.appendChild(spanValue);
546
547 doc.find(".f-info-wrap .f-info.right").insertBefore(li, doc.find(".f-info-wrap .f-info.right>li:nth-of-type(2)"));
548 })
549 .catch((err) => {
550 console.log("ERROR", err);
551
552 if (err.error === "Incorrect ID-entity relation") {
553 let li = doc.new({ type: "li", text: `Armory value: NO API ACCESS` });
554 doc.find(".f-info-wrap .f-info.right").insertBefore(li, doc.find(".f-info-wrap .f-info.right>li:nth-of-type(2)"));
555 }
556 });
557}
558
559async function showUserInfo() {
560 if (!settings.pages.faction.member_info && !(settings.scripts.stats_estimate.global && settings.scripts.stats_estimate.faction_members)) return;
561
562 const factionId = doc.find(".faction-info-wrap .faction-info[data-faction]").getAttribute("data-faction");
563
564 doc.find(".members-list .table-body").classList.add("tt-modified");
565
566 let dataInformation;
567 if (settings.pages.faction.member_info) {
568 dataInformation = await new Promise((resolve) => {
569 fetchApi_v2("torn", { section: "faction", objectid: factionId, selections: `${ownFaction ? "donations," : ""}basic` })
570 .then((result) => resolve(result))
571 .catch((result) => resolve(result));
572 });
573 }
574
575 let estimateCount = 0;
576 for (let tableRow of doc.findAll(".members-list .table-body > li")) {
577 let userId = tableRow.find("a.user.name").getAttribute("data-placeholder")
578 ? tableRow.find("a.user.name").getAttribute("data-placeholder").split(" [")[1].split("]")[0]
579 : tableRow.find("a.user.name").getAttribute("href").split("XID=")[1];
580
581 const container = doc.new({ type: "section", class: "tt-userinfo-container" });
582 tableRow.parentElement.insertBefore(container, tableRow.nextElementSibling);
583
584 if (settings.pages.faction.member_info) {
585 const row = doc.new({ type: "section", class: "tt-userinfo-row" });
586 container.appendChild(row);
587
588 if (!dataInformation.error) {
589 let lastAction = (Date.now() - dataInformation.members[userId].last_action.timestamp * 1000) / 1000;
590 if (lastAction < 0) lastAction = 0;
591
592 row.appendChild(
593 doc.new({
594 type: "div",
595 class: "tt-userinfo-field--last_action",
596 text: `Last Action: ${dataInformation.members[userId].last_action.relative}`,
597 attributes: { "last-action": lastAction.toFixed(0) },
598 })
599 );
600
601 if (dataInformation.donations && dataInformation.donations[userId]) {
602 if (dataInformation.donations[userId].money_balance > 0) {
603 row.appendChild(
604 doc.new({
605 type: "div",
606 text: `Money Balance: $${numberWithCommas(dataInformation.donations[userId].money_balance, false)}`,
607 })
608 );
609 }
610 if (dataInformation.donations[userId].points_balance > 0) {
611 row.appendChild(
612 doc.new({
613 type: "div",
614 text: `Point Balance: ${numberWithCommas(dataInformation.donations[userId].points_balance, false)}`,
615 })
616 );
617 }
618 }
619
620 // Activity notifications
621 const checkpoints = settings.inactivity_alerts_faction;
622 for (let checkpoint of Object.keys(checkpoints).sort((a, b) => b - a)) {
623 if (new Date() - new Date(dataInformation.members[userId].last_action.timestamp * 1000) >= parseInt(checkpoint)) {
624 console.log(checkpoints[checkpoint]);
625 tableRow.style.backgroundColor = `${checkpoints[checkpoint]}`;
626 break;
627 }
628 }
629 } else {
630 let error = dataInformation.error;
631
632 if (error === "Incorrect ID-entity relation") error = "No API access.";
633
634 container.appendChild(
635 doc.new({
636 type: "div",
637 class: "tt-userinfo-message",
638 text: error,
639 attributes: { color: "error" },
640 })
641 );
642 }
643 }
644
645 if (settings.scripts.stats_estimate.global && settings.scripts.stats_estimate.faction_members) {
646 const row = doc.new({ type: "section", class: "tt-userinfo-row" });
647 container.appendChild(row);
648
649 if (!hasCachedEstimate(userId)) estimateCount++;
650
651 new MutationObserver((mutations, observer) => {
652 container.style.display = tableRow.style.display === "none" ? "none" : "block";
653 }).observe(tableRow, { attributes: true, attributeFilter: ["style"] });
654
655 const level = parseInt(tableRow.find(".lvl").innerText);
656
657 loadingPlaceholder(row, true);
658 estimateStats(userId, false, estimateCount, level)
659 .then((result) => {
660 loadingPlaceholder(row, false);
661 row.appendChild(
662 doc.new({
663 type: "span",
664 text: `Stat Estimate: ${result.estimate}`,
665 })
666 );
667 })
668 .catch((error) => {
669 loadingPlaceholder(row, false);
670
671 if (error.show) {
672 row.appendChild(
673 doc.new({
674 type: "span",
675 class: "tt-userinfo-message",
676 text: error.message,
677 attributes: { color: "error" },
678 })
679 );
680 } else {
681 row.remove();
682 if (container.children.length === 0) container.remove();
683 }
684 });
685 }
686 }
687 member_info_added = true;
688}
689
690function showAvailablePlayers() {
691 let count = 0;
692
693 if (doc.find("div.plans-list.p10")) {
694 display(count);
695 return;
696 }
697
698 let list = doc.find("ul.plans-list");
699 for (let member of list.findAll(":scope .item")) {
700 count++;
701 }
702
703 display(count);
704
705 function display(number) {
706 doc.find("#faction-crimes").insertBefore(
707 doc.new({
708 type: "div",
709 class: "info-msg-cont border-round m-top10",
710 html: `
711 <div class="info-msg border-round">
712 <i class="info-icon"></i>
713 <div class="delimiter">
714 <div class="msg right-round">
715 ${number} member${number !== 1 ? "s" : ""} available for OCs.
716 </div>
717 </div>
718 </div>
719 `,
720 }),
721 doc.find("#faction-crimes").firstElementChild
722 );
723 }
724}
725
726function showRecommendedNNB() {
727 const nnb_dict = {
728 Blackmail: "0+",
729 Kidnapping: "~20",
730 "Bomb Threat": "~25",
731 "Planned Robbery": "~35",
732 "Rob a money train": "~45",
733 "Take over a cruise liner": "~50",
734 "Hijack a plane": "55-60",
735 "Political Assassination": "~60",
736 };
737
738 const parent = doc.find(".faction-crimes-wrap .begin-wrap");
739
740 const heading = parent.find(".plan-crimes[role=heading]");
741 heading.appendChild(doc.new({ type: "span", class: "tt-span", text: mobile ? "NNB" : "Recommended NNB" }));
742
743 for (let crime_type of parent.findAll(".crimes-list .item-wrap")) {
744 let name_div = crime_type.find(".plan-crimes");
745 let inner_span = doc.new({ type: "span", class: "tt-span", text: nnb_dict[name_div.innerText] });
746 name_div.appendChild(inner_span);
747 }
748}
749
750function drugInfo() {
751 let item_info_container_mutation = new MutationObserver((mutations) => {
752 for (let mutation of mutations) {
753 if (mutation.target.classList.contains("view-item-info") && (mutation.addedNodes.length > 0 || mutation.attributeName === "style")) {
754 let el = mutation.target;
755 itemInfoLoaded(el).then(() => {
756 let item_name = el.find("span.bold").innerText;
757 if (item_name.indexOf("The") > -1) item_name = item_name.split("The ")[1];
758
759 let drug_details = DRUG_INFORMATION[item_name.toLowerCase().replace(/ /g, "_")];
760 if (drug_details === undefined) {
761 return;
762 }
763
764 // Remove current info
765 for (let eff of el.findAll(".item-effect")) {
766 eff.remove();
767 }
768
769 // Pros
770 if (drug_details.pros) {
771 let pros_header = doc.new({
772 type: "div",
773 class: "t-green bold item-effect m-top10",
774 text: "Pros:",
775 });
776 el.find(".info-msg").appendChild(pros_header);
777
778 for (let eff of drug_details.pros) {
779 let pros_div = doc.new({ type: "div", class: "t-green bold item-effect tabbed", text: eff });
780 el.find(".info-msg").appendChild(pros_div);
781 }
782 }
783
784 // Cons
785 if (drug_details.cons) {
786 let cons_header = doc.new({ type: "div", class: "t-red bold item-effect", text: "Cons:" });
787 el.find(".info-msg").appendChild(cons_header);
788
789 for (let eff of drug_details.cons) {
790 let cons_div = doc.new({ type: "div", class: "t-red bold item-effect tabbed", text: eff });
791 el.find(".info-msg").appendChild(cons_div);
792 }
793 }
794
795 // Cooldown
796 if (drug_details.cooldown) {
797 let cooldown_div = doc.new({
798 type: "div",
799 class: "t-red bold item-effect",
800 text: `Cooldown: ${drug_details.cooldown}`,
801 });
802 el.find(".info-msg").appendChild(cooldown_div);
803 }
804
805 // Overdose
806 if (drug_details.overdose) {
807 let od_header = doc.new({ type: "div", class: "t-red bold item-effect", text: "Overdose:" });
808 el.find(".info-msg").appendChild(od_header);
809
810 // bars
811 if (drug_details.overdose.bars) {
812 let bars_header = doc.new({
813 type: "div",
814 class: "t-red bold item-effect tabbed",
815 text: "Bars",
816 });
817 el.find(".info-msg").appendChild(bars_header);
818
819 for (let bar_eff of drug_details.overdose.bars) {
820 let bar_eff_div = doc.new({
821 type: "div",
822 class: "t-red bold item-effect double-tabbed",
823 text: bar_eff,
824 });
825 el.find(".info-msg").appendChild(bar_eff_div);
826 }
827 }
828
829 // faction time
830 if (drug_details.overdose.hosp_time) {
831 let hosp_div = doc.new({
832 type: "div",
833 class: "t-red bold item-effect tabbed",
834 text: `Hospital: ${drug_details.overdose.hosp_time}`,
835 });
836 el.find(".info-msg").appendChild(hosp_div);
837 }
838
839 // extra
840 if (drug_details.overdose.extra) {
841 let extra_div = doc.new({
842 type: "div",
843 class: "t-red bold item-effect tabbed",
844 text: `Extra: ${drug_details.overdose.extra}`,
845 });
846 el.find(".info-msg").appendChild(extra_div);
847 }
848 }
849 });
850 }
851 }
852 });
853 item_info_container_mutation.observe(doc.find("body"), { childList: true, subtree: true, attributes: true });
854}
855
856function itemInfoLoaded(element) {
857 return requireElement(".ajax-placeholder", { invert: true });
858}
859
860function addFilterToTable(list, title) {
861 let filter_container = content
862 .newContainer("Filters", {
863 id: "tt-player-filter",
864 class: "filter-container",
865 next_element: title,
866 })
867 .find(".content");
868
869 filter_container.innerHTML = `
870 <div class="filter-header">
871 <div class="statistic" id="showing">Showing <span class="filter-count">X</span> of <span class="filter-total">Y</span> users</div>
872 </div>
873 <div class="filter-content ${mobile ? "tt-mobile" : ""}">
874 <div class="filter-wrap" id="activity-filter">
875 <div class="filter-heading">Activity</div>
876 <div class="filter-multi-wrap ${mobile ? "tt-mobile" : ""}">
877 <div class="tt-checkbox-wrap"><input type="checkbox" value="online">Online</div>
878 <div class="tt-checkbox-wrap"><input type="checkbox" value="idle">Idle</div>
879 <div class="tt-checkbox-wrap"><input type="checkbox" value="offline">Offline</div>
880 </div>
881 </div>
882 <div class="filter-wrap" id="status-filter">
883 <div class="filter-heading">Status</div>
884 <div class="filter-multi-wrap ${mobile ? "tt-mobile" : ""}">
885 <div class="tt-checkbox-wrap"><input type="checkbox" value="okay">Okay</div>
886 <div class="tt-checkbox-wrap"><input type="checkbox" value="hospital">Hospital</div>
887 <div class="tt-checkbox-wrap"><input type="checkbox" value="traveling">Traveling</div>
888 <div class="tt-checkbox-wrap"><input type="checkbox" value="jail">Jail</div>
889 </div>
890 </div>
891 <div class='filter-wrap' id='special-filter'>
892 <div class='filter-heading'>Special</div>
893 <div class='filter-multi-wrap ${mobile ? "tt-mobile" : ""}'>
894 <div class='tt-checkbox-wrap'>Y:<input type='checkbox' value='isfedded-yes'>N:<input type='checkbox' value='isfedded-no'>Fedded</div>
895 <!-- <div class='tt-checkbox-wrap'>Y:<input type='checkbox' value='traveling-yes'>N:<input type='checkbox' value='traveling-no'>Traveling</div> -->
896 <div class='tt-checkbox-wrap'>Y:<input type='checkbox' value='newplayer-yes'>N:<input type='checkbox' value='newplayer-no'>New Player</div>
897 <div class='tt-checkbox-wrap'>Y:<input type='checkbox' value='onwall-yes'>N:<input type='checkbox' value='onwall-no'>On Wall</div>
898 <div class='tt-checkbox-wrap'>Y:<input type='checkbox' value='incompany-yes'>N:<input type='checkbox' value='incompany-no'>In Company</div>
899 <!-- <div class='tt-checkbox-wrap'>Y:<input type='checkbox' value='infaction-yes'>N:<input type='checkbox' value='infaction-no'>In Faction</div> -->
900 <div class='tt-checkbox-wrap'>Y:<input type='checkbox' value='isdonator-yes'>N:<input type='checkbox' value='isdonator-no'>Is Donator</div>
901 </div>
902 </div>
903 <div class="filter-wrap" id="level-filter">
904 <div class="filter-heading">Level</div>
905 <div id="tt-level-filter" class="filter-slider"></div>
906 <div class="filter-slider-info"></div>
907 </div>
908 <div class="filter-wrap ${settings.pages.faction.member_info && ownFaction ? "" : "filter-hidden"}" id="last-action-filter">
909 <div class="filter-heading">Last Action</div>
910 <div id="tt-last-action-filter" class="filter-slider"></div>
911 <div class="filter-slider-info"></div>
912 </div>
913 </div>
914 `;
915
916 // Initializing
917 // let time_start = filters.faction.time[0] || 0;
918 // let time_end = filters.faction.time[1] || 99999;
919 let level_start = filters.faction.level[0] || 0;
920 let level_end = filters.faction.level[1] || 100;
921 let last_action_start = settings.pages.faction.member_info ? filters.faction.last_action[0] / 60 / 60 || 0 : 0;
922 // let last_action_end = filters.faction.last_action[1] || 744;
923
924 // for(let faction of filters.preset_data.factions.data){
925 // let option = doc.new({type: "option", value: faction, text: faction});
926 // if(faction == filters.preset_data.factions.default) option.selected = true;
927
928 // filter_container.find("#tt-faction-filter").appendChild(option);
929 // }
930 // let divider_option = doc.new({type: "option", value: "----------", text: "----------", attributes: {disabled: true}});
931 // filter_container.find("#tt-faction-filter").appendChild(divider_option);
932
933 // // Time slider
934 // let time_slider = filter_container.find('#tt-time-filter');
935 // noUiSlider.create(time_slider, {
936 // start: [time_start, time_end],
937 // step: 1,
938 // connect: true,
939 // range: {
940 // 'min': 0,
941 // 'max': 99999
942 // }
943 // });
944
945 // let time_slider_info = time_slider.nextElementSibling;
946 // time_slider.noUiSlider.on('update', function (values) {
947 // values = values.map(x => parseInt(x));
948 // time_slider_info.innerHTML = `Days: ${values.join(' - ')}`;
949 // });
950
951 // Special
952 for (let key in filters.faction.special) {
953 switch (filters.faction.special[key]) {
954 case "yes":
955 filter_container.find(`#special-filter input[value='${key}-yes']`).checked = true;
956 break;
957 case "no":
958 filter_container.find(`#special-filter input[value='${key}-no']`).checked = true;
959 break;
960 case "both":
961 filter_container.find(`#special-filter input[value='${key}-yes']`).checked = true;
962 filter_container.find(`#special-filter input[value='${key}-no']`).checked = true;
963 break;
964 default:
965 filter_container.find(`#special-filter input[value='${key}-yes']`).checked = true;
966 filter_container.find(`#special-filter input[value='${key}-no']`).checked = true;
967 break;
968 }
969 }
970
971 // Level slider
972 let level_slider = filter_container.find("#tt-level-filter");
973 noUiSlider.create(level_slider, {
974 start: [level_start, level_end],
975 step: 1,
976 connect: true,
977 range: {
978 min: 0,
979 max: 100,
980 },
981 });
982
983 let level_slider_info = level_slider.nextElementSibling;
984 level_slider.noUiSlider.on("update", (values) => {
985 values = values.map((x) => parseInt(x));
986 level_slider_info.innerHTML = `Level: ${values.join(" - ")}`;
987 });
988
989 // Last Action slider
990 let last_action_slider = filter_container.find("#tt-last-action-filter");
991 noUiSlider.create(last_action_slider, {
992 start: last_action_start,
993 step: 1,
994 connect: true,
995 range: {
996 min: 0,
997 max: 744,
998 },
999 });
1000
1001 let last_action_slider_info = last_action_slider.nextElementSibling;
1002 last_action_slider.noUiSlider.on("update", (values) => {
1003 values = values.map((x) => timeUntil(parseFloat(x) * 60 * 60 * 1000, { max_unit: "h", hide_nulls: true }));
1004 last_action_slider_info.innerHTML = `Min Hours: ${values.join(" - ")}`;
1005 });
1006
1007 // Event listeners
1008 for (let checkbox of filter_container.findAll(".tt-checkbox-wrap input")) {
1009 checkbox.onclick = applyFilters;
1010 }
1011 for (let dropdown of filter_container.findAll("select")) {
1012 dropdown.onchange = applyFilters;
1013 }
1014 let filter_observer = new MutationObserver((mutations) => {
1015 for (let mutation of mutations) {
1016 if (
1017 mutation.type === "attributes" &&
1018 mutation.target.classList &&
1019 mutation.attributeName === "aria-valuenow" &&
1020 (mutation.target.classList.contains("noUi-handle-lower") || mutation.target.classList.contains("noUi-handle-upper"))
1021 ) {
1022 applyFilters();
1023 }
1024 }
1025 });
1026 filter_observer.observe(filter_container, { attributes: true, subtree: true });
1027
1028 // Page changing
1029 doc.addEventListener("click", (event) => {
1030 if (event.target.classList && !event.target.classList.contains("gallery-wrapper") && hasParent(event.target, { class: "gallery-wrapper" })) {
1031 console.log("click");
1032 setTimeout(() => {
1033 requirePlayerList(".users-list").then(() => {
1034 console.log("loaded");
1035 populateFactions();
1036 applyFilters();
1037 });
1038 }, 300);
1039 }
1040 });
1041
1042 // Initializing
1043 for (let state of filters.faction.activity) {
1044 doc.find(`#activity-filter input[value='${state}']`).checked = true;
1045 }
1046 for (let state of filters.faction.status) {
1047 doc.find(`#status-filter input[value='${state}']`).checked = true;
1048 }
1049 // if(filters.faction.faction.default){
1050 // doc.find(`#faction-filter option[value='${filters.faction.faction}']`).selected = true;
1051 // }
1052
1053 // populateFactions();
1054 if (settings.pages.faction.member_info) {
1055 memberInfoAdded().then(applyFilters);
1056 } else {
1057 applyFilters();
1058 }
1059
1060 // Look for Search bar changes
1061 doc.find("#faction-info-members .table-header .table-cell.member input.search-input").addEventListener("keyup", () => {
1062 setTimeout(() => {
1063 for (let row of doc.findAll("#faction-info-members .table-body>.table-row")) {
1064 if (row.style.display === "none" && row.nextElementSibling && row.nextElementSibling.classList.contains("tt-user-info")) {
1065 row.classList.add("filter-hidden");
1066 } else if (
1067 (row.style.display === "flex" || row.style.display === "") &&
1068 row.nextElementSibling &&
1069 row.nextElementSibling.classList.contains("tt-user-info")
1070 ) {
1071 row.classList.remove("filter-hidden");
1072 }
1073 }
1074 }, 100);
1075 });
1076
1077 function applyFilters() {
1078 let activity = [];
1079 let status = [];
1080 let special = {};
1081 // let faction = ``;
1082 // let time = []
1083 let level = [];
1084 let last_action = [];
1085
1086 // Activity
1087 for (let checkbox of doc.findAll("#activity-filter .tt-checkbox-wrap input:checked")) {
1088 activity.push(checkbox.getAttribute("value"));
1089 }
1090 // Status
1091 for (let checkbox of doc.findAll("#status-filter .tt-checkbox-wrap input:checked")) {
1092 status.push(checkbox.getAttribute("value"));
1093 }
1094 // Special
1095 for (let key in filters.faction.special) {
1096 if (
1097 doc.find(`#tt-player-filter #special-filter input[value='${key}-yes']`).checked &&
1098 doc.find(`#tt-player-filter #special-filter input[value='${key}-no']`).checked
1099 ) {
1100 special[key] = "both";
1101 } else if (doc.find(`#tt-player-filter #special-filter input[value='${key}-yes']`).checked) {
1102 special[key] = "yes";
1103 } else if (doc.find(`#tt-player-filter #special-filter input[value='${key}-no']`).checked) {
1104 special[key] = "no";
1105 } else {
1106 special[key] = "both";
1107 }
1108 }
1109 // // Faction
1110 // faction = doc.find("#faction-filter select option:checked").value;
1111 // // Time
1112 // time.push(parseInt(doc.find("#time-filter .noUi-handle-lower").getAttribute("aria-valuenow")));
1113 // time.push(parseInt(doc.find("#time-filter .noUi-handle-upper").getAttribute("aria-valuenow")));
1114 // Level
1115 level.push(parseInt(doc.find("#level-filter .noUi-handle-lower").getAttribute("aria-valuenow")));
1116 level.push(parseInt(doc.find("#level-filter .noUi-handle-upper").getAttribute("aria-valuenow")));
1117 // Last Action
1118 last_action.push(parseInt(doc.find("#last-action-filter .noUi-handle-lower").getAttribute("aria-valuenow")) * 60 * 60); // convert to seconds
1119 // last_action.push(parseInt(doc.find("#last-action-filter .noUi-handle-upper").getAttribute("aria-valuenow"))*60*60); // convert to seconds
1120
1121 // console.log("Activity", activity);
1122 // console.log("Faction", faction);
1123 // console.log("Time", time);
1124 // console.log("Level", level);
1125
1126 // Filtering
1127 for (let li of list.findAll(":scope > li.table-row")) {
1128 if (li.classList.contains("tt-user-info")) continue;
1129 showRow(li);
1130
1131 // Level
1132 let player_level = parseInt(li.find(".lvl").innerText.trim());
1133 if (!(level[0] <= player_level && player_level <= level[1])) {
1134 showRow(li, false);
1135 continue;
1136 }
1137
1138 // // Time
1139 // let player_time = parseInt(li.find(".days").innerText.trim().replace("Days:", "").trim());
1140 // if(!(time[0] <= player_time && player_time <= time[1])){
1141 // li.classList.add("filter-hidden");
1142 // continue;
1143 // }
1144
1145 // Last Action
1146 if (settings.pages.faction.member_info && ownFaction) {
1147 let player_last_action = "N/A";
1148 if (
1149 li.nextElementSibling &&
1150 li.nextElementSibling.find(".tt-userinfo-field--last_action") &&
1151 li.nextElementSibling.find(".tt-userinfo-field--last_action").getAttribute("last-action")
1152 ) {
1153 player_last_action = parseInt(li.nextElementSibling.find(".tt-userinfo-field--last_action").getAttribute("last-action"));
1154 }
1155 if (player_last_action !== "N/A" && !(last_action[0] <= player_last_action)) {
1156 showRow(li, false);
1157 continue;
1158 }
1159 }
1160
1161 // Activity
1162 let matches_one_activity = activity.length === 0;
1163 for (let state of activity) {
1164 if (li.querySelector(`li[id^='${ACTIVITY_FILTER_DICT[state]}']`)) {
1165 matches_one_activity = true;
1166 }
1167 }
1168 if (!matches_one_activity) {
1169 showRow(li, false);
1170 continue;
1171 }
1172
1173 // Status
1174 let matches_one_status = status.length === 0;
1175 for (let state of status) {
1176 if (li.find(`.status`).innerText.replace("Status:", "").trim().toLowerCase() === state) {
1177 matches_one_status = true;
1178 }
1179 }
1180 if (!matches_one_status) {
1181 showRow(li, false);
1182 }
1183
1184 // Special
1185 for (let key in special) {
1186 if (special[key] === "both") continue;
1187
1188 if (special[key] === "yes") {
1189 let matchesOneIcon = false;
1190 for (let icon of SPECIAL_FILTER_DICT[key]) {
1191 if (li.querySelector(`li[id^='${icon}']`)) {
1192 matchesOneIcon = true;
1193 break;
1194 }
1195 }
1196
1197 if (!matchesOneIcon) {
1198 showRow(li, false);
1199 }
1200 } else if (special[key] === "no") {
1201 let matchesOneIcon = false;
1202 for (let icon of SPECIAL_FILTER_DICT[key]) {
1203 if (li.querySelector(`li[id^='${icon}']`)) {
1204 matchesOneIcon = true;
1205 break;
1206 }
1207 }
1208
1209 if (matchesOneIcon) {
1210 showRow(li, false);
1211 }
1212 }
1213 }
1214 }
1215
1216 ttStorage.change({
1217 filters: {
1218 faction: {
1219 activity: activity,
1220 // faction: faction,
1221 // time: time,
1222 special: special,
1223 status: status,
1224 level: level,
1225 last_action: last_action,
1226 },
1227 },
1228 });
1229
1230 updateStatistics();
1231 }
1232
1233 function showRow(row, show = true) {
1234 if (show) {
1235 row.classList.remove("filter-hidden");
1236 if (
1237 row.nextElementSibling &&
1238 (row.nextElementSibling.classList.contains("tt-user-info") || row.nextElementSibling.classList.contains("tt-userinfo-container"))
1239 ) {
1240 row.nextElementSibling.classList.remove("filter-hidden");
1241 }
1242 } else {
1243 row.classList.add("filter-hidden");
1244 if (
1245 row.nextElementSibling &&
1246 (row.nextElementSibling.classList.contains("tt-user-info") || row.nextElementSibling.classList.contains("tt-userinfo-container"))
1247 ) {
1248 row.nextElementSibling.classList.add("filter-hidden");
1249 }
1250 }
1251 }
1252
1253 function updateStatistics() {
1254 const users = [...list.findAll(":scope>li:not(.tt-user-info)")];
1255
1256 doc.find(".statistic#showing .filter-count").innerText = users.filter((x) => !x.classList.contains("filter-hidden")).length;
1257 doc.find(".statistic#showing .filter-total").innerText = users.length;
1258 }
1259
1260 function populateFactions() {
1261 let faction_tags = [...list.findAll(":scope>li")]
1262 .map((x) => (x.find(".user.faction img") ? x.find(".user.faction img").getAttribute("title") : ""))
1263 .filter((x) => x !== "");
1264
1265 for (let tag of faction_tags) {
1266 if (filter_container.find(`#tt-faction-filter option[value='${tag}']`)) continue;
1267
1268 let option = doc.new({ type: "option", value: tag, text: tag });
1269 filter_container.find("#tt-faction-filter").appendChild(option);
1270 }
1271 }
1272}
1273
1274function armoryFilter() {
1275 let armory_filter = content.newContainer("Armory Filter", {
1276 header_only: true,
1277 id: "ttArmoryFilter",
1278 next_element: doc.find("#faction-armoury-tabs"),
1279 all_rounded: true,
1280 });
1281
1282 if (
1283 !["weapons", "armour"].includes(
1284 doc.find("ul[aria-label='faction armoury tabs']>li[aria-selected='true']").getAttribute("aria-controls").replace("armoury-", "")
1285 )
1286 ) {
1287 armory_filter.classList.add("filter-hidden");
1288 }
1289
1290 // Switching page
1291 if (!mobile) {
1292 for (let link of doc.findAll("ul[aria-label='faction armoury tabs']>li")) {
1293 if (["weapons", "armour"].includes(link.getAttribute("aria-controls").replace("armoury-", ""))) {
1294 link.addEventListener("click", () => {
1295 console.log("filter tab");
1296 if (doc.find("#ttArmoryFilter")) {
1297 doc.find("#ttArmoryFilter").classList.remove("filter-hidden");
1298 }
1299 });
1300 } else {
1301 link.addEventListener("click", () => {
1302 console.log("other tab");
1303 if (doc.find("#ttArmoryFilter")) {
1304 doc.find("#ttArmoryFilter").classList.add("filter-hidden");
1305 }
1306 });
1307 }
1308 }
1309 } else {
1310 doc.find(".armoury-drop-list select#armour-nav-list").addEventListener("change", () => {
1311 if (
1312 ["weapons", "armour"].includes(
1313 doc.find("ul[aria-label='faction armoury tabs']>li[aria-selected='true']").getAttribute("aria-controls").replace("armoury-", "")
1314 )
1315 ) {
1316 console.log("filter tab");
1317 if (doc.find("#ttArmoryFilter")) {
1318 doc.find("#ttArmoryFilter").classList.remove("filter-hidden");
1319 }
1320 } else {
1321 console.log("other tab");
1322 if (doc.find("#ttArmoryFilter")) {
1323 doc.find("#ttArmoryFilter").classList.add("filter-hidden");
1324 }
1325 }
1326 });
1327 }
1328
1329 let unavailable_wrap = doc.new({ type: "div", class: "tt-checkbox-wrap in-title hide-unavailable-option" });
1330 let unavailable_checkbox = doc.new({ type: "input", attributes: { type: "checkbox" } });
1331 let unavailable_text = doc.new({ type: "div", text: "Hide unavailable" });
1332
1333 if (filters.faction_armory.hide_unavailable) {
1334 unavailable_checkbox.checked = filters.faction_armory.hide_unavailable;
1335 }
1336
1337 unavailable_wrap.appendChild(unavailable_checkbox);
1338 unavailable_wrap.appendChild(unavailable_text);
1339
1340 unavailable_checkbox.onclick = filter;
1341
1342 armory_filter.find(".tt-options").appendChild(unavailable_wrap);
1343
1344 armoryItemsLoaded().then(filter);
1345
1346 let items_added_observer = new MutationObserver((mutations) => {
1347 for (let mutation of mutations) {
1348 if (mutation.type === "childList" && mutation.addedNodes[0]) {
1349 for (let added_node of mutation.addedNodes) {
1350 if (added_node.classList && added_node.classList.contains("item-list")) {
1351 if (
1352 ["weapons", "armour"].includes(
1353 doc.find("ul[aria-label='faction armoury tabs']>li[aria-selected='true']").getAttribute("aria-controls").replace("armoury-", "")
1354 )
1355 ) {
1356 console.log("items added");
1357 filter();
1358 }
1359 }
1360 }
1361 }
1362 }
1363 });
1364 items_added_observer.observe(doc.find(`#faction-armoury-tabs`), { childList: true, subtree: true });
1365
1366 function filter() {
1367 let item_list = doc.findAll(`#faction-armoury-tabs .armoury-tabs[aria-expanded='true'] .item-list>li`);
1368 let unavailable = doc.find(".hide-unavailable-option input").checked;
1369
1370 for (let item of item_list) {
1371 item.classList.remove("filter-hidden");
1372
1373 // Unavailable filter
1374 if (unavailable && item.find(".loaned a")) {
1375 item.classList.add("filter-hidden");
1376 }
1377 }
1378
1379 ttStorage.change({ filters: { faction_armory: { hide_unavailable: unavailable } } });
1380 }
1381}
1382
1383const ALLOWED_BLOOD = {
1384 "o+": [738, 739], // 738
1385 "o-": [739], // 739
1386 "a+": [732, 733, 738, 739], // 732
1387 "a-": [733, 739], // 733
1388 "b+": [734, 735, 738, 739], // 734
1389 "b-": [735, 739], // 735
1390 "ab+": [732, 733, 734, 735, 736, 737, 738, 739], // 736
1391 "ab-": [733, 735, 737, 739], // 737
1392};
1393
1394function highlightBloodBags() {
1395 const section = doc.find("ul[aria-label='faction armoury tabs'] > li[aria-selected='true']").getAttribute("aria-controls").replace("armoury-", "");
1396 if (section === "medical") highlight();
1397
1398 new MutationObserver((mutations) => {
1399 if (
1400 !mutations
1401 .filter((mut) => mut.type === "childList" && mut.addedNodes.length)
1402 .flatMap((mut) => Array.from(mut.addedNodes))
1403 .some((node) => node.classList && node.classList.contains("item-list"))
1404 )
1405 return;
1406
1407 const section = doc.find("ul[aria-label='faction armoury tabs'] > li[aria-selected='true']").getAttribute("aria-controls").replace("armoury-", "");
1408 if (section !== "medical") return;
1409
1410 highlight();
1411 }).observe(doc.find(`#faction-armoury-tabs`), { childList: true, subtree: true });
1412
1413 function highlight() {
1414 const allowedBlood = ALLOWED_BLOOD[settings.pages.items.highlight_bloodbags];
1415 const items = doc.findAll(`#faction-armoury-tabs .armoury-tabs[aria-expanded='true'] .item-list > li`);
1416
1417 for (let item of items) {
1418 if (!item.find(".name") || item.find(".name").classList.contains(".tt-modified")) continue;
1419
1420 if (item.find(".img-wrap").getAttribute("data-id") === "1012") continue; // is an irradiated blood bag
1421
1422 if (!item.find(".name").innerText.split(" x")[0].includes("Blood Bag : ")) continue; // is not a filled blood bag
1423
1424 const classes = item.find(".name").classList;
1425
1426 classes.add("tt-modified");
1427
1428 let bloodId = item.find(".img-wrap").getAttribute("data-id");
1429
1430 if (allowedBlood.includes(parseInt(bloodId))) classes.add("tt-good_blood");
1431 else classes.add("tt-bad_blood");
1432
1433 //Add blood bag value
1434 let price = itemlist.items[bloodId].market_value;
1435 let new_element = doc.new("span");
1436
1437 new_element.setClass("tt-item-price");
1438 new_element.innerText = `$${numberWithCommas(price, false)}`;
1439 item.find(".name").appendChild(new_element);
1440 }
1441 }
1442}
1443
1444function armoryTabsLoaded() {
1445 return requireElement("ul[aria-label='faction armoury tabs'] > li[aria-selected='true']");
1446}
1447
1448function armoryItemsLoaded() {
1449 return requireElement("#faction-armoury-tabs .armoury-tabs[aria-expanded='true'] .item-list > li:not(.ajax-placeholder)");
1450}
1451
1452function memberInfoAdded() {
1453 return new Promise((resolve) => {
1454 let checker = setInterval(() => {
1455 if (member_info_added) {
1456 resolve(true);
1457 return clearInterval(checker);
1458 }
1459 });
1460 });
1461}
1462
1463function warOverviewLoaded() {
1464 return requireElement("#react-root ul.f-war-list");
1465}
1466
1467function warDescriptionLoaded() {
1468 return requireElement("#war-react-root ul.f-war-list > li.descriptions");
1469}
1470
1471function observeWarlist() {
1472 if (window.location.hash.includes("/war/")) warDescriptionLoaded().then(observeDescription);
1473
1474 warOverviewLoaded().then(() => {
1475 new MutationObserver((mutations) => {
1476 let found = false;
1477
1478 for (let mutation of mutations) {
1479 for (let node of mutation.addedNodes) {
1480 if (node.classList && node.classList.contains("descriptions")) {
1481 found = true;
1482 break;
1483 }
1484 }
1485
1486 if (found) break;
1487 }
1488
1489 if (!found) return;
1490
1491 observeDescription();
1492 }).observe(doc.find("#war-react-root ul.f-war-list"), { childList: true });
1493 });
1494}
1495
1496function observeDescription() {
1497 requireElement(".descriptions .members-list > li:not(.tt-userinfo-container)").then(() => {
1498 estimateStatsInList(".descriptions .members-list > li:not(.tt-userinfo-container)", (row) => {
1499 if (hasClass(row, "join") || hasClass(row, "timer-wrap")) {
1500 if (hasClass(row.nextElementSibling, "tt-userinfo-container")) row.nextElementSibling.remove();
1501
1502 return {};
1503 }
1504
1505 return {
1506 userId: (row.find("a.user.name").getAttribute("data-placeholder") || row.find("a.user.name > span").getAttribute("title")).match(
1507 /.* \[([0-9]*)]/i
1508 )[1],
1509 level: parseInt(row.find(".level").innerText),
1510 };
1511 });
1512 });
1513
1514 requireElement("#war-react-root ul.f-war-list > li.descriptions ul.members-list").then(() => {
1515 new MutationObserver((mutations) => {
1516 let estimateCount = 0;
1517
1518 for (let mutation of mutations) {
1519 for (let node of mutation.removedNodes) {
1520 if (hasClass(node, "your") || hasClass(node, "enemy")) {
1521 if (hasClass(mutation.nextSibling, "tt-userinfo-container")) mutation.nextSibling.remove();
1522 }
1523 }
1524
1525 for (let node of mutation.addedNodes) {
1526 if (node && node.classList && (node.classList.contains("your") || node.classList.contains("enemy"))) {
1527 const userId = (
1528 node.find("a.user.name").getAttribute("data-placeholder") || node.find("a.user.name > span").getAttribute("title")
1529 ).match(/.* \[([0-9]*)]/i)[1];
1530 const level = parseInt(node.find(".level").innerText);
1531
1532 const container = doc.new({ type: "li", class: "tt-userinfo-container" });
1533 node.parentElement.insertBefore(container, node.nextElementSibling);
1534
1535 const row = doc.new({ type: "section", class: "tt-userinfo-row tt-userinfo-row--statsestimate" });
1536 container.appendChild(row);
1537
1538 if (!hasCachedEstimate(userId)) estimateCount++;
1539
1540 loadingPlaceholder(row, true);
1541 estimateStats(userId, false, estimateCount, level)
1542 .then((result) => {
1543 loadingPlaceholder(row, false);
1544 row.appendChild(
1545 doc.new({
1546 type: "span",
1547 text: `Stat Estimate: ${result.estimate}`,
1548 })
1549 );
1550 })
1551 .catch((error) => {
1552 loadingPlaceholder(row, false);
1553
1554 if (error.show) {
1555 row.appendChild(
1556 doc.new({
1557 type: "span",
1558 class: "tt-userinfo-message",
1559 text: error.message,
1560 attributes: { color: "error" },
1561 })
1562 );
1563 } else {
1564 row.remove();
1565 if (container.children.length === 0) container.remove();
1566 }
1567 });
1568 }
1569 }
1570 }
1571 }).observe(doc.find("#war-react-root ul.f-war-list > li.descriptions ul.members-list"), { childList: true });
1572 });
1573}
1574
1575function highlightOwnOC() {
1576 const member = document.find(`.crimes-list > li.item-wrap .team > a[href="/profiles.php?XID=${userdata.player_id}"]`);
1577 if (!member) return;
1578
1579 findParent(member, { class: "item-wrap" }).setAttribute("background-color", "green");
1580}
1581
1582function showFactionBalance() {
1583 const alreadyShown = doc.find(".user-info-list-wrap.money-depositors > li.depositor.tt-modified");
1584
1585 const balanceFaction = parseInt(doc.find("#money .give-block *[data-faction-money]").getAttribute("data-faction-money"));
1586 let balancePlayers = 0;
1587 let factionShow, factionShowAlt, hasHonors;
1588
1589 for (let balanceRow of doc.findAll(".user-info-list-wrap.money-depositors > li.depositor")) {
1590 balancePlayers += parseInt(balanceRow.find(".amount .money").getAttribute("data-value"));
1591
1592 if (!alreadyShown && !factionShow && !balanceRow.classList.contains("inactive")) {
1593 hasHonors = balanceRow.find(".factionWrap .user.faction img");
1594
1595 if (hasHonors) {
1596 factionShow = hasHonors.getAttribute("src");
1597 factionShowAlt = hasHonors.getAttribute("alt");
1598 hasHonors = true;
1599 } else {
1600 factionShow = balanceRow.find(".factionWrap .user.faction").innerText;
1601 }
1602
1603 hasHonors = !!hasHonors;
1604 }
1605 }
1606
1607 if (alreadyShown) {
1608 alreadyShown.find(".money").innerText = FORMATTER_NO_DECIMALS.format(balanceFaction - balancePlayers);
1609 } else {
1610 const row = doc.new({ type: "li", class: "depositor tt-modified" });
1611
1612 row.innerHTML = `
1613 <div class="clearfix">
1614 <div class="user name btFaction" style="width: 147px;">
1615 ${hasHonors ? `<img src='${factionShow}' border="0" alt="${factionShowAlt}"/>` : `<span>${factionShow}</span>`}
1616 </div>
1617 <div class="amount">
1618 <div class="show">
1619 $<span class="money" id="totalFaction">${FORMATTER_NO_DECIMALS.format(balanceFaction - balancePlayers)}</span>
1620 </div>
1621 </div>
1622 </div>
1623 `;
1624
1625 const userWrap = doc.find(".user-info-list-wrap.money-depositors");
1626 userWrap.insertBefore(row, userWrap.firstElementChild);
1627 }
1628}
1629
1630function suggestBalance() {
1631 const inputElement = doc.find("#money-user");
1632 ["change", "paste", "keyup", "select", "focus", "input"].forEach((e) => inputElement.addEventListener(e, showBalance));
1633 doc.find("#money-user-cont").addEventListener("click", showBalance);
1634 showBalance();
1635
1636 function showBalance() {
1637 const user = findUser();
1638 if (!user) {
1639 doc.find("label[for='money-user']").innerText = "Select player: ";
1640 return;
1641 }
1642
1643 const name = user[1];
1644 const id = parseInt(user[2]);
1645 const balance = getBalance(id);
1646
1647 doc.find("label[for='money-user']").innerText = `${name} has a balance of $${FORMATTER_NO_DECIMALS.format(balance)}`;
1648 }
1649
1650 function findUser() {
1651 return doc.find("#money-user").value.match(/(.*) \[([0-9]*)]/i);
1652 }
1653
1654 function getBalance(id) {
1655 return parseInt(doc.find(`.depositor .user.name[href='/profiles.php?XID=${id}']`).parentElement.find(".amount .money").getAttribute("data-value")) || 0;
1656 }
1657}
1658
1659function displayWarOverTimes() {
1660 warOverviewLoaded().then(() => {
1661 doc.findAll("ul.f-war-list.war-new div.status-wrap div.timer").forEach((timer) => {
1662 if (!timer.parentElement.find("div.timer.tt-timer")) {
1663 let timerParts = timer.innerText.split(":").map((x) => parseInt(x));
1664 let time = timerParts[0] * 24 * 60 * 60 + timerParts[1] * 60 * 60 + timerParts[2] * 60 + timerParts[3];
1665 let overDate = formatDateObject(new Date(new Date().setSeconds(time)));
1666 let rawHTML = `<div class="timer tt-timer">${overDate.formattedTime} ${overDate.formattedDate}</div>`;
1667 timer.insertAdjacentHTML("afterEnd", rawHTML);
1668 }
1669 });
1670 });
1671}
1672
1673function foldFactionDesc() {
1674 if (!doc.find("div[role='main'] i.tt-collapse-desc")) {
1675 let rawHTML = "<i class='tt-collapse-desc fas fa-caret-down' style='padding-top: 9px;padding-left: 7px;'></i>";
1676 doc.find("div[role='main'] div.tt-checkbox-wrap").insertAdjacentHTML("beforeEnd", rawHTML);
1677 doc.find("i.tt-collapse-desc").addEventListener("click", (event) => {
1678 event.target.classList.toggle("fa-caret-down");
1679 event.target.classList.toggle("fa-caret-right");
1680 let facDesc = doc.find("div[role='main'] div.cont-gray10");
1681 if (facDesc.style.display == "none") {
1682 facDesc.toggleAttribute("style");
1683 } else {
1684 facDesc.style.display = "none";
1685 }
1686 doc.find("div[role='main'] div.tt-options").parentElement.classList.toggle("active");
1687 doc.find("div[role='main'] div.tt-options").parentElement.classList.toggle("all-rounded");
1688 });
1689 }
1690}
1691