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