Quizlet Spell Auto-Giveup Countdown.

A forced, automatic, and short study session for spelling words you obviously do NOT know. Includes a short configurable break and an alarm to get them to come back when the break ends. Intended for use with kids.

目前為 2018-09-02 提交的版本,檢視 最新版本

// ==UserScript==
// @name     Quizlet Spell Auto-Giveup Countdown.
// @version  0.0.3
// @grant    none
// @match        https://quizlet.com/*/spell
// @description A forced, automatic, and short study session for spelling words you obviously do NOT know. Includes a short configurable break and an alarm to get them to come back when the break ends. Intended for use with kids.
// @namespace https://gf.qytechs.cn/users/82098
// ==/UserScript==
 function getElementByXpath(path,node=document) {
    return document.evaluate(path, node, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  }
function XPath(xpath) {
  let self = this;
  if (!( self instanceof XPath) ) {
	    return new XPath(xpath);
	}
  self.xpath=xpath;
  self.contains = function(attr,text) {
    self.xpath += "contains(concat(' ', normalize-space(" + attr + "), ' '), ' "
      + text
      +  " ')";
    return self;
  };
  self.append = function(text) {
    self.xpath += text;
    return self;
  };
  self.as_text = function() {
    return self.xpath;
  };
  self.fetch = function(node=document) {
    return getElementByXpath(self.xpath,node);
  };
  return self;
}

function CountdownElm(attr={}) {
  let self = this;
  if (!( self instanceof CountdownElm) ) {
	    return new CountdownElm(attr);
	}
  let elm=attr.elm;
  let endtime=attr.endtime;
  let paused_remainingtime=0;
  let is_paused = false;
  let should_reset_time = false;
  self.updateUI=function() {
    let r = Math.floor(self.remainingtime/1000 + 0.5);
    r = Math.max(r,0);
    elm.innerHTML=Math.floor(self.remainingtime/1000 + 0.5);
  };
  self.pause = function() {
    if (!is_paused) {
      paused_remainingtime = self.remainingtime;
      is_paused = true;
      should_reset_time = true;
    }
  };
  self.unpause = function() {
    if (is_paused) {
      if (should_reset_time) {
        self.endtime_from_millisec(paused_remainingtime);
      }
      paused_remainingtime=0;
      is_paused=false;
      should_reset_time = false;
    }
  };
  self.endtime_from_millisec = function(milli) {
    endtime = new Date().getTime() + milli;
    should_reset_time = false;
  };
  Object.defineProperties(self,{
    endtime: {
      get: function() {
        return endtime;
      },
      set: function(val) {
        endtime = val;
        should_reset_time = false;
        self.updateUI();
      },
    },
    remainingtime: {
      get: function() {
        if (!is_paused) {
          return endtime - new Date().getTime();
        }
        else {
          return paused_remainingtime;
        }
      }
    }
  });
  return self;
}


function QuizletSpellTimer(initargs={}) {
  let self = this;
  if (!( self instanceof QuizletSpellTimer) ) {
	    return new QuizletSpellTimer(initArgs);
	}
  let qst = self;
  function Print(msg) {
		console.log(msg);
	}

  function toSeconds(hms) { // HH:MM:SS to seconds
    let a = hms.split(':'); // split it at the colons
    // minutes are worth 60 seconds. Hours are worth 60 minutes.
    return (+a[0]) * 60 * 60 + (+a[1]) * 60 + (+a[2]);
  }
  function htmlToElement(html) {
    let template = document.createElement('template');
    html = html.trim(); // Never return a text node of whitespace as the result
    template.innerHTML = html;
    return template.content.firstChild;
}
  function isHidden(el) {
      return (el.offsetParent === null)
  }
  function CheckLoop(xpath,fn,cnt=50,delay=100) {
    if (getElementByXpath(xpath)) {
      fn();
    }
    else if (cnt != 0) {
      setTimeout(function() { CheckLoop(cnt - 1); },delay);
    }
    else {
      Print("Failed to find xpath '" + xpath + "'");
    }
  }
  const keycodes={
    	    backspace:8,    tab:9,         enter:13,
    	    shift:16,       ctrl:17,       alt:18,
    	    pause_break:19, capslock:20,   escape:27,
    	    space:32,       pageup:33,     pagedown:34,
    	    end:35,         home:36,       leftarrow:37,
    	    uparrow:38,     rightarrow:39, downarrow:40,
    	    insert:45,      delete:46,
    	    0:48,   1:49,   2:50,   3:51,
    	    4:52,   5:53,   6:54,   7:55,
    	    8:56,   9:57,   a:65,   b:66,
    	    c:67,   d:68,   e:69,   f:70,
    	    g:71,   h:72,   i:73,   j:74,
    	    k:75,   l:76,   m:77,   n:78,
    	    o:79,   p:80,   q:81,   r:82,
    	    s:83,   t:84,   u:85,   v:86,
    	    w:87,   x:88,   y:89,   z:90,
    	    multiply: 106, add: 107, subtract: 109,
    	    decimalpoint: 110, divide: 111,
    	    f1: 112, f2: 113, f3: 114,
    	    f4: 115, f5: 116, f6: 117,
    	    f7: 118, f8: 119, f9: 120,
    	    f10: 121, f11: 122, f12: 123,
    	    numlock: 144, scrolllock: 145,
    	    semicolon: 186, equalsign: 187,
    	    comma: 188, dash: 189, period: 190,
    	    forwardslash: 191, graveaccent: 192,
    	    openbracket: 219, backslash: 220,
    	    closebraket: 221, singlequote: 222
    	};
  const enter_keyboard_event = new KeyboardEvent('keydown',{'keyCode':keycodes.enter,'which':keycodes.enter});
  const GameState = {
    Spelling: 0,
    ReviewingMisspelled: 1,
    Break: 2,
    BreakOver: 3,
    Paused: 4,
  };

  let progress_cont_xp = new XPath("//div[").contains("@class","ModeControls-progress").append("]");
  let break_countdown_cont = htmlToElement(`<div class='ModeControls-progressSection'>
                                   <h4 id="break_tite">Next Break</h4>
                                   <br>
                                   <h5 id="break_time">0</h5>
                                   </div>`);
  let spell_countdown_cont = htmlToElement(`<div class='ModeControls-progressSection'>
                                     <h4 id="spell_tite">Give Up In</h4>
                                     <br>
                                     <h5 id="spell_time">0</h5>
                                     </div>`);
  let pause_button_cont = htmlToElement(`<div class='ModeControls-progressSection'>
                                     <button class="h1" id="pause_play">Play</button>
                                     </div>`);
  let paused_overlay = htmlToElement(`<div style="z-index: 100; background-color: #000; height: 100%; width: 100%; position: absolute; top:0; left:0; display:none; ">
                                     </div>`);

  qst.settings = {
    per_letter_time: 5,
    max_spell_time: 45,
    loop_timeout: 300,
    break: {
      length: toSeconds("00:05:00"),
      delay: toSeconds("00:15:00"),
    },
    sounds: {
      break_over: 'https://freesound.org/data/previews/250/250629_4486188-hq.mp3',
    },
  };
  qst.audio = new Audio(qst.settings.sounds.break_over);
  qst.elm = {};
  qst.state = {};
  qst.is_initialized=false;
  qst.ctime=new Date().getTime();
  qst.pause = {
    max_pause_cnt: 2,
    pause_history_durration: toSeconds("00:05:00"),
    pause_hist: [],
    can_pause: function() {
      let pause_cnt = 0;
      for (const pause of qst.pause.pause_hist ) {
        if (pause >= (qst.ctime - (qst.pause.pause_history_durration * 1000)) ) {
          pause_cnt += 1;
        }
      }
      return pause_cnt < qst.pause.max_pause_cnt;
    },
    try_pause: function() {
      if (qst.pause.can_pause()) {
        qst.pause.pause_hist.push(qst.ctime);
        qst.pause.pause_hist.splice(0,qst.pause.pause_hist.length - qst.pause.max_pause_cnt);
        return true;
      }
      return false;
    }
  }

  qst.gamestate= GameState.Spelling;

  function isReviewing() {
    //Print(Game.$diff.textContent);
    if (Game.$diff.textContent) {
      return true;
    }
    return false;
  }

  function initNewWord() {
    qst.state.spell_countdown.unpause();
    qst.state.break_countdown.unpause();
    qst.state.spell_countdown.endtime_from_millisec(qst.settings.max_spell_time * 1000);
    qst.gamestate=GameState.Spelling;
    qst.rev_workaround_cnt = 0;
  }
  function onSpellTimeout() {
    qst.elm.spelling_box.dispatchEvent(enter_keyboard_event);
  }
  function onPause() {
    qst.gamestate = GameState.Paused;
    paused_overlay.style.display="inline";
    qst.state.spell_countdown.pause();
    qst.state.break_countdown.pause();
  }
function onResume() {
    qst.gamestate = GameState.Spelling;
    paused_overlay.style.display="inline";
    qst.state.spell_countdown.unpause();
    qst.state.break_countdown.unpause();
  }
  function onPausePlay() {
    if (qst.gamestate == GameState.Paused || qst.gamestate == GameState.BreakOver ) {
      onResume();
    }
    else if (qst.gamestate == GameState.Spelling || qst.gamestate == GameState.ReviewingMisspelled ) {
      if (qst.pause.try_pause()) {
        onPause();
      }
    }
  }
  function updatePausePlay() {
    if (qst.gamestate == GameState.Paused || qst.gamestate == GameState.BreakOver ) {
      qst.elm.pauseplay_button.textContent="Play";
    }
    else if (qst.gamestate == GameState.Spelling || qst.gamestate == GameState.ReviewingMisspelled ) {
      qst.elm.pauseplay_button.textContent="Pause";
      qst.elm.pauseplay_button.disabled=!qst.pause.can_pause();
    }
  }

  function onBreak() {
    onPause();
    qst.gamestate=GameState.Break;
    qst.state.break_countdown.endtime_from_millisec(qst.settings.break.length * 1000);
  }
  function onCorrect() {
    Print("Correct");
    clearTimeout(qst.mainloop_timer);
    initNewWord();
    qst.rev_workaround_cnt=0;
    qst.mainloop_timer=setTimeout(MainLoop,1000);
  }
  function onMissed() {
    Print("Missed");
    clearTimeout(qst.mainloop_timer);
    qst.gamestate=GameState.ReviewingMisspelled;
    qst.state.spell_countdown.pause();
    qst.state.break_countdown.pause();
    qst.rev_workaround_cnt=0;
    qst.mainloop_timer=setTimeout(MainLoop,1000);
  }
  qst.mainloop_timer;
  qst.rev_workaround_cnt=0;
  function MainLoop() {
    clearTimeout(qst.mainloop_timer);
    qst.ctime=new Date().getTime();
    let ctimeout = qst.settings.loop_timeout;
    if (qst.gamestate == GameState.Spelling) {
      paused_overlay.style.display="none";
      if (qst.state.break_countdown.remainingtime <= 0) {
        onBreak();
      }
      if (qst.state.spell_countdown.remainingtime <= 0) {
        onSpellTimeout();
      }
    }
    else if (qst.gamestate == GameState.ReviewingMisspelled) {
      if (! isReviewing() ) {
        initNewWord();
        qst.gamestate=GameState.Spelling;
      }
      /*if (! isReviewing() && qst.rev_workaround_cnt < 1) {
        qst.rev_workaround_cnt+=1
      }
      else if (! isReviewing() && qst.rev_workaround_cnt >= 1) {
        qst.rev_workaround_cnt=0;
        initNewWord();
        qst.gamestate=GameState.Spelling;
      }*/
    }
    else if (qst.gamestate == GameState.Break) {
      if (qst.state.break_countdown.remainingtime <= 0) {
        qst.gamestate == GameState.BreakOver;
        qst.audio.play();
      }
    }
    else if (qst.gamestate == GameState.BreakOver) {
      qst.gamestate=GameState.Spelling;
      qst.state.break_countdown.endtime_from_millisec(qst.settings.break.delay * 1000);
      initNewWord(); // Lets give them a full word's worth of time
    }
    qst.state.spell_countdown.updateUI();
    qst.state.break_countdown.updateUI();
    updatePausePlay();
    qst.mainloop_timer=setTimeout(MainLoop,ctimeout);
  }

  // NOTE: Game.curTerm.getRawWord() -> Current term string.
  function Init() {
    let spell_cont = XPath("//div[@id='SpellModeTarget']").fetch();
    paused_overlay.style.backgroundColor="#c9ceeb";
    XPath("//div[").contains("@class","ModeLayout-content").append("]").fetch(spell_cont).appendChild(paused_overlay);
    qst.elm.spelling_box= new XPath("//div[").contains("@class","UITextarea-content").append("]//textarea").fetch();
    qst.progress_container = progress_cont_xp.fetch();
    qst.progress_container.appendChild(break_countdown_cont);
    qst.progress_container.appendChild(spell_countdown_cont);
    qst.progress_container.appendChild(pause_button_cont);
    qst.elm.break_countdown = new XPath("//*[@id='break_time']").fetch(break_countdown_cont);
    qst.elm.spell_countdown = new XPath("//*[@id='spell_time']").fetch(spell_countdown_cont);
    qst.elm.pauseplay_button = new XPath("//*[@id='pause_play']").fetch(pause_button_cont);
    qst.state={
      spell_countdown: CountdownElm({
        elm: qst.elm.spell_countdown,
        endtime: 0,
      }),
      break_countdown: CountdownElm({
        elm: qst.elm.break_countdown,
        endtime: 0,
      }),
    };
    qst.elm.pauseplay_button.onclick=onPausePlay;
    qst.state.break_countdown.endtime_from_millisec(qst.settings.break.delay * 1000);
    let oldMissed = Game.missedTerm;
    Game.missedTerm = function () {
      onMissed();
      oldMissed.apply(this);
    };
    let oldCorrect = Game.beatTerm;
    Game.beatTerm = function () {
      onCorrect();
      oldCorrect.apply(this);
    };
    initNewWord();
    MainLoop();
  }


  qst.run=function() {
    CheckLoop(progress_cont_xp.as_text(), Init);
  };
  return qst;
}

let qst = new QuizletSpellTimer();
setTimeout(qst.run, 3000);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址