// ==UserScript== // @name Steam Curator: Import Review Link from Backloggd // @namespace https://github.com/ceeprus // @version 2.4 // @description Adds a searchable "Import from Backloggd" box next to the "URL for full review (Optional)" field on Steam Curator review-edit pages, letting you pick one of your Backloggd reviews, auto-fill the link, and optionally import the review text into "Write your review". Prompts for your Backloggd username on first use. // @author Cee // @icon https://www.google.com/s2/favicons?sz=64&domain=store.steampowered.com // @match https://store.steampowered.com/curator/*/admin/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @grant GM_openInTab // @grant GM_registerMenuCommand // @connect backloggd.com // @run-at document-idle // @license MIT // @downloadURL https://raw.githubusercontent.com/ceeprus/userscript/main/steam/backloggd-review-import.user.js // @updateURL https://raw.githubusercontent.com/ceeprus/userscript/main/steam/backloggd-review-import.user.js // ==/UserScript== (function () { 'use strict'; const USERNAME_KEY = 'backloggd_username'; const CACHE_KEY_PREFIX = 'bglg_reviews_cache_'; const CACHE_TTL_MS = 1000 * 60 * 60 * 6; // 6 hours const MAX_RESULTS_SHOWN = 8; // Bump this whenever the shape of cached review items changes, so old // caches (missing newer fields like `text`) are automatically refetched. const CACHE_SCHEMA_VERSION = 2; const STYLE_ID = 'bglg-import-style'; // Returns the saved Backloggd username, prompting the user to set one if // this is the first time the script has run (or it was never configured). function getUsername(promptIfMissing) { let username = GM_getValue(USERNAME_KEY, '').trim(); if (!username && promptIfMissing) { username = promptForUsername(); } return username || null; } function promptForUsername() { const input = window.prompt( 'Enter your Backloggd username (the part after backloggd.com/u/ in your profile URL):' ); const trimmed = (input || '').trim(); if (trimmed) { GM_setValue(USERNAME_KEY, trimmed); return trimmed; } return ''; } function changeUsername() { const current = GM_getValue(USERNAME_KEY, ''); const input = window.prompt( 'Enter your Backloggd username (the part after backloggd.com/u/ in your profile URL):', current ); if (input === null) return; const trimmed = input.trim(); if (!trimmed) return; GM_setValue(USERNAME_KEY, trimmed); } if (typeof GM_registerMenuCommand === 'function') { GM_registerMenuCommand('Set/change Backloggd username', changeUsername); } function cacheKey(username) { return CACHE_KEY_PREFIX + username.toLowerCase(); } function injectStyles() { if (document.getElementById(STYLE_ID)) return; const style = document.createElement('style'); style.id = STYLE_ID; style.textContent = ` .bglg-import-container { position: relative; display: inline-block; margin-left: 10px; vertical-align: middle; font-family: inherit; } .bglg-import-toggle { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border: 1px solid #2a475e; border-radius: 4px; background: #16202d; color: #67c1f5; font-size: 12px; text-decoration: none; cursor: pointer; transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease; } .bglg-import-toggle:hover { background: #2a475e; border-color: #67c1f5; color: #fff; } .bglg-import-toggle-icon { width: 16px; height: 16px; border-radius: 2px; flex-shrink: 0; } .bglg-import-panel { position: absolute; top: 100%; left: 0; z-index: 1000; margin-top: 4px; width: 340px; max-height: 340px; overflow-y: auto; background: #1b2838; border: 1px solid #2a475e; border-radius: 4px; padding: 8px; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6); font-size: 12px; color: #c6d4df; } .bglg-import-search { width: 100%; box-sizing: border-box; padding: 6px 8px; margin-bottom: 6px; background: #0f1923; border: 1px solid #2a475e; color: #c6d4df; border-radius: 3px; font-size: 12px; } .bglg-import-search:focus { outline: none; border-color: #67c1f5; } .bglg-import-status { margin-bottom: 6px; opacity: 0.7; } .bglg-import-item { display: flex; align-items: center; gap: 8px; padding: 5px 6px; border-radius: 3px; cursor: pointer; } .bglg-import-item-cover { flex: 0 0 32px; width: 32px; height: 45px; object-fit: cover; border-radius: 2px; background: #0f1923; } .bglg-import-item-cover-placeholder { border: 1px solid #2a475e; } .bglg-import-item-text { flex: 1 1 auto; min-width: 0; } .bglg-import-item-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .bglg-import-item-date { font-size: 11px; opacity: 0.6; margin-top: 1px; } .bglg-import-item:hover { background: #2a475e; color: #fff; } .bglg-import-empty { padding: 5px 6px; opacity: 0.6; } .bglg-import-bottom-row { display: flex; justify-content: space-between; align-items: center; margin-top: 6px; } .bglg-import-refresh, .bglg-import-loadmore { color: #67c1f5; font-size: 11px; text-decoration: none; } .bglg-import-refresh:hover, .bglg-import-loadmore:hover { color: #fff; text-decoration: underline; } .bglg-import-arrow { display: inline-block; font-size: 10px; } .bglg-import-blurb-prompt { display: inline-flex; align-items: center; gap: 6px; margin-top: 6px; padding: 4px 10px; border: 1px solid #2a475e; border-radius: 4px; background: #16202d; color: #c6d4df; font-size: 12px; } .bglg-import-blurb-prompt a { color: #67c1f5; text-decoration: none; cursor: pointer; } .bglg-import-blurb-prompt a:hover { color: #fff; text-decoration: underline; } `; document.head.appendChild(style); } // Walk up from the "open review" link to find the related game's title. function findGameTitle(reviewLink) { const card = reviewLink.closest('.review-card'); if (card) { let sib = card.previousElementSibling; while (sib) { if (sib.classList && sib.classList.contains('game-name')) { const h3 = sib.querySelector('h3'); if (h3 && h3.textContent.trim()) return h3.textContent.trim(); } sib = sib.previousElementSibling; } // Fallback: cover image alt text within the card const img = card.querySelector('img[alt]'); if (img && img.alt && img.alt.trim()) return img.alt.trim(); } // Last-resort generic fallback let el = reviewLink; for (let i = 0; i < 6 && el; i++) { el = el.parentElement; if (!el) break; const img = el.querySelector('img[alt]'); if (img && img.alt && img.alt.trim()) return img.alt.trim(); } return null; } // Find the review date (e.g. "Jun 12, 2026") from the card's