Board Game Geek - One page auction geeklists

Loads all geeklist items into a single view simmilar to Peyo61's external tool.

目前為 2022-08-29 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Board Game Geek - One page auction geeklists
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Loads all geeklist items into a single view simmilar to Peyo61's external tool.
// @author       Kempeth @ boardgamegeek
// @match        https://boardgamegeek.com/geeklist/*
// @icon         https://cf.geekdo-static.com/icons/favicon2.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

const FETCHLIMIT = 20000;

var nodeBtnConvert;
var nodeProgressFull;
var nodeProgressEmpty;
var nodeItemList;
var nodeFirstItem;

var geeklistid;
var taggedItems = []; // list of ids
var items = []; // data from api

(function() {
    'use strict';

    //console.log('root function');

    init();
})();

function retry(f)
{
    window.setTimeout(f, 500);
}

function init()
{
    //console.log('init function');

    var els = document.getElementsByTagName('gg-geeklist-page-ui');
    //console.log(els);
    if (els.length == 0) { retry(init); return; }

    els = els[0].getElementsByTagName('header');
    //console.log(els);
    if (els.length == 0) { retry(init); return; }

    els = els[0].getElementsByTagName('nav');
    //console.log(els);
    if (els.length == 0) { retry(init); return; }

    var el = els[0];
    nodeBtnConvert = document.createElement('button');
    nodeBtnConvert.innerHTML = "One Page Auction";
    nodeBtnConvert.className = "btn btn-warning";
    nodeBtnConvert.onclick = convert;
    el.append(nodeBtnConvert);
}

function setProgress(done, total)
{
    var percentage = done * 100.0 / total;
    nodeProgressFull.style.width = percentage + '%';
    nodeProgressEmpty.style.width = (100 - percentage) + '%';
    nodeProgressFull.setAttribute('aria-valuemax', total);
    nodeProgressFull.setAttribute('aria-valuenow', done);
    if (percentage > 50)
    {
        nodeProgressFull.innerHTML = done + ' / ' + total;
        nodeProgressEmpty.innerHTML = '';
    }
    else
    {
        nodeProgressEmpty.innerHTML = done + ' / ' + total;
        nodeProgressFull.innerHTML = '';
    }
}

