- // ==UserScript==
- // @name Player Filters
- // @namespace dev.kwack.torn.player-filters
- // @version 0.0.3
- // @description Adds player filters to various userlists in Torn City
- // @author Kwack [2190604]
- // @match https://www.torn.com/*
- // @icon
- // @grant none
- // ==/UserScript==
-
- // THIS SCRIPT IS STILL BEING TESTED, do NOT expect all to work perfectly.
- // If you find any issues, please report them to me on Torn or Discord.
-
- (() => {
- const STATUS_ENUM = {
- OKAY: "OKAY",
- HOSPITAL: "HOSPITAL",
- JAIL: "JAIL",
- FALLEN: "FALLEN",
- FEDERAL: "FEDERAL",
- TRAVELING: "TRAVELING",
- ABROAD: "ABROAD",
- UNKNOWN: "UNKNOWN",
- };
-
- const ONLINE_STATUS_ENUM = {
- ONLINE: "ONLINE",
- OFFLINE: "OFFLINE",
- IDLE: "IDLE",
- UNKNOWN: "UNKNOWN",
- };
-
- const FILTERS = [
- {
- check: () =>
- document.location.pathname === "/blacklist.php" || document.location.pathname === "/friendlist.php",
- table: () => $("div.content-wrapper > div.blacklist > ul.user-info-blacklist-wrap")[0],
- insertFilters: (f) => f.insertBefore($("div.content-wrapper > div.blacklist > hr")),
- rows: (t) => t.children,
- filters: {
- name: {
- type: "text",
- fn: (r) => r.find("a.user.name span.honor-text:not(.honor-text-svg)").text(),
- },
- id: {
- type: "text",
- fn: (r) =>
- r
- .find("a.user.name")
- .attr("href")
- .match(/\?XID=([\d]+)/)[1],
- },
- level: {
- type: "min-max",
- min: 1,
- max: 100,
- fn: (r) => r.find(".level")[0].lastChild.textContent.trim(),
- },
- status: {
- type: "select",
- options: STATUS_ENUM,
- fn: (r) =>
- STATUS_ENUM[r.find("div.status > span:last-child").text().trim().toUpperCase()] ??
- STATUS_ENUM.UNKNOWN,
- },
- online: {
- type: "select",
- options: ONLINE_STATUS_ENUM,
- fn: (r) =>
- ONLINE_STATUS_ENUM[
- r
- .find("ul#iconTray > .iconShow")
- .attr("title")
- .match(/<b>([\w]+)<\/b>/)[1]
- .toUpperCase()
- ] ?? ONLINE_STATUS_ENUM.UNKNOWN,
- },
- },
- },
- {
- check: () =>
- document.location.pathname === "/index.php" &&
- $(document.body).data("abroad") &&
- new URLSearchParams(document.location.search).get("page") === "people",
- table: () => $("div.content-wrapper > div.travel-people > ul.users-list")[0],
- rows: (t) => t.children,
- insertFilters: (f) => f.insertAfter($("div.content-wrapper > div.info-msg-cont").last()),
- filters: {
- name: {
- type: "text",
- fn: (r) => r.find("a.user.name span.honor-text:not(.honor-text-svg)").text(),
- },
- id: {
- type: "text",
- fn: (r) =>
- r
- .find("a.user.name")
- .attr("href")
- .match(/\?XID=([\d]+)/)[1],
- },
- level: {
- type: "min-max",
- min: 1,
- max: 100,
- fn: (r) => r.find(".level")[0].lastChild.textContent.trim(),
- },
- status: {
- type: "select",
- options: Object.entries(STATUS_ENUM)
- .filter(([k]) => k !== "ABROAD" && k !== "TRAVELING" && k !== "JAIL")
- .reduce((a, [k, v]) => ((a[k] = v), a), {}),
- fn: (r) =>
- STATUS_ENUM[r.find("span.status > span:last-child").text().trim().toUpperCase()] ??
- STATUS_ENUM.UNKNOWN,
- },
- online: {
- type: "select",
- options: ONLINE_STATUS_ENUM,
- fn: (r) =>
- ONLINE_STATUS_ENUM[
- r
- .find("ul#iconTray > .iconShow")
- .attr("title")
- .match(/<b>([\w]+)<\/b>/)[1]
- .toUpperCase()
- ] ?? ONLINE_STATUS_ENUM.UNKNOWN,
- },
- },
- },
- {
- check: () => document.location.pathname === "/bounties.php",
- table: () =>
- $(
- "div.content-wrapper > div.newspaper-wrap div.bounties-wrap > div.bounties-cont > ul.bounties-list"
- )[0],
- insertFilters: (f) => f.insertBefore($("div.content-wrapper > div.newspaper-wrap div.bounties-wrap")),
- rows: (t) => [...t.children].filter((c) => c.getAttribute("data-id")),
- filters: {
- name: {
- type: "text",
- fn: (r) => r.find("ul.item div.target > a").text(),
- },
- id: {
- type: "text",
- fn: (r) =>
- r
- .find("ul.item div.target > a")
- .attr("href")
- .match(/\?XID=([\d]+)/)[1],
- },
- level: {
- type: "min-max",
- min: 1,
- max: 100,
- fn: (r) => r.find("ul.item div.level")[0].lastChild.textContent.trim(),
- },
- status: {
- type: "select",
- options: STATUS_ENUM,
- fn: (r) =>
- STATUS_ENUM[r.find("ul.item div.status").children().last().text().toUpperCase()] ??
- STATUS_ENUM.UNKNOWN,
- },
- },
- },
- {
- check: () =>
- document.location.pathname === "/page.php" &&
- new URLSearchParams(document.location.search).get("sid").toLowerCase() === "userlist",
- table: () => $("div.content-wrapper > div.userlist-wrapper > ul.user-info-list-wrap")[0],
- rows: (t) => t.children,
- insertFilters: (f) => f.insertAfter($("div.content-wrapper > div.content-title")),
- filters: {
- name: {
- type: "text",
- fn: (r) => r.find("a.user.name span.honor-text:not(.honor-text-svg)").text().trim(),
- },
- id: {
- type: "text",
- fn: (r) =>
- r
- .find("a.user.name")
- .attr("href")
- .match(/\?XID=([\d]+)/)[1],
- },
- level: {
- type: "min-max",
- min: 1,
- max: 100,
- fn: (r) => r.find(".level").children().last().text().trim(),
- },
- status: {
- type: "select",
- // There's no way to differentiate between traveling and abroad, so all are set to traveling.
- options: Object.entries(STATUS_ENUM)
- .filter(([k]) => k !== "ABROAD")
- .reduce((a, [k, v]) => ((a[k] = v), a), {}),
- fn: (r) => {
- const icons = r.find("div.level-icons-wrap > .user-icons ul#iconTray > li").toArray();
- for (const i of icons) {
- const iconNumber = i.id?.match(/^icon([\d]+)_/)?.[1];
- switch (iconNumber) {
- case "15":
- return STATUS_ENUM.HOSPITAL;
- case "16":
- return STATUS_ENUM.JAIL;
- case "70":
- return STATUS_ENUM.FEDERAL;
- case "71":
- return STATUS_ENUM.TRAVELING;
- case "77":
- return STATUS_ENUM.FALLEN;
- }
- }
- return STATUS_ENUM.OKAY;
- },
- online: {
- type: "select",
- options: ONLINE_STATUS_ENUM,
- fn: (r) =>
- ONLINE_STATUS_ENUM[
- r
- .find("li > div:not(.level-icons-wrap) > ul#iconTray > li")
- .text()
- .match(/<b>([\w]+)<\/b>/)[1]
- .toUpperCase()
- ] ?? ONLINE_STATUS_ENUM.UNKNOWN,
- },
- },
- },
- },
- {
- check: () => {
- if (document.location.pathname !== "/factions.php") return false;
- const params = new URLSearchParams(document.location.search);
- if (params.get("step") === "profile") return true;
- if (params.get("step") === "your" && document.location.hash.includes("tab=info")) return true;
- return false;
- },
- },
- ];
-
- function init() {
- // Finds first filter where check is valid
- const filter = FILTERS.find((f) => f.check());
- if (!filter) return; // No filter found
- let f = createFilter(filter);
- new MutationObserver(() => {
- if (document.contains(f[0])) return;
- if (!filter.table()) return;
- showAllRows(filter);
- f = createFilter(filter);
- }).observe(document.body, { childList: true, subtree: true });
- injectStyle();
- }
-
- function createFilter(filterOptions) {
- const filters = $("<div/>", {
- id: "kw--filter-container",
- style:
- "display: flex;justify-content: space-between;background: linear-gradient(to bottom, #ff149311, #ff149344);" +
- "padding: 10px;border-radius: 10px; margin: 10px 0;flex-wrap: wrap;gap: 10px;",
- });
- Object.entries(filterOptions.filters).forEach(([name, { type, min, max, options, fn }]) => {
- const filter = $("<div/>", {
- class: "kw--filter",
- style: "display: flex; flex-direction: column; gap: 0.25rem;",
- }).appendTo(filters);
- filter.append(
- $("<label/>", {
- text: name + ":",
- style: "font-weight: bolder; font-size: 0.9rem",
- })
- );
- switch (type) {
- case "text":
- filter.append(
- $("<input/>", {
- type: "text",
- class: "kw--filter-text kw--filter-row",
- })
- .data("filter", { name, type, fn })
- .on("input", () => handleFilterChange(filterOptions))
- );
- break;
- case "min-max":
- filter.append(
- $("<input/>", {
- type: "number",
- class: "kw--filter-min kw--filter-row",
- min,
- max,
- style: "min-width: 50px;",
- })
- .attr("placeholder", "min")
- .data("filter", { name, type, is: "min", fn })
- .on("input", () => handleFilterChange(filterOptions))
- );
- filter.append(
- $("<input/>", {
- type: "number",
- class: "kw--filter-max kw--filter-row",
- min,
- max,
- style: "min-width: 50px;",
- })
- .attr("placeholder", "max")
- .data("filter", { name, type, is: "max", fn })
- .on("input", () => handleFilterChange(filterOptions))
- );
- break;
- case "select":
- const select = $("<select/>", {
- class: "kw--filter-select kw--filter-row",
- })
- .data("filter", { name, type, fn })
- .on("change", () => handleFilterChange(filterOptions))
- .append(
- $("<option/>", {
- value: "",
- text: "ANY",
- })
- )
- .appendTo(filter);
- Object.entries(options)
- .filter(([key, value]) => key !== "UNKNOWN" || value !== "UNKNOWN")
- .forEach(([key, value]) => {
- select.append(
- $("<option/>", {
- value: key,
- text: value,
- })
- );
- });
- break;
- }
- });
- filterOptions.insertFilters(filters);
- return filters;
- }
-
- function handleFilterChange(filterOptions) {
- const table = filterOptions.table();
- if (!table) return console.error("[kw--player-filters]: Table could not be found");
- const rows = filterOptions.rows(table);
- const filters = $(".kw--filter-row")
- .toArray()
- .map((f) => ({ f: $(f).data("filter"), e: $(f) }));
- [...rows].forEach((r) => {
- const row = $(r);
- const data = filters.map(({ f, e }) => {
- const value = f.fn(row);
- const filterVal = e.val();
- if (!filterVal || !value) return true;
- try {
- switch (f.type) {
- case "text":
- return value.toLowerCase().includes(filterVal.toLowerCase());
- case "min-max":
- if (f.is === "min") return parseInt(value) >= parseInt(filterVal);
- if (f.is === "max") return parseInt(value) <= parseInt(filterVal);
- return true;
- case "select":
- return value === filterVal;
- }
- } catch (e) {
- console.error(e);
- console.debug({ f, row, value });
- return true;
- }
- });
- if (data.every((d) => d)) row.show();
- else row.hide();
- });
- }
-
- function showAllRows(pageData) {
- const table = pageData.table();
- if (!table) return console.error("[kw--player-filters]: Table could not be found");
- const rows = pageData.rows(table);
- [...rows].forEach((r) => $(r).show());
- }
-
- init();
-
- function injectStyle() {
- const style = `
- #kw--filter-container input {
- border: 1px solid var(--input-border-color, #ccc);
- border-radius: 5px;
- font-family: Arial, serif;
- color: var(--input-color, #000);
- background: var(--input-background-color, #fff);
- padding: 9px 10px;
- }
-
- #kw--filter-container select {
- height: 34px;
- line-height: 34px;
- color: #444;
- border: 1px solid var(--default-panel-divider-inner-side-color, #fff);
- border-radius: 5px;
- background: linear-gradient(to bottom, #e4e4e4, #f2f2f2);
- }
-
- body.dark-mode #kw--filter-container select {
- color: #ddd;
- background: #000;
- border-color: #444;
- }
- `;
- if (typeof GM_addStyle !== "undefined") return GM_addStyle(style);
- $(document.head).append($("<style/>", { text: style }));
- }
- })();