您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Navigate to most recent incomplete crossword
// ==UserScript== // @name Puzzmo Crossword Navigator // @namespace http://tampermonkey.net/ // @version 1.0 // @description Navigate to most recent incomplete crossword // @author Michael Abon // @match https://www.puzzmo.com/* // @grant none // @esversion 8 // @license MIT // ==/UserScript== (function() { 'use strict'; function createButton(id = 'crossword-nav-btn') { const button = document.createElement('button'); button.id = id; button.textContent = 'Go to Latest Incomplete Crossword'; button.style.cssText = ` padding: 0.25rem 1.5rem; width: 100%; background: var(--theme-player); color: var(--theme-playerFG); border: none; border-radius: 0.300rem; font-size: 13px; cursor: pointer; transition: background 0.2s; font-family: "Poppins-SemiBold", "Poppins SemiBold", "Poppins"; `; button.addEventListener('click', navigateToLatestCrossword); return button; } function injectButton() { const todayIntro = document.getElementById('today-intro'); if (!todayIntro || document.getElementById('crossword-nav-btn')) return; const button = createButton(); // Insert before the target element const container = document.createElement('div'); container.style.cssText = ` margin-top: 1rem; display: flex; justify-content: flex-start; `; container.appendChild(button); todayIntro.parentNode.insertBefore(container, todayIntro); } function injectButtonInSidebar() { // Look for the specific link in the puzzle complete sidebar with "More puzzles" text const targetLinks = document.querySelectorAll('a[aria-label="Go to [object Object]"][href^="/today"]'); const targetLink = Array.from(targetLinks).find(link => link.innerText.trim() === 'More puzzles'); if (!targetLink || document.getElementById('crossword-nav-btn-sidebar')) return; const button = createButton('crossword-nav-btn-sidebar'); // Create container for the button - this will be a sibling of the parent with onclick const container = document.createElement('div'); container.style.cssText = ` margin-bottom: 12px; display: flex; justify-content: flex-start; `; container.appendChild(button); // Get the parent with onclick and its parent (grandparent) const parentWithOnClick = targetLink.parentElement; const grandparent = parentWithOnClick.parentElement; if (grandparent) { // Insert our container before the parent with onclick grandparent.insertBefore(container, parentWithOnClick); } } function getAuthData() { try { const puzmoAuth = localStorage.getItem('puzmoAuth'); if (!puzmoAuth) { throw new Error('No puzmoAuth found in localStorage'); } const authData = JSON.parse(puzmoAuth); return { token: authData.token, userID: authData.data.userID }; } catch (error) { console.error('Failed to parse auth data:', error); return null; } } async function getAuthHeaders() { const authData = getAuthData(); if (!authData) { throw new Error('Could not retrieve authentication data'); } return { 'Accept': '*/*', 'Accept-Language': 'en-CA,en-US;q=0.7,en;q=0.3', 'auth-provider': 'custom', 'authorization': `Bearer ${authData.token}`, 'puzzmo-gameplay-id': authData.userID, 'runtime': 'web', 'Content-Type': 'application/json', 'Origin': 'https://www.puzzmo.com', 'Referer': window.location.href }; } async function fetchCalendarData(month, year) { const query = ` query CalendarQuery( $month: Int! $year: Int! $gameFilter: String ) { dailiesForAMonth(month: $month, year: $year, gameFilter: $gameFilter) { day puzzles(cacheKey: "calendar") { series { id } lock { start type overrideRoles } status urlPath puzzle { id slug name emoji variantID subvariantID seriesNumber game { slug displayName flagsArr id } gameNameOverride currentAccountGamePlayed { slug completed pointsAwarded id } } id } id } }`; const headers = await getAuthHeaders(); try { const response = await fetch('https://www.puzzmo.com/_api/prod/graphql?CalendarQuery=', { method: 'POST', headers, body: JSON.stringify({ query, operationName: 'CalendarQuery', variables: { month, year, gameFilter: null } }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.error('Failed to fetch calendar data:', error); return null; } } function findIncompleteCrosswords(calendarData) { if (!calendarData?.data?.dailiesForAMonth) return []; const incompleteCrosswords = []; calendarData.data.dailiesForAMonth.forEach(daily => { if (!daily.puzzles) return; daily.puzzles.forEach(puzzleEntry => { const puzzle = puzzleEntry.puzzle; // Check if it's a crossword game if (puzzle?.game?.slug === 'crossword') { const gamePlay = puzzle.currentAccountGamePlayed; // Check if incomplete (null gamePlay or completed: false) if (!gamePlay || !gamePlay.completed) { incompleteCrosswords.push({ day: daily.day, urlPath: puzzleEntry.urlPath, name: puzzle.name || puzzle.gameNameOverride || 'Crossword', emoji: puzzle.emoji || '📝', seriesNumber: puzzle.seriesNumber }); } } }); }); // Sort by seriesNumber (most recent first - higher numbers are newer) return incompleteCrosswords.sort((a, b) => b.seriesNumber - a.seriesNumber); } async function navigateToLatestCrossword() { const button = document.getElementById('crossword-nav-btn') || document.getElementById('crossword-nav-btn-sidebar'); const originalText = button.textContent; button.textContent = 'Searching...'; button.disabled = true; try { const now = new Date(); let currentMonth = now.getMonth(); // 0-indexed let currentYear = now.getFullYear(); // Search back indefinitely (with reasonable safety limit of 5 years) const maxMonthsBack = 60; // 5 years for (let monthsBack = 0; monthsBack < maxMonthsBack; monthsBack++) { console.log(`Searching ${currentYear}-${currentMonth + 1}...`); button.textContent = `Searching ${currentYear}-${String(currentMonth + 1).padStart(2, '0')}...`; const calendarData = await fetchCalendarData(currentMonth, currentYear); if (!calendarData) { console.error('Failed to fetch calendar data'); break; } const incompleteCrosswords = findIncompleteCrosswords(calendarData); if (incompleteCrosswords.length > 0) { const latest = incompleteCrosswords[0]; console.log(`Found incomplete crossword: ${latest.name} on day ${latest.day}`); button.textContent = `Found ${latest.name}! Navigating...`; // Navigate to the puzzle window.location.href = `https://www.puzzmo.com/puzzle/${latest.urlPath}`; return; } // Go back one month currentMonth--; if (currentMonth < 0) { currentMonth = 11; currentYear--; } // Small delay to avoid hammering the API await new Promise(resolve => setTimeout(resolve, 100)); } alert(`No incomplete crosswords found in the last ${maxMonthsBack / 12} years!`); } catch (error) { console.error('Error finding crossword:', error); alert('Error finding incomplete crossword. Check console for details.'); } finally { button.textContent = originalText; button.disabled = false; } } function injectAllButtons() { injectButton(); injectButtonInSidebar(); } // Wait for page to load, then inject function init() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', injectAllButtons); } else { injectAllButtons(); } // Also watch for dynamic content changes const observer = new MutationObserver(injectAllButtons); observer.observe(document.body, { childList: true, subtree: true }); } init(); })();