function convert()
{
    var els = document.getElementsByTagName('gg-geeklist-page-ui');
    //console.log(els);
    if (els.length == 0) { retry(convert); return; }

    els = els[0].getElementsByTagName('div');
    //console.log(els);
    if (els.length == 0) { retry(convert); return; }

    var footerNav = els[0].getElementsByTagName('nav');
    //console.log(footerNav);
    if (els.length == 0) { retry(convert); return; }
    footerNav = footerNav[footerNav.length - 1];

    els = els[0].getElementsByTagName('header');
    //console.log(els);
    if (els.length == 0) { retry(convert); return; }

    els = els[0].getElementsByTagName('nav');
    //console.log(els);
    if (els.length == 0) { retry(convert); return; }
    var headerNav = els[0];

    els = document.getElementsByTagName('gg-geeklist-items-ui');
    //console.log(els);
    if (els.length == 0) { retry(convert); return; }

    els = els[0].getElementsByTagName('div');
    //console.log(els);
    if (els.length == 0) { retry(convert); return; }
    nodeItemList = els[0];

    // remove header items
    for (var child of headerNav.childNodes)
    {
        if (child.tagName != 'GG-THUMBS-GEEKGOLD-GIVEN' && child.tagName != 'GG-SUBSCRIPTION-BUTTON')
        {
            headerNav.removeChild(child);
        }
    }

    // add header items
    var progress = document.createElement('div');
    progress.className = "progress";
    progress.style.height = "32px";
    progress.style.width = "256px";
    headerNav.appendChild(progress);

    nodeProgressFull = document.createElement('div');
    nodeProgressFull.className = "progress-bar";
    nodeProgressFull.setAttribute('role', 'progressbar');
    nodeProgressFull.setAttribute('aria-valuemin', 0);
    nodeProgressFull.setAttribute('aria-valuemax', 0);
    nodeProgressFull.setAttribute('aria-valuenow', 0);
    nodeProgressFull.style.width = '0%';
    nodeProgressFull.innerHTML = '';
    progress.appendChild(nodeProgressFull);

    nodeProgressEmpty = document.createElement('div');
    nodeProgressEmpty.className = "progress-bar bg-secondary";
    nodeProgressEmpty.setAttribute('role', 'progressbar');
    nodeProgressEmpty.style.width = '100%';
    nodeProgressEmpty.innerHTML = '0 / ???';
    progress.appendChild(nodeProgressEmpty);

    var btn = document.createElement('button');
    btn.innerHTML = "Refresh";
    btn.className = "btn btn-primary";
    headerNav.appendChild(btn);

    // Sort Dropdown
    var ddSort = document.createElement('div');
    ddSort.className = "dropdown";
    headerNav.appendChild(ddSort);

    var ddSortBtn = document.createElement('button');
    ddSortBtn.innerText = "Sort ";
    ddSortBtn.className = "btn btn-secondary dropdown-toggle";
    ddSort.appendChild(ddSortBtn);

    var ddSortList = document.createElement('ul');
    ddSortList.className = "dropdown-menu";
    ddSort.appendChild(ddSortList);

    ddSortBtn.onclick = () => {
        ddSortList.style.display = (ddSortList.style.display == "block") ? "none" : "block";
    };
    ddSortBtn.onblur = () => {
        window.setTimeout(() => {
            ddSortList.style.display = "none";
        }, 200);
    };

    var ddSortPosted = document.createElement('li');
    ddSortPosted.innerHTML = "<button type=\"button\" class=\"dropdown-item\">Posted</button>";
    ddSortPosted.onclick = () => {
        console.log('sorting by default');
        sortBy('');
    };
    ddSortList.appendChild(ddSortPosted);

    var ddSortName = document.createElement('li');
    ddSortName.innerHTML = "<button type=\"button\" class=\"dropdown-item\">Name</button>";
    ddSortName.onclick = () => {
        console.log('sorting by name');
        sortBy('name');
    };
    ddSortList.appendChild(ddSortName);

    // Filter Dropdown
    var ddFilter = document.createElement('div');
    ddFilter.className = "dropdown";
    headerNav.appendChild(ddFilter);

    var ddFilterBtn = document.createElement('button');
    ddFilterBtn.innerText = "Filter ";
    ddFilterBtn.className = "btn btn-secondary dropdown-toggle";
    ddFilter.appendChild(ddFilterBtn);

    var ddFilterList = document.createElement('ul');
    ddFilterList.className = "dropdown-menu";
    ddFilter.appendChild(ddFilterList);

    ddFilterBtn.onclick = () => {
        ddFilterList.style.display = (ddFilterList.style.display == "block") ? "none" : "block";
    };
    ddFilterBtn.onblur = () => {
        window.setTimeout(() => {
            ddFilterList.style.display = "none";
        }, 200);
    };

    var ddFilterBoardgames = document.createElement('li');
    ddFilterBoardgames.className = "dropdown-item";
    ddFilterBoardgames.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterBoardgames\"> " +
      "<label class=\"form-check-label\" for=\"filterBoardgames\">Boardgames</label>";
    ddFilterList.appendChild(ddFilterBoardgames);
    document.getElementById('filterBoardgames').onclick = filterList;

    var ddFilterExpansions = document.createElement('li');
    ddFilterExpansions.className = "dropdown-item";
    ddFilterExpansions.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterExpansions\"> " +
      "<label class=\"form-check-label\" for=\"filterExpansions\">Expansions</label>";
    ddFilterList.appendChild(ddFilterExpansions);
    document.getElementById('filterExpansions').onclick = filterList;

    var ddFilterDivider = document.createElement('li');
    ddFilterDivider.innerHTML = "<hr class=\"dropdown-divider\">";
    ddFilterList.appendChild(ddFilterDivider);

    var ddFilterUntagged = document.createElement('li');
    ddFilterUntagged.className = "dropdown-item";
    ddFilterUntagged.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterUntagged\"> " +
      "<label class=\"form-check-label\" for=\"filterUntagged\">Untagged</label>";
    ddFilterList.appendChild(ddFilterUntagged);
    document.getElementById('filterUntagged').onclick = filterList;

    var ddFilterTagged = document.createElement('li');
    ddFilterTagged.className = "dropdown-item";
    ddFilterTagged.innerHTML = "<input class=\"form-check-input\" type=\"checkbox\" value=\"\" checked id=\"filterTagged\"> " +
      "<label class=\"form-check-label\" for=\"filterTagged\">Tagged</label>";
    ddFilterList.appendChild(ddFilterTagged);
    document.getElementById('filterTagged').onclick = filterList;

    // remove convert button
    nodeBtnConvert.parentNode.removeChild(nodeBtnConvert);

    // remove geeklist items
    //nodeItemList.innerHTML = '';

    nodeFirstItem = nodeItemList.childNodes[0];
    nodeItemList.className = "tw-grid tw-gap-2";

    // remove footer nav
    //footerNav.parentNode.removeChild(footerNav);
    footerNav.innerHTML = '';

    geeklistid = window.location.pathname.split('/')[2];

    getTaggedItems();
    items = [];
    fetchPage(1);
}

