Greasy Fork

Infornia FR 2.0

A refined skribbl.io helper avec liste de mots fréquents/francisés

// ==UserScript==
// @name         Infornia FR 2.0
// @namespace    https://greasyfork.org/en/users/1084087-fermion
// @version      0.2.0
// @description  A refined skribbl.io helper avec liste de mots fréquents/francisés
// @author       fermion
// @match        https://skribbl.io/*
// @match        https://www.skribbl.io/*
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==
(function(){
  'use strict';

  /*** MODULE 1: WordListManager ***/
  class WordListManager {
    constructor(key){
      this.key = key;
      this.correct = new Set(GM_getValue(key, []));
      this.freq = [];
      this.big = new Set();
    }
    async init(){
      const OS_URL = 'https://static.opensubtitles.org/fr/frequency_top5000.txt';
      const FR_URL = 'https://raw.githubusercontent.com/frodonh/french-words/main/french.txt';
      const [freq, big] = await Promise.all([
        this.fetch(OS_URL),
        this.fetch(FR_URL)
      ]);
      this.freq = freq;
      freq.forEach(w => this.big.add(w));
      big.forEach(w => this.big.add(w));
      this.correct.forEach(w => this.big.add(w));
      this.save();
    }
    async fetch(url){
      try {
        const r = await fetch(url);
        if (!r.ok) return [];
        return r.text()
          .then(t => t.split('\n').map(w=>w.trim().toLowerCase()).filter(Boolean));
      } catch(e) { console.error(e); return []; }
    }
    save(){ GM_setValue(this.key, [...this.correct]); }
    add(w){ if(!this.correct.has(w)){ this.correct.add(w); this.save(); } }
    rank(w){
      const i = this.freq.indexOf(w);
      return i>=0 ? i : this.freq.length;
    }
    filter(cands, pattern, parts, close, guessed, prefix){
      return cands.filter(w=>{
        if(guessed.has(w)) return false;
        if(close && levenshtein(w, close)>1) return false;
        if(w.split(' ').length !== parts.length) return false;
        if(!new RegExp(`^${pattern}$`,'i').test(w)) return false;
        if(prefix && !w.startsWith(prefix)) return false;
        return true;
      }).map(w=>({w,score:this.rank(w)}))
        .sort((a,b)=>a.score-b.score)
        .map(o=>o.w);
    }
  }

  /*** MODULE 2: GuessEngine ***/
  class GuessEngine {
    constructor(wm){
      this.wm = wm; this.guessed = new Set(); this.close = '';
    }
    reset(){ this.guessed.clear(); this.close = ''; }
    noteClose(w){ this.close = w; }
    onReveal(w){ this.wm.add(w); }
    generate(hints, input){
      const patt = hints.map(ch=>ch==='_'?'.':ch).join('');
      const parts = patt.split(' ');
      return this.wm.filter([...this.wm.big], patt, parts, this.close, this.guessed, input);
    }
    remember(w){ this.guessed.add(w); }
  }

  /*** MODULE 3: GuiManager ***/
  class GuiManager {
    constructor(onClick, onExport){
      this.createUI();
      this.exportBtn.onclick = onExport;
      document.addEventListener('keydown', e=>{
        if(e.key==='F2'){
          const vis = this.panel.style.display !== 'none';
          this.panel.style.display = vis ? 'none':'block';
          GM_setValue('visible', !vis);
        }
      });
    }
    createUI(){
      this.panel = document.createElement('div');
      Object.assign(this.panel.style,{position:'fixed',bottom:'0',right:'0',width:'100%',background:'#fff',zIndex:9999});
      document.body.appendChild(this.panel);

      this.exportBtn = document.createElement('button');
      this.exportBtn.textContent = 'Export';
      Object.assign(this.exportBtn.style,{position:'absolute',bottom:'100%',right:'0',margin:'5px'});
      this.panel.appendChild(this.exportBtn);

      this.container = document.createElement('div');
      Object.assign(this.container.style,{display:'flex',flexWrap:'wrap',gap:'6px',maxHeight:'200px',overflowY:'auto',padding:'10px'});
      this.panel.appendChild(this.container);

      const vis = GM_getValue('visible', true);
      this.panel.style.display = vis ? 'block' : 'none';
    }
    render(list, inputEl, engine){
      this.container.innerHTML = '';
      list.slice(0,100).forEach((w,i)=>{
        const div = document.createElement('div');
        div.textContent = w;
        Object.assign(div.style,{fontWeight:'bold',padding:'5px',background:`hsl(${360*i/(list.length||1)},100%,50%)`,color:'#fff',cursor:'pointer'});
        div.onmouseenter = ()=>div.style.background='lightgray';
        div.onmouseleave = ()=>div.style.background='';
        div.onmousedown = ()=>div.style.background='gray';
        div.onclick = ()=>{
          inputEl.value = w;
          engine.remember(w);
          const form = document.querySelector('#game-chat form');
          form.dispatchEvent(new Event('submit',{bubbles:true,cancelable:true}));
        };
        this.container.appendChild(div);
      });
    }
  }

  /*** UTILS ***/
  function levenshtein(a,b){ const m=a.length,n=b.length; const M=Array.from({length:n+1},(_,i)=>[i]); for(let j=0;j<=m;j++)M[0][j]=j;
    for(let i=1;i<=n;i++)for(let j=1;j<=m;j++)
      M[i][j]=(b[i-1]===a[j-1]?M[i-1][j-1]:Math.min(M[i-1][j-1]+1,M[i][j-1]+1,M[i-1][j]+1));
    return M[n][m];
  }

  /*** MAIN ***/
  async function main(){
    const wm = new WordListManager('correctAnswers');
    await wm.init();

    const engine = new GuessEngine(wm);
    const gui = new GuiManager(null, ()=>exportNew());
    gui.onGuess = w=>engine.remember(w);

    const inputEl = document.querySelector('#game-chat input');
    function update() {
      const hints = [...document.querySelectorAll('.hints .hint')].map(e=>e.textContent);
      const list = engine.generate(hints, inputEl.value.trim());
      gui.render(list, inputEl, engine);
    }

    function exportNew(){
      const a = document.createElement('a');
      const blob = new Blob([[...wm.correct].join('\n')], {type:'text/plain;charset=utf-8'});
      a.href = URL.createObjectURL(blob);
      a.download = 'words.txt';
      a.click();
    }

    ['input','keydown'].forEach(ev=>inputEl.addEventListener(ev, update));

    // Observers
    const hintObs = new MutationObserver(m=>update());
    document.querySelectorAll('.hints .container, .words, #game-word')
      .forEach(el=>el && hintObs.observe(el,{childList:true,subtree:true}));

    const chatObs = new MutationObserver(muts=>{
      muts.forEach(mut=>mut.addedNodes.forEach(node=>{
        const msg = node.textContent || '';
        const col = getComputedStyle(node).color;
        if(col==='rgb(226, 203, 0)' && msg.includes('is close!'))
          engine.noteClose(msg.split(' ')[0]);
        if(col==='rgb(57, 117, 206)') engine.reset();

        if([...document.querySelectorAll('.hints .hint')].every(e=>e.classList.contains('uncover'))){
          const correct = [...document.querySelectorAll('.hints .hint')].map(e=>e.textContent).join('').toLowerCase();
          engine.onReveal(correct);
        }
      }));
    });
    const chatC = document.querySelector('.chat-content');
    if(chatC) chatObs.observe(chatC,{childList:true});

    // Initial pump
    update();
  }
  main();

})();