diff --git a/source/ContentScript/element-factories/owns-column.ts b/source/ContentScript/element-factories/owns-column.ts new file mode 100644 index 0000000..5b215fb --- /dev/null +++ b/source/ContentScript/element-factories/owns-column.ts @@ -0,0 +1,26 @@ +import Trader from "../trader"; + +export default class OwnsColumn { + static create(trader: Trader): HTMLTableCellElement { + let count = trader.ownsCount ?? 0; + + let td = document.createElement("td"); + td.style.textAlign = "center"; + + if (count === 0) { + td.innerText = "0"; + td.style.color = "#cbd5e1"; + + return td; + } + + let a = document.createElement("a"); + a.target = "_blank"; + a.href = trader.ownsList ?? "#"; + a.innerHTML = `${count} `; + + td.appendChild(a); + + return td; + } +} diff --git a/source/ContentScript/element-factories/wants-column.ts b/source/ContentScript/element-factories/wants-column.ts new file mode 100644 index 0000000..f499fae --- /dev/null +++ b/source/ContentScript/element-factories/wants-column.ts @@ -0,0 +1,26 @@ +import Trader from "../trader"; + +export default class WantsColumn { + static create(trader: Trader): HTMLTableCellElement { + let count = trader.wantsCount ?? 0; + + let td = document.createElement("td"); + td.style.textAlign = "center"; + + if (count === 0) { + td.innerText = "0"; + td.style.color = "#cbd5e1"; + + return td; + } + + let a = document.createElement("a"); + a.target = "_blank"; + a.href = trader.wantsList ?? "#"; + a.innerHTML = `${count} `; + + td.appendChild(a); + + return td; + } +} diff --git a/source/ContentScript/index.ts b/source/ContentScript/index.ts index 186104d..46a948e 100644 --- a/source/ContentScript/index.ts +++ b/source/ContentScript/index.ts @@ -1,3 +1,5 @@ +import TradeFinderEnhancer from "./page-enhancers/trade-finder-enhancer"; +import TradeFinderTheirsEnhancer from "./page-enhancers/trade-finder-theirs-enhancer"; import WhoOwnsEnhancer from "./page-enhancers/who-owns-enhancer"; import WhoWantsEnhancer from "./page-enhancers/who-wants-enhancer"; @@ -8,3 +10,11 @@ if (window.location.href.includes("who-owns.php")) { if (window.location.href.includes("who-wants.php")) { WhoWantsEnhancer.enhance(document); } + +if (window.location.href.includes("profile/trade_finder.php")) { + TradeFinderEnhancer.enhance(document); +} + +if (window.location.href.includes("profile/trade_finder_theirs.php")) { + TradeFinderTheirsEnhancer.enhance(document); +} diff --git a/source/ContentScript/page-elements/trade-finder-row.ts b/source/ContentScript/page-elements/trade-finder-row.ts new file mode 100644 index 0000000..34972e2 --- /dev/null +++ b/source/ContentScript/page-elements/trade-finder-row.ts @@ -0,0 +1,88 @@ +import OwnsColumn from "../element-factories/owns-column"; +import WantsColumn from "../element-factories/wants-column"; +import Trader from "../trader"; +import TraderRepository from "../trader-repository"; + +export default class TradeFinderRow { + constructor( + public row: HTMLTableRowElement, + public cells: NodeListOf, + public repository: TraderRepository | null + ) {} + + static parse( + row: HTMLTableRowElement, + repository: TraderRepository | null = null + ): TradeFinderRow | null { + let cells = row.querySelectorAll("td"); + + if (cells.length < 2) { + return null; + } + + return new TradeFinderRow(row, cells, repository); + } + + get username(): string { + return this.cells[0] + .querySelector("a")! + .querySelector("strong")! + .innerText.trim(); + } + + get profile(): string { + return this.cells[0].querySelector("a")!.href; + } + + get open(): boolean { + return this.cells[0] + .querySelector("span")! + .innerText.includes("Open to trades"); + } + + get count(): number { + let link = this.cells[1].querySelector("a"); + + return link ? parseInt(link.innerText.match(/\d+/)![0]) : 0; + } + + get list(): string | null { + let link = this.cells[1].querySelector("a"); + + return link ? link.href : null; + } + + get trader(): Trader | null { + if (this.repository === null) { + throw new Error( + "Cannot get trader from TradeFinderRow without a repository" + ); + } + + return this.repository.find(this.username); + } + + addWantsColumn() { + if (this.trader === null) { + throw new Error( + "Cannot add wants column to a TradeFinderRow that has no trader data" + ); + } + + let td = WantsColumn.create(this.trader!); + + this.row.appendChild(td); + } + + addOwnsColumn() { + if (this.trader === null) { + throw new Error( + "Cannot add owns column to a TradeFinderRow that has no trader data" + ); + } + + let td = OwnsColumn.create(this.trader!); + + this.row.appendChild(td); + } +} diff --git a/source/ContentScript/page-enhancers/trade-finder-enhancer.ts b/source/ContentScript/page-enhancers/trade-finder-enhancer.ts new file mode 100644 index 0000000..43a9ecf --- /dev/null +++ b/source/ContentScript/page-enhancers/trade-finder-enhancer.ts @@ -0,0 +1,30 @@ +import TradeFinderRow from "../page-elements/trade-finder-row"; +import TraderRepository from "../trader-repository"; + +export default class TradeFinderEnhancer { + static addHeader(page: Document) { + let th = document.createElement("th"); + th.innerText = "# of their wants that I own"; + th.style.textAlign = "center"; + th.scope = "col"; + + page.querySelector("table thead tr")?.appendChild(th); + } + + static async enhance(page: Document) { + let repository = await TraderRepository.load(); + + this.addHeader(page); + + let tbody = page.querySelector("table tbody")!; + let rows = tbody.querySelectorAll("tr"); + + Array.from(rows) + .map((row) => + TradeFinderRow.parse(row as HTMLTableRowElement, repository) + ) + .filter((row) => row !== null) + .filter((row) => row!.trader !== null) + .forEach((row) => row!.addWantsColumn()); + } +} diff --git a/source/ContentScript/page-enhancers/trade-finder-theirs-enhancer.ts b/source/ContentScript/page-enhancers/trade-finder-theirs-enhancer.ts new file mode 100644 index 0000000..b2a4e74 --- /dev/null +++ b/source/ContentScript/page-enhancers/trade-finder-theirs-enhancer.ts @@ -0,0 +1,30 @@ +import TradeFinderRow from "../page-elements/trade-finder-row"; +import TraderRepository from "../trader-repository"; + +export default class TradeFinderTheirsEnhancer { + static addHeader(page: Document) { + let th = document.createElement("th"); + th.innerText = "# of my wants that they own"; + th.style.textAlign = "center"; + th.scope = "col"; + + page.querySelector("table thead tr")?.appendChild(th); + } + + static async enhance(page: Document) { + let repository = await TraderRepository.load(); + + this.addHeader(page); + + let tbody = page.querySelector("table tbody")!; + let rows = tbody.querySelectorAll("tr"); + + Array.from(rows) + .map((row) => + TradeFinderRow.parse(row as HTMLTableRowElement, repository) + ) + .filter((row) => row !== null) + .filter((row) => row!.trader !== null) + .forEach((row) => row!.addOwnsColumn()); + } +} diff --git a/source/ContentScript/trader-repository.ts b/source/ContentScript/trader-repository.ts index 57069d8..322eead 100644 --- a/source/ContentScript/trader-repository.ts +++ b/source/ContentScript/trader-repository.ts @@ -29,8 +29,8 @@ export default class TraderRepository { let text = await response.text(); let page = new DOMParser().parseFromString(text, "text/html"); - let table = page.querySelector("table")!; - let rows = table.querySelectorAll("tr"); + let tbody = page.querySelector("table tbody")!; + let rows = tbody.querySelectorAll("tr"); return Array.from(rows) .map((row) => Trader.fromWantsRow(row as HTMLTableRowElement)) @@ -47,8 +47,8 @@ export default class TraderRepository { let text = await response.text(); let page = new DOMParser().parseFromString(text, "text/html"); - let table = page.querySelector("table")!; - let rows = table.querySelectorAll("tr"); + let tbody = page.querySelector("table tbody")!; + let rows = tbody.querySelectorAll("tr"); return Array.from(rows) .map((row) => Trader.fromOwnsRow(row as HTMLTableRowElement)) diff --git a/source/ContentScript/trader.ts b/source/ContentScript/trader.ts index a092139..38a58d0 100644 --- a/source/ContentScript/trader.ts +++ b/source/ContentScript/trader.ts @@ -1,10 +1,4 @@ -interface RowData { - username: string; - profile: string; - open: boolean; - count: number; - list: string | null; -} +import TradeFinderRow from "./page-elements/trade-finder-row"; export default class Trader { constructor( @@ -17,53 +11,11 @@ export default class Trader { public wantsList: string | null = null ) {} - /** - * Get the underlying trader data from a row in a "Trade finder" table. - */ - static parseRow(row: HTMLTableRowElement): RowData | null { - let cells = row.querySelectorAll("td"); - - if (cells.length < 2) { - return null; - } - - let profileLink = cells[0].querySelector("a"); - - if (!profileLink) { - return null; - } - - let username = - profileLink.querySelector("strong")?.innerText ?? - profileLink.innerText; - let profile = profileLink.href; - - let open = - cells[0] - .querySelector("span") - ?.innerText.includes("Open to trades") ?? false; - - let wantsLink = cells[1].querySelector("a"); - - let count = wantsLink - ? parseInt(wantsLink.innerText.match(/\d+/)![0]) - : 0; - let list = wantsLink ? wantsLink.href : null; - - return { - username, - profile, - open, - count, - list, - }; - } - /** * Create a Trader from a row in a "Trade finder wants" table. */ static fromWantsRow(row: HTMLTableRowElement): Trader | null { - let data = Trader.parseRow(row); + let data = TradeFinderRow.parse(row); if (data === null) { return null; @@ -84,7 +36,7 @@ export default class Trader { * Create a Trader from a row in a "Trade finder owns" table. */ static fromOwnsRow(row: HTMLTableRowElement): Trader | null { - let data = Trader.parseRow(row); + let data = TradeFinderRow.parse(row); if (data === null) { return null;