Filter 17Lands data

Adds a input to quickly filter cards by name. Separate by commas to see multiple cards. Click `/` to quickly focus on input

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name        Filter 17Lands data
// @namespace   Violentmonkey Scripts
// @match       https://www.17lands.com/*
// @grant       none
// @version     1.4.0
// @author      rsromanowski
// @license     MIT
// @description Adds a input to quickly filter cards by name. Separate by commas to see multiple cards. Click `/` to quickly focus on input
// @require https://cdn.jsdelivr.net/npm/@violentmonkey/dom@2
// @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1
// ==/UserScript==

// URL
// https://www.17lands.com/card_data
// ?expansion=SOS
// &format=PremierDraft
// &user_group=top
// &color=w~u~g~multicolor~colorless
// &deck_color=G
// &rarity=common~uncommon
// &start=2026-04-21
// &end=2026-04-28

// Dropwdonws:
// color,rarity - button no id
// dates, no id
//
// Filters:
// container > container .card-performance-checkbox
// inputs hidden and readonly. ui checkbox, checked class toggled

function init() {
	switch (window.location.pathname) {
		case "/card_data":
			initCardData();
			break;
		case "/card_data/details":
			initCardDetails();
			break;
		default:
			console.log(`Nothing to do on page: ${window.location.pathname}`);
			break;
	}
}

function initCardData() {
	function findComponents() {
		const expansion = document.querySelector("select#expansion");
		const format = document.querySelector("select#format");
		const users = document.querySelector("select#user-group");
		const deckColors = document.querySelector("select#deck_color");
		console.log(
			`Dropdowns: expansion=${expansion}, format=${format}, users=${users}, deckColors=${deckColors}`,
		);
	}
	findComponents();

	function prettify() {
		const toolbar =
			document.querySelector("div > div > select").parentElement.parentElement;
		toolbar.style.cssText += " justify-content: space-between";

		const divs = document.querySelectorAll("#app > div > div");
		divs.forEach((d) => {
			d.classList.add("container");
		});
	}

	function filterTable(filter) {
		const columns = [
			{ name: "Name", index: 0, isFilter: true },
			{ name: "Color", index: 1, isFilter: false },
			{ name: "Rarity", index: 2, isFilter: false },
		];

		const filterColumns = columns.filter((c) => c.isFilter).map((c) => c.index);

		const rarityRegex = /r[:=]([curm])/i;
		const colorRegex = /c[:=]([wubrgcm])/i;

		// Currently only one table on page
		// Headers are in thead
		const rows = document.querySelectorAll(`table > tbody > tr`);

		const clauses = filter.split(",");
		// const rarityClauses = clauses.filter((c) => rarityRegex.test(c));
		// const colorClauses = clauses.filter((c) => colorRegex.test(c));
		const nameClauses = clauses.filter(
			(c) => !rarityRegex.test(c) && !colorRegex.test(c),
		);

		const orFilter = nameClauses
			.filter((f) => f.trim().length > 0)
			.map((f) => f.trim())
			.join("|");
		const regex = new RegExp(`${orFilter}`, "i");
		const isFoundInTds = (td) => regex.test(td.innerHTML);
		const isFound = (childrenArr) => childrenArr.some(isFoundInTds);
		const setTrStyleDisplay = ({ style, children }) => {
			style.display = isFound([
				...filterColumns.map((c) => children[c]), // <-- filter Columns
			])
				? ""
				: "none";
		};

		rows.forEach(setTrStyleDisplay);
	}

	function createQueryInput() {
		const div = document.querySelector(".container:first-of-type");
		const i = document.createElement("input");
		i.id = "q";
		i.className = "form-control";
		i.placeholder =
			'Press \'/\' to focus. Search by card names. i.e. "Tome Blast" or "tome,scatter"';
		div.appendChild(i);

		i.addEventListener("input", (event) => {
			filterTable(event.target.value);
		});

		// Select all text of focus for quick overwrite
		i.addEventListener("focus", function () {
			this.select();
		});
	}

	VM.observe(document.body, () => {
		const table = document.querySelector("table");

		if (table) {
			createQueryInput();
			prettify();

			return true; // disconnect observer
		}
	});

	VM.shortcut.register("/", () => {
		document.getElementById("q").focus();
	});
}

// Doesn't work since dropdown changed
function initCardDetails() {
	VM.shortcut.register("k", () => {
		const dropdown = document.getElementById("card");
		dropdown.selectedIndex = Math.max(0, dropdown.selectedIndex - 1);
		dropdown.dispatchEvent(new window.Event("change", { bubbles: true }));
	});

	VM.shortcut.register("j", () => {
		const dropdown = document.getElementById("card");
		dropdown.selectedIndex = Math.min(
			dropdown.childElementCount - 1,
			dropdown.selectedIndex + 1,
		);
		dropdown.dispatchEvent(new window.Event("change", { bubbles: true }));
	});
}

init();