Ebay regex filter for search results + Price sort (S-Card Layout)

Regex results filter + price sorting for eBay S-Card layout

スクリプトをインストールするには、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         Ebay regex filter for search results + Price sort (S-Card Layout)
// @namespace    ebay-regex-filter-scard
// @version      1.1
// @description  Regex results filter + price sorting for eBay S-Card layout
// @match        https://www.ebay.com/sch/*
// @grant        none
// @license MIT
// @run-at       document-idle
// ==/UserScript==

(function () {
'use strict';

const ROW_ID = "ebay-regex-row-scard";
const LS_KEY = "ebay_regex_filter_scard";
let activeRegex = null;
let savedPattern = localStorage.getItem(LS_KEY) || "";
let sortState = 0;
let originalOrder = [];

let dynamicStyle = document.createElement("style");
document.head.appendChild(dynamicStyle);

function getListings() {
    return [...document.querySelectorAll("li.s-card")];
}

function getVisibleListings() {
    return getListings().filter(el => !el.closest(".s-clipped[aria-hidden='true']"));
}

function getContainer() {
    const vis = getVisibleListings();
    if (!vis.length) return null;
    return vis[0].parentElement;
}

function extractPrice(item) {
    const priceEl = item.querySelector(".s-card__price");
    if (!priceEl) return Infinity;
    const raw = priceEl.innerText.trim();
    const m = raw.match(/([\$£€]?\s*\d[\d,\.]*)/);
    if (!m) return Infinity;
    const cleaned = m[1].replace(/[^0-9.]/g, '');
    const num = parseFloat(cleaned);
    return isNaN(num) ? Infinity : num;
}

function getSearchableText(item) {
    const t = item.querySelector(".s-card__title");
    const s = item.querySelector(".s-card__subtitle");
    return ((t ? t.innerText : "") + "\n" + (s ? s.innerText : "")).toLowerCase();
}

function rebuildStyleFilter() {
    if (!activeRegex) {
        dynamicStyle.textContent = "";
        return;
    }

    const items = getListings();
    let css = "";

    items.forEach((item, i) => {
        item.setAttribute("data-regex-uid", i);
        const txt = getSearchableText(item);

        if (!activeRegex.test(txt)) {
            css += `li.s-card[data-regex-uid="${i}"]{display:none!important;}`;
        }
    });

    dynamicStyle.textContent = css;
}

function sortListings() {
    const container = getContainer();
    if (!container) return;

    const items = getVisibleListings();
    if (!items.length) return;

    if (sortState === 0) originalOrder = [...items];

    sortState = sortState === 0 ? 1 : sortState === 1 ? 2 : 0;

    let out;
    if (sortState === 0) {
        out = originalOrder;
    } else {
        out = [...items].sort((a, b) => {
            const pa = extractPrice(a);
            const pb = extractPrice(b);
            return sortState === 1 ? pa - pb : pb - pa;
        });
    }

    out.forEach(el => container.appendChild(el));
    rebuildStyleFilter();
}

function buildUI() {
    const row = document.createElement("div");
    row.id = ROW_ID;

    row.style.margin = "12px 0";
    row.style.padding = "8px 0";
    row.style.display = "flex";
    row.style.alignItems = "center";
    row.style.gap = "12px";

    const label = document.createElement("span");
    label.textContent = "Regex Filter:";
    label.style.fontSize = "16px";
    label.style.fontWeight = "600";

    const input = document.createElement("input");
    input.type = "text";
    input.placeholder = "sony|jbl|adam|whatever";
    input.value = savedPattern;

    input.style.flex = "1";
    input.style.height = "32px";
    input.style.padding = "4px 6px";
    input.style.border = "1px solid #bbb";
    input.style.borderRadius = "6px";
    input.style.fontSize = "14px";

    if (savedPattern.trim()) {
        try {
            activeRegex = new RegExp(savedPattern, "i");
            input.style.borderColor = "#0a0";
        } catch {
            activeRegex = null;
            input.style.borderColor = "#c00";
        }
    }

    input.addEventListener("input", () => {
        const v = input.value.trim();
        localStorage.setItem(LS_KEY, v);

        if (!v) {
            activeRegex = null;
            input.style.borderColor = "#bbb";
            rebuildStyleFilter();
            return;
        }

        try {
            activeRegex = new RegExp(v, "i");
            input.style.borderColor = "#0a0";
        } catch {
            activeRegex = null;
            input.style.borderColor = "#c00";
        }

        rebuildStyleFilter();
    });

    const b = document.createElement("button");
    b.textContent = "Sort Price";
    b.style.padding = "6px 10px";
    b.style.border = "1px solid #bbb";
    b.style.borderRadius = "6px";
    b.style.cursor = "pointer";

    b.onclick = () => sortListings();

    row.appendChild(label);
    row.appendChild(input);
    row.appendChild(b);

    return row;
}

function insertRow() {
    if (document.getElementById(ROW_ID)) return;

    const t =
        document.querySelector("#srp-river-main") ||
        document.querySelector("#mainContent") ||
        document.querySelector("#content") ||
        document.body;

    const r = buildUI();
    t.insertBefore(r, t.firstChild);
}

insertRow();
rebuildStyleFilter();

})();