// ==UserScript==
// @name Board Game Geek - One page auction geeklists
// @namespace http://tampermonkey.net/
// @version 0.5
// @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()
{
var els = document.getElementsByTagName('gg-geeklist-page-ui');
if (els.length == 0) { retry(init); return; }
els = els[0].getElementsByTagName('header');
if (els.length == 0) { retry(init); return; }
els = els[0].getElementsByTagName('nav');
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 = total > 0 ? (done * 100.0 / total) : 0;
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');
if (els.length == 0) { retry(convert); return; }
els = els[0].getElementsByTagName('div');
if (els.length == 0) { retry(convert); return; }
var footerNav = els[0].getElementsByTagName('nav');
if (els.length == 0) { retry(convert); return; }
footerNav = footerNav[footerNav.length - 1];
els = els[0].getElementsByTagName('header');
if (els.length == 0) { retry(convert); return; }
els = els[0].getElementsByTagName('nav');
if (els.length == 0) { retry(convert); return; }
var headerNav = els[0];
els = document.getElementsByTagName('gg-geeklist-items-ui');
if (els.length == 0) { retry(convert); return; }
els = els[0].getElementsByTagName('div');
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 = () => {
new Promise((resolve, reject) => {
filterList();
resolve();
});
};
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 = () => {
new Promise((resolve, reject) => {
filterList();
resolve();
});
};
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 = () => {
new Promise((resolve, reject) => {
filterList();
resolve();
});
};
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 = () => {
new Promise((resolve, reject) => {
filterList();
resolve();
});
};
// 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 => {
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);
var spanRight = document.createElement('span');
spanRight.className = "tw-ml-auto tw-hidden tw-pr-0.5 tw-text-sm tw-text-muted md:tw-inline";
row.appendChild(spanRight);
var btnPreview = document.createElement('button');
btnPreview.className = "btn btn-sm text-bg-secondary";
btnPreview.innerText = "Preview";
btnPreview.onclick = () => {
if (btnPreview.data != null)
{
btnPreview.data.parentNode.removeChild(btnPreview.data);
btnPreview.data = null;
}
else
{
makePreview(item, btnPreview);
}
};
spanRight.appendChild(btnPreview);
nodeItemList.insertBefore(row, nodeFirstItem);
item.node = row;
}
function makePreview(item, btn)
{
var gli = document.createElement('div');
gli.className = "geeklist-item tw-grid tw-items-start tw-gap-2.5 sm:tw-grid-cols-[minmax(auto,_185px)_minmax(65%,_1fr)] lg:tw-gap-3 lg:tw-gap-x-4";
item.node.parentNode.insertBefore(gli, item.node.nextSibling);
btn.data = gli;
const gliImg = makeItemImage(item);
gli.appendChild(gliImg);
var gliDiv = document.createElement('div');
gliDiv.className = "tw-min-w-0 sm:tw-mt-0";
gli.appendChild(gliDiv);
const gliDivBody = makeItemBody(item);
gliDiv.appendChild(gliDivBody);
}
function makeItemUser(item)
{
}
function makeItemBody(item)
{
var body = document.createElement('div');
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(item.bodyXml, "text/xml");
for (var child of xmlDoc.firstChild.childNodes)
{
console.log(child);
if (child.nodeName == 'safehtml')
{
var safehtml = document.createElement('div');
safehtml.innerHTML = child.firstChild.data;
body.appendChild(safehtml);
}
else
{
var other = document.createElement('div');
other.className = "alert alert-warning";
other.innerText = "displaying " + child.nodeName + " is not yet supported by one page auction userscript.";
body.appendChild(other);
}
}
return body;
}
function makeItemImage(item)
{
var gliImg = document.createElement('a');
gliImg.className = "geeklist-item__img-link tw-relative tw-block tw-overflow-hidden tw-rounded-md tw-bg-gray-100";
gliImg.href = item.linkedImage.href;
var gliImgDiv = document.createElement('div');
gliImgDiv.className = "tw-h-44 tw-w-full sm:tw-aspect-w-1 sm:tw-aspect-h-1";
gliImg.appendChild(gliImgDiv);
var gliImgDivImg = document.createElement('img');
gliImgDivImg.className = "img-fluid geeklist-item__img tw-absolute tw-w-full tw-h-full tw-p-2.5 md:tw-p-3.5 tw-object-contain tw-z-10";
gliImgDivImg.loading = "lazy";
gliImgDivImg.style.display = "inherit";
gliImgDivImg.src = item.item.imageSets.square100.src;
gliImgDivImg.srcset = item.item.imageSets.square100.src + " 1x," + item.item.imageSets.square100["src@2x"] + " 2x";
gliImgDivImg.alt = item.linkedImage.alt;
gliImgDivImg.sizes = "";
gliImgDiv.appendChild(gliImgDivImg);
gliImgDivImg = document.createElement('img');
gliImgDivImg.className = "img-fluid tw-absolute tw-w-full tw-h-full tw-object-cover tw-scale-125 tw-opacity-30 tw-blur-xl tw-bg-white";
gliImgDivImg.loading = "lazy";
gliImgDivImg.style.display = "inherit";
gliImgDivImg.src = item.item.imageSets.square100.src;
gliImgDivImg.srcset = item.item.imageSets.square100.src + " 1x," + item.item.imageSets.square100["src@2x"] + " 2x";
gliImgDivImg.sizes = "";
gliImgDiv.appendChild(gliImgDivImg);
gliImgDiv = document.createElement('div');
gliImgDiv.className = "tw-absolute tw-right-2 tw-bottom-2 tw-z-20";
gliImgDiv.innerHTML = "<gg-rating-indicator tooltip=\"Avg. Rating\" shape=\"hex\" size=\"md\">" +
"<span container=\"body\" class=\"hex rating--" + Math.floor(item.stats.average) + " tw-flex tw-font-semibold tw-h-[var(--hex-h)] tw-items-center tw-justify-center tw-text-white tw-text-xs tw-w-[var(--hex-w)]\"" +
" style=\"background:" + getColorFromRating(item.stats.average) + "; --hex-w: 1.625rem; --hex-h: 1.875rem; -webkit-clip-path: polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%); clip-path: polygon(50% 0,100% 25%,100% 75%,50% 100%,0 75%,0 25%);\">" +
"<span class=\"tw-pb-px\"> " + (Math.round(item.stats.average * 10) / 10) + " </span>" +
"</span>" +
"</gg-rating-indicator>";
gliImg.appendChild(gliImgDiv);
return gliImg;
}
function getColorFromRating(rating)
{
const i = Math.floor(rating);
switch (i)
{
case 1:
case 2: return "#b2151f";
case 3:
case 4: return "#d71925";
case 5:
case 6: return "#5369a2";
case 7: return "#1978b3";
case 8: return "#1d804c";
case 9:
case 10: return "#186b40";
default: return "#666e75";
}
}
function encodeWithTextNode(htmlstring) {
let textarea = document.createElement('textarea');
let text = document.createTextNode(htmlstring);
textarea.appendChild(text);
return textarea.innerHTML;
}
function getTaggedItems()
{
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;
return r;
});
}
for (var i = 0; i < sorted.length; i++)
{
var item = sorted[i];
nodeItemList.removeChild(item.node);
nodeItemList.insertBefore(item.node, nodeFirstItem);
/*if ((i + 1) % 100 == 0 || i == sorted.length - 1)
{
setProgress(i+1, sorted.length);
}*/
}
}
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 = 0; i < items.length; i++)
{
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;
/*if ((i + 1) % 100 == 0 || i == items.length - 1)
{
setProgress(i+1, items.length);
}*/
}
}