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