function fetchPage(page)
{
    // https://api.geekdo.com/api/listitems?page=1&listid=301669
    var url = "https://api.geekdo.com/api/listitems?page=" + page + "&listid=" + geeklistid;

    fetch(url)
        .then(response => response.json())
        .then(
        data => {
            //console.log(data);

            var nr = (data.pagination.pageid - 1) * data.pagination.perPage;
            for (var item of data.data)
            {
                items[nr] = item;
                nr++;
                makeRow(nr, item);
            }
            setProgress(nr, data.pagination.total);

            if (data.data.length < data.pagination.perPage)
            {
                console.log("last page");
            }
            else if (page < FETCHLIMIT)
            {
                console.log("more pages");
                fetchPage(page + 1);
            }
        }
    );
}

function makeRow(nr, item)
{
    var row = document.createElement('span');
    row.className = "tw-relative tw-flex tw-flex-wrap tw-items-center tw-gap-x-1.5 tw-gap-y-2 tw-rounded-md tw-border tw-border-gray-400 tw-bg-gray-100 tw-p-2";
    const thingtype = item.item.href.split('/')[1];
    const bodylower = item.body.toLowerCase();
    const notsold = bodylower.includes("not sold");
    const soldto = bodylower.includes("sold to");
    const sold = bodylower.includes("sold");
    const auctionclosed = bodylower.includes("auction closed");

    var struckout = 0;
    var struckoutstart = bodylower.indexOf("[-]");
    var struckoutend = bodylower.indexOf("[/-]");
    while (struckoutstart >= 0 && struckoutend >= 0)
    {
        struckout += struckoutend - struckoutstart;
        struckoutstart = bodylower.indexOf("[-]", struckoutstart + 1);
        struckoutend = bodylower.indexOf("[/-]", struckoutend + 1);
    }


    var tagButton = document.createElement("button");
    if (taggedItems.includes(item.id))
    {
        tagButton.className = "btn btn-sm btn-primary";
        tagButton.innerText = "Tagged";
    }
    else
    {
        tagButton.className = "btn btn-sm btn-outline-primary";
        tagButton.innerText = "Tag";
    }
    tagButton.onclick = () => {
        toggleTaggedItem(tagButton, item.id);
    };
    row.appendChild(tagButton);

    var tagGeeklistLink = document.createElement("a");
    tagGeeklistLink.href = "/geeklist/" + geeklistid + "/test?itemid=" + item.id + "#" + item.id;
    tagGeeklistLink.target = "_blank";
    tagGeeklistLink.innerText = nr + ".";
    row.appendChild(tagGeeklistLink);

    var txtThingType = document.createTextNode(thingtype == "boardgame" ? "Boardgame" : thingtype == "boardgameexpansion" ? "Expansion" : "Other");
    row.appendChild(txtThingType);

    var tagGeeklistItemLink = document.createElement("a");
    tagGeeklistItemLink.href = item.item.href;
    tagGeeklistItemLink.target = "_blank";
    tagGeeklistItemLink.style.maxWidth = "50%";
    tagGeeklistItemLink.style.textOverflow = "ellipsis";
    tagGeeklistItemLink.style.overflow = "hidden";
    tagGeeklistItemLink.style.whiteSpace = "nowrap";
    tagGeeklistItemLink.innerText = item.item.name;
    row.appendChild(tagGeeklistItemLink);

    var tagState = document.createElement("span");
    if (notsold)
    {
        tagState.innerText = "not sold";
        tagState.className = "badge bg-danger";
    }
    else if (soldto || sold)
    {
        tagState.innerText = "sold";
        tagState.className = "badge bg-danger";
    }
    else if (auctionclosed || struckout > 100)
    {
        tagState.innerText = "closed";
        tagState.className = "badge bg-secondary";
    }
    else
    {
        tagState.innerText = "open";
        tagState.className = "badge bg-success";
    }
    row.appendChild(tagState);

    nodeItemList.insertBefore(row, nodeFirstItem);
    item.node = row;
}

