Greasy Fork is available in English.

WikiRace Smart Solver Pro

Advanced Beam-Search solver with Hide UI toggle and MIT license.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         WikiRace Smart Solver Pro
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Advanced Beam-Search solver with Hide UI toggle and MIT license.
// @author       Gemini
// @match        *://*.wiki-race.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const API_URL = "https://en.wikipedia.org/w/api.php";
    let currentMaxDepth = 6;
    let currentBeamWidth = 45; 

    const STOP_WORDS = new Set(["the", "a", "an", "of", "in", "to", "and", "is", "was", "for", "on", "with", "at", "by", "from", "as", "it", "that", "this", "are", "be", "or", "its", "has", "had", "have", "were", "been", "not", "but", "which", "their", "also", "may", "can", "all", "into", "than", "other", "some", "such", "no", "if"]);
    const SKIP_PREFIXES = ["Wikipedia:", "WP:", "Help:", "Template:", "Talk:", "User:", "Category:", "File:", "Portal:", "Draft:", "Module:", "MediaWiki:", "Special:", "Template talk:"];

    function tokenize(text) {
        if (!text) return new Set();
        const words = text.toLowerCase().match(/[a-z]{2,}/g) || [];
        return new Set(words.filter(w => !STOP_WORDS.has(w)));
    }

    async function fetchAPI(params) {
        const url = new URL(API_URL);
        url.search = new URLSearchParams({ ...params, origin: '*', format: 'json', formatversion: 2 });
        try {
            const res = await fetch(url);
            return res.ok ? await res.json() : null;
        } catch (e) { return null; }
    }

    async function getLinks(title) {
        let links = [];
        let plcontinue = null;
        do {
            const params = { action: 'query', titles: title, prop: 'links', pllimit: 'max', plnamespace: 0 };
            if (plcontinue) params.plcontinue = plcontinue;
            const data = await fetchAPI(params);
            if (!data) break;
            const page = data.query?.pages?.[0];
            if (page?.links) {
                page.links.forEach(l => { 
                    if (!l.title.endsWith(" (disambiguation)") && !SKIP_PREFIXES.some(p => l.title.startsWith(p))) links.push(l.title); 
                });
            }
            plcontinue = data.continue?.plcontinue;
        } while (plcontinue);
        return links;
    }

    async function findLinkSection(article, targetLink) {
        const data = await fetchAPI({ action: 'parse', page: article, prop: 'text', redirects: 1 });
        if (!data?.parse?.text) return "Introduction";
        const doc = new DOMParser().parseFromString(data.parse.text, 'text/html');
        const targetHref = `/wiki/${targetLink.replace(/ /g, "_")}`;
        const linkElem = Array.from(doc.querySelectorAll('a')).find(a => a.getAttribute('href') === targetHref || a.title === targetLink);
        if (linkElem) {
            let curr = linkElem;
            while (curr && curr !== doc.body) {
                if (curr.previousElementSibling?.tagName.match(/^H[2-6]$/)) return curr.previousElementSibling.textContent.replace(/\[edit\]/g, '').trim();
                curr = curr.parentElement || curr.previousElementSibling;
            }
        }
        return "Lead / Introduction";
    }

    class RelevanceScorer {
        constructor(targetCtx) {
            this.targetTitle = targetCtx.raw_title.toLowerCase();
            this.targetWords = targetCtx.title_tokens;
            this.keywords = new Set([...this.targetWords, ...targetCtx.category_tokens, ...targetCtx.extract_tokens]);
        }
        score(linkTitle) {
            const linkLower = linkTitle.toLowerCase();
            const linkTokens = tokenize(linkTitle);
            let score = 0.0;
            if (linkLower === this.targetTitle) return 1000.0;
            const titleOverlap = [...linkTokens].filter(x => this.targetWords.has(x)).length;
            if (titleOverlap > 0) score += 30.0 * (titleOverlap / Math.max(this.targetWords.size, 1));
            const kwOverlap = [...linkTokens].filter(x => this.keywords.has(x)).length;
            score += 5.0 * kwOverlap;
            const hubs = ["united states", "europe", "history", "science", "geography"];
            if (hubs.some(h => linkLower.includes(h))) score += 2.0;
            return score;
        }
    }

    async function solveRace(start, end, statusCallback) {
        const startRes = await fetchAPI({ action: 'query', titles: start, redirects: 1 });
        const endRes = await fetchAPI({ action: 'query', titles: end, redirects: 1 });
        start = startRes?.query?.pages?.[0]?.title || start;
        end = endRes?.query?.pages?.[0]?.title || end;

        statusCallback("Analyzing target...");
        const [catData, extData] = await Promise.all([
            fetchAPI({ action: 'query', titles: end, prop: 'categories', cllimit: 'max', clshow: '!hidden' }),
            fetchAPI({ action: 'query', titles: end, prop: 'extracts', exintro: 1, explaintext: 1, exchars: 1500 })
        ]);
        
        let categories = new Set();
        catData?.query?.pages?.[0]?.categories?.forEach(c => tokenize(c.title.replace("Category:", "")).forEach(w => categories.add(w)));
        const scorer = new RelevanceScorer({ raw_title: end, title_tokens: tokenize(end), category_tokens: categories, extract_tokens: tokenize(extData?.query?.pages?.[0]?.extract || "") });

        let parents = { [start]: null };
        let frontier = [start];

        for (let depth = 1; depth <= currentMaxDepth + 1; depth++) {
            let candidates = [];
            statusCallback(`Searching Depth ${depth}...`);
            const currentBeam = (depth > currentMaxDepth) ? currentBeamWidth + 50 : currentBeamWidth;

            for (let title of frontier) {
                const links = await getLinks(title);
                for (let link of links) {
                    if (link === end) {
                        parents[link] = title;
                        let path = [], curr = end;
                        while (curr) { path.push(curr); curr = parents[curr]; }
                        path.reverse();
                        const sections = [];
                        for (let j = 0; j < path.length - 1; j++) sections.push(await findLinkSection(path[j], path[j+1]));
                        return { path, sections };
                    }
                    if (!(link in parents)) candidates.push({ score: scorer.score(link), link, parent: title });
                }
            }
            if (candidates.length === 0) break;
            candidates.sort((a, b) => b.score - a.score);
            frontier = candidates.slice(0, currentBeam).map(c => { parents[c.link] = c.parent; return c.link; });
        }
        return null;
    }

    function createUI() {
        const container = document.createElement('div');
        container.id = 'wr-container';
        container.style.cssText = `position: fixed; bottom: 20px; right: 20px; width: 320px; background: #1e1e2e; color: #cdd6f4; border-radius: 10px; padding: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); z-index: 999999; font-family: sans-serif; border: 1px solid #45475a; transition: all 0.3s ease;`;

        const minimized = document.createElement('div');
        minimized.id = 'wr-minimized';
        minimized.style.cssText = `position: fixed; bottom: 20px; right: 20px; width: 50px; height: 50px; background: #a6e3a1; border-radius: 50%; display: none; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 10px rgba(0,0,0,0.3); z-index: 999999; font-size: 20px;`;
        minimized.innerText = '🏁';

        container.innerHTML = `
            <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;">
                <h3 style="margin: 0; color: #a6e3a1; font-size: 16px;">🏁 WikiRace Bot</h3>
                <button id="wr-hide" style="background:none; border:none; color:#f38ba8; cursor:pointer; font-size:18px; font-weight:bold;">—</button>
            </div>
            <input type="text" id="wr-start" placeholder="Start Article" style="width: 100%; padding: 6px; margin-bottom: 8px; background: #313244; color: white; border: 1px solid #585b70; border-radius: 4px;">
            <input type="text" id="wr-end" placeholder="Target Article" style="width: 100%; padding: 6px; margin-bottom: 8px; background: #313244; color: white; border: 1px solid #585b70; border-radius: 4px;">
            <button id="wr-solve" style="width: 100%; padding: 10px; background: #89b4fa; color: #11111b; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">Find Path</button>
            <button id="wr-auto" style="width: 100%; padding: 6px; margin-top: 5px; background: #fab387; color: #11111b; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">Auto-Detect</button>
            <div id="wr-status" style="margin-top: 10px; font-size: 12px; color: #f9e2af; max-height: 150px; overflow-y: auto;">Ready.</div>
        `;

        document.body.appendChild(container);
        document.body.appendChild(minimized);

        document.getElementById('wr-hide').onclick = () => { container.style.display = 'none'; minimized.style.display = 'flex'; };
        minimized.onclick = () => { container.style.display = 'block'; minimized.style.display = 'none'; };

        document.getElementById('wr-auto').onclick = () => {
            const lS = document.querySelector('input[placeholder="From here..."]'), lE = document.querySelector('input[placeholder="To there..."]');
            if (lS?.value) document.getElementById('wr-start').value = lS.value;
            if (lE?.value) document.getElementById('wr-end').value = lE.value;
            const s = Array.from(document.querySelectorAll('*')).find(e => e.textContent.includes('Start:')), e = Array.from(document.querySelectorAll('*')).find(e => e.textContent.includes('Target:'));
            if (s) document.getElementById('wr-start').value = s.textContent.replace('Start:', '').trim();
            if (e) document.getElementById('wr-end').value = e.textContent.replace('Target:', '').trim();
        };

        document.getElementById('wr-solve').onclick = async () => {
            const s = document.getElementById('wr-start').value, e = document.getElementById('wr-end').value, btn = document.getElementById('wr-solve'), st = document.getElementById('wr-status');
            if (!s || !e) return;
            btn.disabled = true; btn.innerText = "SOLVING...";
            const res = await solveRace(s, e, m => st.innerText = m);
            if (res) {
                let out = `🏆 FOUND (${res.path.length - 1} clicks): \n` + res.path.map((p, i) => `${i+1}. ${p}${res.sections[i] ? ` (Section: ${res.sections[i]})` : ''}`).join('\n');
                st.innerText = out; alert(out);
            } else st.innerText = "❌ No path found.";
            btn.disabled = false; btn.innerText = "Find Path";
        };
    }
    createUI();
})();