// ==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;
}
})();