function encodeWithTextNode(htmlstring) {
	let textarea = document.createElement('textarea');
	let text = document.createTextNode(htmlstring);
	textarea.appendChild(text);
	return textarea.innerHTML;
}

function getTaggedItems()
{
    //GM_setValue("GamePage_PlayTime_" + gameid, JSON.stringify(cached));
    //var cached = JSON.parse(GM_getValue("GamePage_PlayTime_" + gameid, "{}"));
    taggedItems = JSON.parse(GM_getValue("TaggedItems_" + geeklistid, "[]"));
}

function toggleTaggedItem(btn, itemid)
{
    if (taggedItems.includes(itemid))
    {
        taggedItems = taggedItems.filter(i => i != itemid);
        btn.className = "btn btn-sm btn-outline-primary";
        btn.innerText = "Tag";
    }
    else
    {
        taggedItems[taggedItems.length] = itemid;
        btn.className = "btn btn-sm btn-primary";
        btn.innerText = "Tagged";
    }
    GM_setValue("TaggedItems_" + geeklistid, JSON.stringify(taggedItems));
}

function sortBy(type)
{
    var sorted;
    if (type == null || type == '' || type == 'default')
    {
        sorted = items;
    }
    else if (type == 'name')
    {
        sorted = [...items];
        sorted = sorted.sort((a,b) => {
            var r = a.item.name > b.item.name ? 1 : -1;
            //console.log(a.item.name, b.item.name, r);
            return r;
        });
    }

    for (var i in sorted)
    {
        var item = sorted[i];
        nodeItemList.removeChild(item.node);
        nodeItemList.insertBefore(item.node, nodeFirstItem);
    }
}

function filterList()
{
    console.log("filtering list");
    const fBg = document.getElementById('filterBoardgames').checked;
    const fEx = document.getElementById('filterExpansions').checked;
    const fUT = document.getElementById('filterUntagged').checked;
    const fTg = document.getElementById('filterTagged').checked;

    for (var i in items)
    {
        var show = true;
        var item = items[i];
        const thingtype = item.item.href.split('/')[1];
        const tagged = taggedItems.includes(item.id);
        if (!fBg && thingtype == 'boardgame') show = false;
        if (!fEx && thingtype == 'boardgameexpansion') show = false;
        if (!fUT && !tagged) show = false;
        if (!fTg && tagged) show = false;

        var cls = (show ? "tw-relative tw-flex tw-flex-wrap tw-items-center tw-gap-x-1.5 tw-gap-y-2 tw-rounded-md tw-border tw-border-gray-400 tw-bg-gray-100 tw-p-2" : "tw-relative d-none tw-flex-wrap tw-items-center tw-gap-x-1.5 tw-gap-y-2 tw-rounded-md tw-border tw-border-gray-400 tw-bg-gray-100 tw-p-2");
        item.node.className = cls;
        //console.log(show);
    }
}





QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址