Something kind of working for this WaniKani cli app
commit
1f8cb044ae
@ -0,0 +1,194 @@
|
|||||||
|
import { LocalStorage } from "https://deno.land/x/storage@0.0.5/mod.ts";
|
||||||
|
import * as Colors from "https://deno.land/std/fmt/colors.ts";
|
||||||
|
import wanakana from 'https://cdn.esm.sh/v27/wanakana@4.0.2/esnext/wanakana.js';
|
||||||
|
import { readKeypress } from "https://deno.land/x/keypress@0.0.7/mod.ts";
|
||||||
|
import { Select, Input, Confirm } from "https://deno.land/x/cliffy/prompt/mod.ts";
|
||||||
|
|
||||||
|
const API_BASE = "https://api.wanikani.com/v2";
|
||||||
|
const store = new LocalStorage<string>();
|
||||||
|
|
||||||
|
async function getToken() {
|
||||||
|
if (!store.get('token')) {
|
||||||
|
store.set({ token: await Input.prompt('Enter your API Token') });
|
||||||
|
store.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const wkFetch = (url: string, options = {}) =>
|
||||||
|
fetch(url, {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
"Wanikani-Revision": "20170710",
|
||||||
|
Authorization: `Bearer ${store.get("token")}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const subjects = JSON.parse(store.get('subjects') || '{}');
|
||||||
|
|
||||||
|
const getSubject = async (id: number) => {
|
||||||
|
if (!subjects[id]) {
|
||||||
|
subjects[id] = await wkFetch(`${API_BASE}/subjects/${id}`).then((res) => res.json());
|
||||||
|
store.set('subjects', JSON.stringify(subjects));
|
||||||
|
store.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
return subjects[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSummary = async () =>
|
||||||
|
await wkFetch(`${API_BASE}/summary`)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
return res.json();
|
||||||
|
} else {
|
||||||
|
throw "Error";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function authError() {
|
||||||
|
console.error('Error authenticating.');
|
||||||
|
store.delete('token');
|
||||||
|
}
|
||||||
|
|
||||||
|
// const availableReviews = (summary: any) =>
|
||||||
|
const availableNow = (type: string, summary: { data: any }) =>
|
||||||
|
summary.data[type].filter((rev: any) => {
|
||||||
|
const date = new Date(rev.available_at);
|
||||||
|
return date < new Date();
|
||||||
|
}).flatMap((e: { subject_ids: any }) => e.subject_ids);
|
||||||
|
|
||||||
|
async function loop(summary: { data: any }) {
|
||||||
|
const reviews = availableNow("reviews", summary);
|
||||||
|
const lessons = availableNow("lessons", summary);
|
||||||
|
console.log("W A N I 🦀 🐊 K A N I");
|
||||||
|
console.log(Colors.red(lessons.length.toString() + " Lessons") + ' ' + Colors.blue(reviews.length.toString() + ' Reviews'));
|
||||||
|
|
||||||
|
const command: string = await Select.prompt({
|
||||||
|
message: "What would you like to do?",
|
||||||
|
options: [
|
||||||
|
{ name: "Reviews", value: "reviews" },
|
||||||
|
{ name: "Lessons", value: "lessons", disabled: true },
|
||||||
|
Select.separator("--------"),
|
||||||
|
{ name: "Quit", value: "quit" },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (command) {
|
||||||
|
case "quit":
|
||||||
|
break;
|
||||||
|
case "reviews":
|
||||||
|
await startReviews(reviews);
|
||||||
|
loop(summary);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
loop(summary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startReviews = async (reviews: any) => {
|
||||||
|
const [first, ...rest] = reviews;
|
||||||
|
const subject = await getSubject(first);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (subject.object !== 'radical') {
|
||||||
|
const incorrectReadings = await readingPrompt(subject);
|
||||||
|
const audio = subject.data.pronunciation_audios?.[0].url;
|
||||||
|
if (audio) {
|
||||||
|
const cmd = Deno.run({ cmd: ["mpv", audio], stdout: "null", stderr: "null" }); // TODO
|
||||||
|
await cmd.status();
|
||||||
|
cmd.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!incorrectReadings) {
|
||||||
|
console.log("Correct!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const incorrectMeanings = await meaningPrompt(subject);
|
||||||
|
if (!incorrectMeanings) {
|
||||||
|
console.log("Correct!");
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
console.log('error', e);
|
||||||
|
// TODO wrap up
|
||||||
|
// loop(summary);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await startReviews(rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
const readingPrompt = async (subject: any): Promise<number> => {
|
||||||
|
const { object, data: { characters, readings } } = subject;
|
||||||
|
|
||||||
|
console.log(`${characters} (${object} reading)`);
|
||||||
|
|
||||||
|
let input = '';
|
||||||
|
for await (const key of readKeypress()) {
|
||||||
|
if (key.key === 'return') {
|
||||||
|
break;
|
||||||
|
} else if (key.ctrlKey && key.key === 'c') {
|
||||||
|
throw 'Cancelled';
|
||||||
|
} else if (key.key === 'backspace') {
|
||||||
|
input = input.slice(0, -1);
|
||||||
|
} else {
|
||||||
|
input = input + key.sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
const kana = wanakana.toHiragana(input);
|
||||||
|
const text = new TextEncoder().encode('\r' + kana);
|
||||||
|
Deno.writeAll(Deno.stdout, text);
|
||||||
|
}
|
||||||
|
const reading = wanakana.toHiragana(input);
|
||||||
|
|
||||||
|
const text = new TextEncoder().encode('\r\n');
|
||||||
|
Deno.writeAll(Deno.stdout, text);
|
||||||
|
|
||||||
|
if (readings.find((r: any) => r.reading === reading && r.accepted_answer)) {
|
||||||
|
return 0;
|
||||||
|
} else if (readings.find((r: any) => r.reading === reading && !r.accepted_answer)) {
|
||||||
|
console.log('Looking for the other reading...');
|
||||||
|
return await readingPrompt(subject);
|
||||||
|
} else {
|
||||||
|
const tryAgain: boolean = await Confirm.prompt({ message: "Try again?", default: true });
|
||||||
|
if (!tryAgain) {
|
||||||
|
console.log(readings);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 1 + await readingPrompt(subject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const meaningPrompt = async (subject: any): Promise<number> => {
|
||||||
|
const { object, data: { characters, meanings } } = subject;
|
||||||
|
|
||||||
|
console.log(`${characters} (${object} meaning)`);
|
||||||
|
|
||||||
|
const meaning = await Input.prompt('');
|
||||||
|
|
||||||
|
if (meanings.find((r: any) => r.meaning.toLowerCase() === meaning.toLowerCase() && r.accepted_answer)) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
const tryAgain: boolean = await Confirm.prompt({ message: "Try again?", default: true });
|
||||||
|
if (!tryAgain) {
|
||||||
|
console.log(meanings);
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return 1 + await meaningPrompt(subject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function start() {
|
||||||
|
await getToken();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const summary = await getSummary();
|
||||||
|
loop(summary);
|
||||||
|
} catch(e) {
|
||||||
|
authError();
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start();
|
Loading…
Reference in New Issue