您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Use customized words in any NYT Connections puzzle (Daily and Archive)
// ==UserScript== // @name Custom NYT Connections Puzzle // @namespace https://github.com/amitmadnawat // @version 1.8.2 // @description Use customized words in any NYT Connections puzzle (Daily and Archive) // @author Amit Madnawat // @license MIT // @match https://www.nytimes.com/games/connections* // @exclude https://www.nytimes.com/games-assets/* // @icon https://www.nytimes.com/games-assets/v2/assets/icons/connections.svg // @run-at document-idle // @grant none // ==/UserScript== (function () { "use strict"; const customGroups = [ { name: "Subway", words: ["Underground", "Metro", "Footlong", "Tube"] }, { name: "Law", words: ["Civil", "Criminal", "Divorce", "Corporate"] }, { name: "Bunny", words: ["Easter", "Hare", "Playboy", "Carrot"] }, { name: "Sadhika", words: ["Will", "You", "Marry", "Me"] } ]; // Track whether we should show or hide the results screen let showResultsScreenFlag = false; let resultsScreenDelay = 15000; (function injectCSS() { const css = ` [data-testid="card-label"]{ display:flex; justify-content:center; align-items:center; text-align:center; white-space:nowrap; /* prefer single-line; script will allow wrap if needed */ overflow:visible; padding:4px; line-height:1; } @media (max-width:480px){ [data-testid="card-label"]{ padding:3px; } } /* remove pseudo commas for list items inside containers where we add .no-after */ .no-after li::after{ content:none !important; } .no-after::after{ content:none !important; } /* Dynamic results screen visibility */ .pz-moment__congrats.on-stage { display: flex !important; } .pz-game-screen.on-stage { display: flex !important; } .pz-game-field > article > section > a { display: none !important; } /* Remove promo link under connections board */ [data-testid="connections-board"] { section { a { display: none !important; } } } `; const s = document.createElement("style"); s.id = "nyt-connections-size-only-css"; s.textContent = css; document.head.appendChild(s); })(); function getDateFromUrl() { try { const m = window.location.pathname.match(/\/games\/connections\/(\d{4}-\d{2}-\d{2})/); return m ? m[1] : null; } catch (e) { return null; } } function formatLocalDate(d) { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } async function fetchJsonV2(date) { const url = `https://www.nytimes.com/svc/connections/v2/${date}.json`; const resp = await fetch(url, { credentials: "include" }); if (!resp.ok) throw new Error(`HTTP ${resp.status}`); return resp.json(); } function findLabelTextNode(label) { for (const n of label.childNodes) { if (n.nodeType === Node.TEXT_NODE && n.nodeValue && n.nodeValue.trim().length) return n; } return null; } function walkTextNodes(root, fn) { const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false); let node; while ((node = walker.nextNode())) fn(node); } // Build maps from API data function buildMapsFromApi(data) { const wordMap = {}; const titleMap = {}; data.categories.forEach((cat, catIdx) => { const customGroup = customGroups[catIdx] || { name: `Group ${catIdx + 1}`, words: [] }; if (cat.title) titleMap[cat.title.trim().toLowerCase()] = customGroup.name; if (Array.isArray(cat.cards)) { cat.cards.forEach((card, cIdx) => { const orig = String(card.content || "").trim().toLowerCase(); const custom = customGroup.words[cIdx] || customGroup.words[0] || ""; if (orig) wordMap[orig] = custom; }); } }); return { wordMap, titleMap }; } // Apply mapped custom words to visible cards function applyCardReplacements(wordMap) { const labels = document.querySelectorAll('[data-testid="card-label"]'); labels.forEach((label) => { const origText = label.textContent.trim(); const key = origText.toLowerCase(); if (key && key in wordMap) { const textNode = findLabelTextNode(label); if (textNode) textNode.nodeValue = wordMap[key]; else label.textContent = wordMap[key]; } }); } // Per-word shrink logic // Per-word max-size buckets (px). Tuned small for phones; only font-size/line-height changed. function maxSizeForLength(len) { if (len <= 4) return 16; if (len <= 6) return 14; if (len <= 8) return 12; if (len <= 12) return 11; return 10; } // Shrink-to-fit for a single label: modify only font-size and line-height when needed. function shrinkLabelToFit(label) { const prevVis = label.style.visibility || ""; label.style.visibility = "hidden"; const textNode = findLabelTextNode(label); if (!textNode) { label.style.visibility = prevVis; return; } const text = textNode.nodeValue.trim(); const maxPx = maxSizeForLength(text.length); const minPx = 9; let fitted = false; let size = Math.min(maxPx, 20); while (size >= minPx) { label.style.fontSize = size + "px"; const fits = label.scrollWidth <= label.clientWidth + 1; if (fits) { fitted = true; break; } size -= 1; } if (!fitted) { label.style.whiteSpace = "normal"; const twoLineSize = Math.max(minPx, Math.min(11, maxPx)); label.style.fontSize = twoLineSize + "px"; label.style.lineHeight = "1.05"; } else { label.style.lineHeight = "1"; } label.style.visibility = prevVis; } let autosizeTimer = null; function autosizeAllLabels() { clearTimeout(autosizeTimer); autosizeTimer = setTimeout(() => { const labels = document.querySelectorAll('[data-testid="card-label"]'); labels.forEach((label) => shrinkLabelToFit(label)); }, 30); } function observeLabelChanges() { const labels = document.querySelectorAll('[data-testid="card-label"]'); labels.forEach((label) => { if (label.__autosize_obs) return; const obs = new MutationObserver(() => autosizeAllLabels()); obs.observe(label, { childList: true, characterData: true, subtree: true }); label.__autosize_obs = obs; }); } function formatPurpleRow() { setTimeout(() => { const solvedCategoryContainer = document.querySelectorAll('[data-testid="solved-category-container"]'); if (!solvedCategoryContainer) return; const purpleRowWordList = customGroups[3].words; const purpleRow = Array.from(solvedCategoryContainer).find((category) => Array.from(category.querySelectorAll('li[data-testid="solved-category-card"]')).every((wordListItem) => purpleRowWordList.includes(wordListItem.textContent) ) ); if (!purpleRow || purpleRow.length === 0) return; const purpleRowWordListItems = purpleRow.querySelectorAll('li[data-testid="solved-category-card"]'); purpleRowWordListItems.forEach((wordListItem, index, arr) => { const word = wordListItem.textContent; if (purpleRowWordList.includes(word)) { wordListItem.textContent = ""; if (index !== arr.length - 1) { wordListItem.style.display = "none"; } } }); if (purpleRowWordListItems[3]) purpleRowWordListItems[3].textContent = purpleRowWordList.join(" ") + "?"; }, 50); } // Ensure purple formatting persists by observing the purple container function ensurePersistentPurpleFormatting(container) { if (!container) return; if (container.__purple_obs_attached) return; const obs = new MutationObserver(() => { setTimeout(() => { formatPurpleRow(); autosizeAllLabels(); }, 45); }); obs.observe(container, { childList: true, subtree: true, characterData: true }); container.__purple_obs_attached = true; } function hideResultsScreen() { const congratsElement = document.querySelector(".pz-moment__congrats"); if (congratsElement) congratsElement.classList.remove("on-stage"); const gameScreen = document.querySelector(".pz-game-screen"); if (gameScreen) gameScreen.classList.add("on-stage"); } function showResultsScreen() { const congratsElement = document.querySelector(".pz-moment__congrats"); if (congratsElement) congratsElement.classList.add("on-stage"); const gameScreen = document.querySelector(".pz-game-screen"); if (gameScreen) gameScreen.classList.remove("on-stage"); } function observeGameCompletion() { const completionObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === "childList" && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE) { if ( node.classList && (node.classList.contains("pz-moment__congrats") || (node.querySelector && node.querySelector(".pz-moment__congrats"))) ) { if (!showResultsScreenFlag) { hideResultsScreen(); } else { showResultsScreen(); } } } }); } }); }); completionObserver.observe(document.body, { childList: true, subtree: true }); } // New: temporary on-stage CSS injection/removal to avoid flash when on-stage classes are added let _tempOnStageTimer = null; function addTempOnStageCss(ms) { try { // If already present, refresh timer only if (document.getElementById("nyt-temp-onstage")) { if (_tempOnStageTimer) { clearTimeout(_tempOnStageTimer); } _tempOnStageTimer = setTimeout(() => { const el = document.getElementById("nyt-temp-onstage"); if (el) el.remove(); _tempOnStageTimer = null; }, ms); return; } const css = ` /* Temporary rules to prevent flash while on-stage classes are settling */ .pz-moment__congrats, .pz-moment__congrats.on-stage { visibility: hidden !important; opacity: 0 !important; pointer-events: none !important; } .pz-game-screen, .pz-game-screen.on-stage { display: flex !important; visibility: visible !important; opacity: 1 !important; } `; const el = document.createElement("style"); el.id = "nyt-temp-onstage"; el.textContent = css; document.head.appendChild(el); _tempOnStageTimer = setTimeout(() => { const rr = document.getElementById("nyt-temp-onstage"); if (rr) rr.remove(); _tempOnStageTimer = null; }, ms); } catch (e) { _tempOnStageTimer = null; } } // Observe attribute changes to detect when 'on-stage' class is added to relevant elements const onStageObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type !== "attributes" || mutation.attributeName !== "class") continue; const target = mutation.target; if (!(target instanceof Element)) continue; try { if (target.matches && (target.matches(".pz-moment__congrats") || target.matches(".pz-game-screen"))) { if (target.classList && target.classList.contains("on-stage")) { addTempOnStageCss(resultsScreenDelay); } } } catch (e) { /* ignore */ } } }); // Start observing for class changes onStageObserver.observe(document.body, { attributes: true, subtree: true, attributeFilter: ["class"] }); // Add button click handler to toggle results screen visibility function setupResultsScreenToggle() { const button = document.querySelector('div.pz-game-field section button[class^="ActionButton-module_archiveGameOverButton"]'); if (button && !button.__resultsToggleAdded) { button.__resultsToggleAdded = true; button.addEventListener('click', function(e) { // Toggle the visibility state showResultsScreenFlag = !showResultsScreenFlag; resultsScreenDelay = 20; // Apply the appropriate visibility if (showResultsScreenFlag) { showResultsScreen(); } else { hideResultsScreen(); } // Prevent default behavior if needed e.stopPropagation(); return false; }); } } function updateArchiveTitle() { const h2Title = document.querySelector('h2.pz-moment__title'); const updatedTitle = 'Connections'; if (h2Title && h2Title.textContent !== updatedTitle) { h2Title.textContent = updatedTitle; } } function updateBotLinkContent() { const botDiv = document.querySelector('[data-testid="bot-link-cta"]'); const updatedHTML = `<p>How tough was today's puzzle?<br><span class="botLinkHref">Find out with Connections Bot ›</span></p>`; if (botDiv && botDiv.lastChild.innerHTML !== updatedHTML) { botDiv.lastChild.innerHTML = updatedHTML; } } function hideArchiveLinkButton() { const archiveLinkButton = document.querySelector('section[class^="Board-module_archiveGameOverButtonGroup"] a[class^="ActionButton-module_archiveGameOverButton"]'); if (archiveLinkButton) { archiveLinkButton.style.setProperty('display', 'none', 'important'); } } function removeArchiveGameOverLinkOnce() { function tryRemove() { const link = document.querySelector('div.pz-game-field a[class^="ActionButton-module_archiveGameOverButton"]'); if (link) { if (typeof link.remove === "function") link.remove(); else if (link.parentNode) link.parentNode.removeChild(link); return true; } return false; } if (tryRemove()) return; const observer = new MutationObserver(function () { if (tryRemove()) observer.disconnect(); }); observer.observe(document.body, { childList: true, subtree: true }); if (document.readyState === "loading") { window.addEventListener("DOMContentLoaded", function onLoad() { tryRemove(); window.removeEventListener("DOMContentLoaded", onLoad); }); } else { tryRemove(); } } const mainObserver = new MutationObserver((mutations, obs) => { const labels = document.querySelectorAll('[data-testid="card-label"]'); if (labels.length === 16) { obs.disconnect(); (async () => { try { let puzzleDate = getDateFromUrl(); if (!puzzleDate) puzzleDate = formatLocalDate(new Date()); const data = await fetchJsonV2(puzzleDate); if (!data || !Array.isArray(data.categories)) { console.error("Unexpected API response format for", puzzleDate); return; } const maps = buildMapsFromApi(data); applyCardReplacements(maps.wordMap); autosizeAllLabels(); observeLabelChanges(); // initial purple attempt and persistent formatting const purpleContainer = (function tryFormat() { formatPurpleRow(); // attempt to find the container quickly (may be set by formatPurpleRow) return document.querySelector('[data-testid="solved-category-container"].no-after') || null; })(); if (purpleContainer) ensurePersistentPurpleFormatting(purpleContainer); // Start light observer for congrats additions observeGameCompletion(); // Also call hide once (in case congrats already present) hideResultsScreen(); // Setup results screen toggle button setupResultsScreenToggle(); // Call so the archive link is removed as soon as the main observer detects the board ready removeArchiveGameOverLinkOnce(); hideArchiveLinkButton(); // Update Bot link text on the results page updateBotLinkContent(); // reveal observer: replace titles/words and react to solved container additions const revealObserver = new MutationObserver((mutList) => { mutList.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeType !== Node.ELEMENT_NODE) return; // If reveal modal inserted, replace category titles const titleElements = node.querySelectorAll(".category-title"); if (titleElements && titleElements.length) { titleElements.forEach((el, idx) => { if (idx < customGroups.length) el.textContent = customGroups[idx].name; }); } // If solved-category-container added or updated, run formatting and attach persistence if ((node.matches && node.matches('[data-testid="solved-category-container"]')) || (node.querySelector && node.querySelector('[data-testid="solved-category-container"]'))) { const found = (function f() { formatPurpleRow(); return document.querySelector('[data-testid="solved-category-container"].no-after') || null; })(); if (found) { ensurePersistentPurpleFormatting(found); autosizeAllLabels(); } } // If congrats element added anywhere, hide it and show game if (node.classList && (node.classList.contains("pz-moment__congrats") || (node.querySelector && node.querySelector(".pz-moment__congrats")))) { if (!showResultsScreenFlag) { hideResultsScreen(); } else { showResultsScreen(); } } // Check if the results toggle button was added if (node.querySelector && node.querySelector('div.pz-game-field section button[class^="ActionButton-module_archiveGameOverButton"]')) { setupResultsScreenToggle(); } if (node.querySelector && node.querySelector('div.pz-game-field a[class^="ActionButton-module_archiveGameOverButton"]')) { removeArchiveGameOverLinkOnce(); } hideArchiveLinkButton(); // Update Bot link text on the results page updateBotLinkContent(); // Walk text nodes for replacements walkTextNodes(node, (textNode) => { const t = String(textNode.nodeValue || "").trim(); if (!t) return; const lower = t.toLowerCase(); if (lower in maps.titleMap) { textNode.nodeValue = maps.titleMap[lower]; return; } for (const [origWord, customWord] of Object.entries(maps.wordMap)) { if (t.toLowerCase() === origWord.toLowerCase()) { textNode.nodeValue = customWord; break; } } }); }); }); autosizeAllLabels(); }); revealObserver.observe(document.body, { childList: true, subtree: true }); window.addEventListener("resize", autosizeAllLabels); } catch (err) { console.error("Failed to fetch NYT connections JSON for date:", err); } })(); } }); // Update the h2 title on the first page for archive puzzles updateArchiveTitle(); mainObserver.observe(document.body, { childList: true, subtree: true }); console.log("NYT Connections customizer loaded"); })();