您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Update your progress in the seasonal reading every day challenge automatically.
// ==UserScript== // @name WK Reading challenge progress updater // @namespace http://tampermonkey.net/ // @version 0.1.1 // @description Update your progress in the seasonal reading every day challenge automatically. // @author Gorbit99 // @match https://community.wanikani.com/t/* // @icon https://www.google.com/s2/favicons?sz=64&domain=wanikani.com // @grant none // @license MIT // ==/UserScript== (function() { "use strict"; let template = window.localStorage.getItem("reading-challenge-updater.template") ?? `Day $$dayNum$$ / [Calendar]($$homePost$$) $$month$$ $$shortMonth$$ $$dayOfMonth$$ `; const knownReadingChallenges = [ "t/read-every-day-challenge-summer-2021/51713", "t/read-every-day-challenge-fallautumn-2021/53285", "t/read-every-day-challenge-winter-2022/54948", "t/read-every-day-challenge-spring-2022/56375", "t/read-every-day-challenge-winter-2024/64212", ]; const observerConfig = { subtree: true, childList: true, }; const observer = new MutationObserver(addButton); function addButton() { if (!knownReadingChallenges.some((challenge) => location.href.includes(challenge) )) { return; } const footerButtons = document.querySelector(".topic-footer-main-buttons"); if (footerButtons !== null && footerButtons.querySelector(".reading-challenge-btn") === null) { const addDayButton = document.createElement("button"); addDayButton.title = "complete the current day"; addDayButton.classList.add("btn-default"); addDayButton.classList.add("create"); addDayButton.classList.add("btn"); addDayButton.classList.add("btn-icon-text"); addDayButton.classList.add("ember-view"); addDayButton.classList.add("reading-challenge-btn"); addDayButton.innerHTML = ` <svg class="fa d-icon d-icon-reply svg-icon svg-string" xmlns="http://www.w3.org/2000/svg" > <use href="#calendar-day"></use> </svg> <span class="d-button-label">Add Day</span> `; addDayButton.addEventListener("click", addDay); footerButtons.append(addDayButton); observer.takeRecords(); } const editBoxButtons = document.querySelector(".d-editor-button-bar"); if (editBoxButtons && editBoxButtons.querySelector(".reading-challenge-template") === null) { const saveTemplateButton = document.createElement("a"); saveTemplateButton.classList.add("cancel"); saveTemplateButton.classList.add("reading-challenge-template"); saveTemplateButton.innerHTML = "save template"; saveTemplateButton.style.marginLeft = "auto"; saveTemplateButton.style.marginRight = "15px"; editBoxButtons.append(saveTemplateButton); saveTemplateButton.addEventListener("click", saveTemplate); observer.takeRecords(); } } observer.observe(document.querySelector("#main"), observerConfig); async function addDay() { const csrfToken = document.querySelector("meta[name=csrf-token]").content; function updatePost(postId, postContent) { const url = "https://community.wanikani.com/posts/" + postId; const encodedPost = encodeURI(("post[raw]=" + postContent) .replace(/ /g, "+")); const config = { headers: { "content-type": "application/x-www-form-urlencoded; charset=UTF-8", "x-csrf-token": csrfToken, "x-requested-with": "XMLHttpRequest", }, "method": "PUT", "body": encodedPost, }; fetch(url, config).then(); } const threadURL = `${window.location.href.match(/https:\/\/[^/]+\/t\/[^/]+\/\d+/)[0]}.json`; const threadData = await (await fetch(threadURL)).json(); const username = document.querySelector("#current-user .icon").title; const progressPostID = threadData.post_stream.stream[1]; const progressPostURL = `https://community.wanikani.com/posts/${progressPostID}.json`; const progressPost = await (await fetch(progressPostURL)).json(); const userProgressRegex = new RegExp(`(\\|\\[?${username}[^|]*\\|[^|]+\\|)(\\d+)\\|?`, "i"); const updatedProgressPost = progressPost.raw.replace(userProgressRegex, (_, head, tail) => { return `${head}${parseInt(tail) + 1}|`; }); updatePost(progressPostID, updatedProgressPost); const userHomePostRegex = new RegExp(`${username}.*\\(([^)]+)\\)`, "i"); const userHomePostURL = progressPost.raw.match(userHomePostRegex)?.[1]; let curDayNum = 0; if (userHomePostURL !== undefined) { const postIndex = userHomePostURL .match(/https:\/\/[^/]+\/[^/]+\/[^/]+\/[^/]+\/(\d+)/)[1]; const postId = threadData.post_stream.stream[postIndex - 1]; const homePostURL = `https://community.wanikani.com/posts/${parseInt(postId)}.json`; const homePost = await (await fetch(homePostURL)).json(); const monthNames = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; const monthName = monthNames[new Date().getMonth()]; const dayNum = new Date().getDate(); const paddedDayNum = dayNum.toString().padStart(2, "0"); const xPlacerRegex = new RegExp(`(${monthName}.+?${paddedDayNum})\\[\\]`, "s"); const updatedHomePost = homePost.raw.replace(xPlacerRegex, "$1[X]"); const dayCounterRegex = new RegExp(`^.*${monthName}.*?${paddedDayNum}\\[X?\\]`, "gs"); updatePost(postId, updatedHomePost); curDayNum = (updatedHomePost.match(dayCounterRegex)[0].match(/\[X?\]/g) || []) .length; } document.querySelector("button.create:not(.reply)").click(); const interval = setInterval(async function() { const editor = document.querySelector("textarea.d-editor-input"); if (editor) { editor.value = handleTemplate(template, curDayNum, userHomePostURL); clearInterval(interval); } }, 100); } function saveTemplate() { const textbox = document.querySelector(".d-editor-input"); template = textbox.value; window.localStorage.setItem("reading-challenge-updater.template", template); } function handleTemplate(template, curDay, homePost) { const monthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ]; const shortMonthNames = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; let interpolated = template; interpolated = interpolated.replaceAll(/\$\$([^$]+)\$\$/g, (_, name) => { switch (name) { case "dayNum": return curDay; case "homePost": return homePost; case "month": return monthNames[new Date().getMonth()]; case "shortMonth": return shortMonthNames[new Date().getMonth()]; case "dayOfMonth": return new Date().getDate(); default: return `$$${name}$$`; } }); return interpolated; } })();