[e-typing]累計コンボ数表示

e-typingで継続ノーミス記録を表示する

  1. // ==UserScript==
  2. // @name [e-typing]累計コンボ数表示
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @description e-typingで継続ノーミス記録を表示する
  6. // @author xyu
  7. // @match https://www.e-typing.ne.jp/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=e-typing.ne.jp
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12. if(localStorage.getItem('totalCombo') == undefined){
  13. localStorage.setItem('totalCombo',0)
  14. localStorage.setItem('round',0)
  15. localStorage.setItem('averageWPM',0)
  16. localStorage.setItem('bestCombo',0)
  17. }
  18.  
  19. let clearLineCount = 0
  20. let typingAppModInterval
  21. let createOptionInterval
  22. let displayComboSwitch
  23. let bestCombo = +localStorage.getItem('bestCombo')
  24. let totalCombo = +localStorage.getItem("totalCombo")
  25. let combo = 0
  26. let wpmCountFlag = true
  27. let scoreCheck
  28. let typingMode = "roma"
  29. let enteredClass = 2
  30. if(location.href.match(/kana\.1/)){
  31. typingMode = "kana"
  32. enteredClass = 1
  33. }else if(location.href.match(/std\.2/) || location.href.match(/lstn\.4/)){
  34. typingMode = "eng"
  35. enteredClass = 1
  36. }else{
  37. typingMode = "roma"
  38. enteredClass = 2
  39. }
  40.  
  41. const createOption = () => {
  42. console.log("createOption")
  43. const FUNC_VIEW = document.getElementById("func_view")
  44. const DISABLE_OPTION = document.getElementById("disable-option")
  45. if(FUNC_VIEW){
  46. displayComboSwitch = localStorage.getItem("combo-option") == "false" ? false : true;
  47. FUNC_VIEW.style.height = document.getElementById("func_view").clientHeight + 30 + "px"
  48. FUNC_VIEW.insertAdjacentHTML('beforeend' , `<div>
  49. <div><label><small>累計コンボ数表示</small><input id="combo-option" type="checkbox" style="display:none;" ${displayComboSwitch == false ? "" : "checked"}><div id="combo-btn" style="margin-left:4px;" class="switch_btn"><a class="on_btn btn show">ON</a><a class="off_btn btn" style="display:${displayComboSwitch == false ? "block" : ""};">OFF</a></div></label></div>
  50. </div>`)
  51.  
  52. document.getElementById("combo-option").addEventListener("change" , event => {
  53. localStorage.setItem("combo-option" , event.target.checked);
  54.  
  55. if(event.target.checked){
  56. document.querySelector("#combo-btn .off_btn").style.display = ""
  57. displayComboSwitch = true;
  58. }else{
  59. document.querySelector("#combo-btn .off_btn").style.display = "block"
  60. displayComboSwitch = false;
  61. }
  62. })
  63. clearInterval(createOptionInterval)
  64. }
  65. }
  66.  
  67.  
  68. const typingAppMod = () => {
  69.  
  70. const example_container = document.getElementById("example_container") //iframe内の要素を取得
  71.  
  72.  
  73. if(example_container){
  74.  
  75. let keyJudge = event => {
  76.  
  77. let sentenceText = document.getElementsByClassName("entered")[enteredClass]
  78.  
  79. if(sentenceText){
  80.  
  81. sentenceText = document.getElementsByClassName("entered")[enteredClass].nextSibling.textContent
  82.  
  83. let char = windows_keymap[event.code] ? windows_keymap[event.code] : kana_keymap[event.key];
  84. if(!char || (event.key == ' ' && typingMode != "eng")){return;}
  85.  
  86. //正タイプ
  87. combo++
  88. updateCombo()
  89. if(scoreCheck){
  90. scoreCheck.bestScorePass()
  91. }
  92. if(sentenceText.length == 1){
  93. //ラインクリア
  94. clearLineCount++
  95. }
  96. console.log(combo)
  97. }else{
  98.  
  99. const startMsg = document.getElementById("start_msg")
  100. const countdown = document.getElementById('countdown')
  101. if((startMsg || countdown) && event.key == ' '){
  102. totalCombo = +localStorage.getItem("totalCombo")
  103. updateCombo()
  104. document.getElementById('total-combo').textContent = totalCombo
  105. document.getElementById('round').textContent = localStorage.getItem("round")
  106. missScreenObserver()
  107. appObserver()
  108. wpmCountFlag = true
  109. scoreCheck = new ScoreCheck()
  110. document.getElementById("wpm-area").classList.add('hide')
  111. }
  112. }
  113. }
  114.  
  115. if(displayComboSwitch){
  116. const width = document.getElementById("example_container").clientWidth
  117. const scale = document.getElementById("app").style.webkitTransform.replace(/[^\d^\.]/g,'')
  118. document.addEventListener("keydown",keyJudge,false)
  119. document.getElementById("ad_frame").style.display = 'none';
  120. document.getElementById("ad_frame").insertAdjacentHTML('beforebegin',
  121. `<div id="combo-container" style="width:${width * (scale ? scale:1)}px;">
  122. <span id="combo-area" class="area" title="最高記録:${bestCombo}コンボ"><ruby><span id="total-combo">${totalCombo}</span><rt>累計</rt></ruby>
  123. + <ruby><span id="combo">0</span><rt>コンボ</rt></ruby>
  124. = <ruby><span id="all-combo" class="${bestCombo < totalCombo ? 'gold' : ''}">${totalCombo}</span><rt>継続</rt></ruby></span>
  125. <span id="round-area" class="area"><span id="round">${+localStorage.getItem('round')}</span>周</span>
  126. <span id="wpm-area" class="hide area"><ruby><span id="average-wpm_">---</span><rt>平均WPM</rt></ruby></span></div>
  127. <style>
  128. #combo-container{
  129. margin-left: auto;
  130. margin-right: auto;
  131. font-weight: bold;
  132. font-size: 2rem;
  133. margin-top: 2rem;
  134. white-space: nowrap;
  135. margin-bottom: 2rem;
  136. }
  137. .area{
  138. padding: 0.2rem 1rem;
  139. background: #ff9c0091;
  140. border-radius: 23px;
  141. }
  142. #combo-container > span:not(:first-child){
  143. margin-left:1rem;
  144. }
  145. #wpm-area{
  146. padding-top:1rem;
  147. }
  148. #combo-area{
  149. padding-top:1.5rem;
  150. }
  151. #all-combo{
  152. font-size:2.5rem;
  153. }
  154. .hide{
  155. visibility: hidden;
  156. }
  157. .gold{
  158. color: gold;
  159. text-shadow:
  160. 1px 1px 0px #000, -1px -1px 0px #000,
  161. -1px 1px 0px #000, 1px -1px 0px #000,
  162. 1px 0px 0px #000, -1px 0px 0px #000,
  163. 0px 1px 0px #000, 0px -1px 0px #000;
  164. }
  165. </style>
  166. `)
  167. window.addEventListener('beforeunload', () => {
  168. if(combo){
  169. updateTotalCombo()
  170. updateRoundCount()
  171. }
  172. })
  173. if(window.parent.document.getElementsByClassName('pp_close').length){
  174. window.parent.document.getElementsByClassName('pp_close')[0].addEventListener('click', () => {
  175. updateTotalCombo()
  176. updateRoundCount()
  177. })
  178. }
  179.  
  180. window.addEventListener('resize',resize)
  181.  
  182. }
  183.  
  184. clearInterval(typingAppModInterval)
  185. typingAppModInterval = null
  186. }
  187.  
  188. console.log("searching for example_container")
  189. }
  190.  
  191.  
  192. function updateCombo(){
  193. document.getElementById('combo').textContent = combo
  194. document.getElementById('all-combo').textContent = totalCombo + combo
  195. }
  196.  
  197. function updateTotalCombo(){
  198. localStorage.setItem("totalCombo",+localStorage.getItem("totalCombo") + combo)
  199. combo = 0
  200. }
  201.  
  202. class ScoreCheck{
  203.  
  204. bestScorePass(){
  205. if(bestCombo < (totalCombo + combo)){
  206. document.getElementById('all-combo').classList.add('gold')
  207. scoreCheck = null
  208. }
  209. }
  210.  
  211. }
  212.  
  213. function resetCombo(){
  214. combo = 0
  215. totalCombo = 0
  216. localStorage.setItem("totalCombo",0)
  217. }
  218.  
  219. function updateRoundCount(){
  220. let round = +localStorage.getItem('round')
  221. round += Math.round((clearLineCount/15) * 10) / 10;
  222. localStorage.setItem("round",round)
  223. clearLineCount = 0
  224. }
  225.  
  226. function updateAverageWpm(){
  227. setTimeout( () => {
  228. if(document.getElementById('result') != null){
  229. const results = document.getElementsByClassName("result_data")[0].firstElementChild.children
  230. const aveWpm = +localStorage.getItem('averageWPM')
  231. for(let i=0;i<results.length;i++){
  232. if(results[i].firstElementChild.textContent == 'WPM'){
  233. const wpm = +results[i].lastElementChild.textContent
  234. putOptionSaveData(wpm)
  235. getAllIndexeddbData(wpm)
  236. }
  237.  
  238. }
  239. }else{
  240. updateAverageWpm()
  241. }
  242. },100)
  243.  
  244. }
  245.  
  246. function reset(){
  247.  
  248. if(bestCombo < (totalCombo + combo)){
  249. bestCombo = (totalCombo + combo)
  250. localStorage.setItem('bestCombo',totalCombo + combo)
  251. document.getElementById('combo-area').title = `最高記録:${bestCombo}コンボ`
  252. }
  253. document.getElementById('all-combo').classList.remove('gold')
  254. resetCombo()
  255. updateCombo()
  256. clearData()
  257. document.getElementById('total-combo').textContent = totalCombo
  258. localStorage.setItem("round",0)
  259. clearLineCount = 0
  260. document.getElementById('round').textContent = 0
  261. wpmCountFlag = false
  262. scoreCheck = new ScoreCheck()
  263. }
  264.  
  265.  
  266. const createEventInTypingApp = (() => {
  267.  
  268. // https://www.e-typing.ne.jp/app/jsa_std/typing.asp タイピング画面のiframe URL
  269. if( location.href.match(/app\/jsa_/)){
  270.  
  271. typingAppModInterval = setInterval(typingAppMod , 50)
  272. createOptionInterval = setInterval(createOption , 100)
  273. }
  274.  
  275. })()
  276.  
  277. function resize(){
  278. if(document.getElementById("example_container") == null){return;}
  279. const width = document.getElementById("example_container").clientWidth
  280. const scale = document.getElementById("app").style.webkitTransform.replace(/[^\d^\.]/g,'')
  281. document.getElementById('combo-container').style.width = (width * (scale ? scale:1)) + "px"
  282. }
  283.  
  284. function missScreenObserver(){
  285.  
  286. const target = document.getElementById('miss_type_screen'); // body要素を監視
  287. const observer_a = new MutationObserver(function (mutations) {
  288. // observer.disconnect(); // 監視を終了
  289. if(combo){
  290. reset()
  291. }
  292. });
  293.  
  294. // 監視を開始
  295. observer_a.observe(target, {
  296. attributes: true // 属性変化の監視
  297. });
  298.  
  299. }
  300.  
  301.  
  302. function appObserver(){
  303.  
  304. const target = document.getElementById('app'); // body要素を監視
  305. const observer_b = new MutationObserver(function (mutations) {
  306. observer_b.disconnect(); // 監視を終了
  307. updateTotalCombo()
  308. updateRoundCount()
  309. if(wpmCountFlag){
  310. updateAverageWpm()
  311. }
  312. });
  313.  
  314. // 監視を開始
  315. observer_b.observe(target, {
  316. childList: true
  317. });
  318.  
  319. }
  320.  
  321.  
  322.  
  323. let kana_keymap = {
  324. 0: ["わ"],
  325. 1: ["ぬ"],
  326. "!": ["ぬ"],
  327. 2: ["ふ"],
  328. 3: ["あ"],
  329. 4: ["う"],
  330. 5: ["え"],
  331. 6: ["お"],
  332. 7: ["や"],
  333. 8: ["ゆ"],
  334. 9: ["よ"],
  335. "-": ["ほ","-"],
  336. "q": ["た"],
  337. "Q": ["た"],
  338. "w": ["て"],
  339. "W": ["て"],
  340. "e": ["い"],
  341. "E": ["い"],
  342. "r": ["す"],
  343. "R": ["す"],
  344. "t": ["か"],
  345. "T": ["か"],
  346. "y": ["ん"],
  347. "Y": ["ん"],
  348. "u": ["な"],
  349. "U": ["な"],
  350. "i": ["に"],
  351. "I": ["に"],
  352. "o": ["ら"],
  353. "O": ["ら"],
  354. "p": ["せ"],
  355. "P": ["せ"],
  356. "a": ["ち"],
  357. "A": ["ち"],
  358. "s": ["と"],
  359. "S": ["と"],
  360. "d": ["し"],
  361. "D": ["し"],
  362. "f": ["は"],
  363. "F": ["は"],
  364. "g": ["き"],
  365. "G": ["き"],
  366. "h": ["く"],
  367. "H": ["く"],
  368. "j": ["ま"],
  369. "J": ["ま"],
  370. "k": ["の"],
  371. "K": ["の"],
  372. "l": ["り"],
  373. "L": ["り"],
  374. "z": ["つ"],
  375. "Z": ["つ"],
  376. "x": ["さ"],
  377. "X": ["さ"],
  378. "c": ["そ"],
  379. "C": ["そ"],
  380. "v": ["ひ"],
  381. "V": ["ひ"],
  382. "b": ["こ"],
  383. "B": ["こ"],
  384. "n": ["み"],
  385. "N": ["み"],
  386. "m": ["も"],
  387. "M": ["も"],
  388. ",": ["ね",","],
  389. "<": ["、"],
  390. ".": ["る","."],
  391. ">": ["。"],
  392. "/": ["め","/"],
  393. "?": ["・"],
  394. "#": ["ぁ"],
  395. "$": ["ぅ"],
  396. "%": ["ぇ"],
  397. "'": ["ゃ","’","'"],
  398. "^": ["へ"],
  399. "~": ["へ"],
  400. "&": ["ぉ"],
  401. "(": ["ゅ"],
  402. ")": ["ょ"],
  403. '|': ["ー"],
  404. "_": ["ろ"],
  405. "=": ["ほ"],
  406. "+": ["れ"],
  407. ";": ["れ"],
  408. '"': ["ふ","”","“","\""],
  409. "@": ["゛"],
  410. '`': ["゛"],
  411. "[": ["゜"],
  412. ']': ["む"],
  413. "{": ["「"],
  414. '}': ["」"],
  415. ":": ["け"],
  416. "*": ["け"]
  417. }
  418. let windows_keymap = {
  419. 'IntlYen': ["ー","¥","\\"],
  420. "IntlRo": ["ろ","¥","\\"],
  421. "Space": [" "],
  422. "Numpad1": [],
  423. "Numpad2": [],
  424. "Numpad3": [],
  425. "Numpad4": [],
  426. "Numpad5": [],
  427. "Numpad6": [],
  428. "Numpad7": [],
  429. "Numpad8": [],
  430. "Numpad9": [],
  431. "Numpad0": [],
  432. "NumpadDivide": [],
  433. "NumpadMultiply": [],
  434. "NumpadSubtract": [],
  435. "NumpadAdd": [],
  436. "NumpadDecimal": []
  437. }
  438.  
  439.  
  440.  
  441. let db;
  442. let indexedDB = window.indexedDB || window.mozIndexedDB || window.msIndexedDB;
  443. let OptionDatabaseObject = {}
  444. const STORE_NAME = "wpmDB";
  445. const STORE_KEYPATH = ["wpm"];
  446.  
  447. //filterDBにアクセス
  448. (function accessIndexedDB(){
  449. const OPEN_REQUEST = indexedDB.open(STORE_NAME, 1.0);
  450.  
  451. //データベースストア新規作成。(初回アクセス時)
  452. OPEN_REQUEST.onupgradeneeded = function(event) {
  453. // データベースのバージョンに変更があった場合(初めての場合もここを通ります。)
  454. db = event.target.result;
  455. const CREATE_filterList = db.createObjectStore("wpm", { keyPath:STORE_KEYPATH[0]});
  456. }
  457. //データベースストアアクセス成功時。
  458. OPEN_REQUEST.onsuccess = function(event) {
  459. db = event.target.result;
  460. }
  461. })();
  462.  
  463.  
  464. function getAllIndexeddbData(wpm) {
  465. //トランザクション
  466. var transaction = db.transaction(STORE_KEYPATH, 'readonly');
  467. //オブジェクトストアにアクセスします。
  468. var listObjectStore = transaction.objectStore(STORE_KEYPATH[0]);
  469. //全件取得
  470. var listRequest = listObjectStore.getAllKeys()
  471. //取得が成功した場合の関数宣言
  472. listRequest.onsuccess = function (event) {
  473. const result = event.currentTarget.result
  474. let sum = result.reduce(function (acc, cur) {
  475. return acc + cur;
  476. });
  477. document.getElementById("average-wpm_").textContent = ((sum+wpm) / (result.length+1)).toFixed(2)
  478. document.getElementById("wpm-area").classList.remove('hide')
  479. };
  480. }
  481.  
  482. //データを保存
  483. function putOptionSaveData(Data){
  484. const SEND_DATA = {[STORE_KEYPATH[0]] : Data};
  485. const OPEN_REQ = window.indexedDB.open(STORE_NAME);
  486.  
  487. OPEN_REQ.onsuccess = function(event){
  488. var db = event.target.result;
  489. var trans = db.transaction(STORE_KEYPATH[0], 'readwrite');
  490. var store = trans.objectStore(STORE_KEYPATH[0]);
  491. var putReq = store.put(SEND_DATA);
  492. }
  493. }
  494.  
  495. //削除
  496. function clearData() {
  497. // open a read/write db transaction, ready for clearing the data
  498. const transaction = db.transaction(STORE_KEYPATH, "readwrite");
  499.  
  500. // create an object store on the transaction
  501. const objectStore = transaction.objectStore(STORE_KEYPATH[0]);
  502.  
  503. // Make a request to clear all the data out of the object store
  504. const objectStoreRequest = objectStore.clear();
  505.  
  506. objectStoreRequest.onsuccess = (event) => {
  507. // report the success of our request
  508. };
  509. };

QingJ © 2025

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