From 16f3e28690ae0723cad56ba50ff60faeeeb0f087 Mon Sep 17 00:00:00 2001 From: Pacharapol Withayasakpunt Date: Tue, 26 Apr 2022 08:12:15 +0700 Subject: [PATCH] add src folder --- .prettierrc.json | 4 +- scripts/download-radical.ts | 54 ++++++------- scripts/wk-api/beautify-radicals.ts | 120 ++++++++++++++-------------- scripts/wk-api/build-radicals.ts | 28 +++---- scripts/wk-api/dump-subjects.ts | 50 +----------- scripts/wk-api/shared.ts | 49 ------------ src/ankiconnect.ts | 11 +++ src/wanikani.ts | 112 ++++++++++++++++++++++++++ tsconfig.json | 8 +- 9 files changed, 232 insertions(+), 204 deletions(-) delete mode 100644 scripts/wk-api/shared.ts create mode 100644 src/ankiconnect.ts create mode 100644 src/wanikani.ts diff --git a/.prettierrc.json b/.prettierrc.json index 95bf7f6..0dd12bb 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,6 +1,6 @@ { - "semi": false, + "semi": true, "arrowParens": "always", "singleQuote": true, - "trailingComma": "none" + "trailingComma": "all" } diff --git a/scripts/download-radical.ts b/scripts/download-radical.ts index 1517f85..fbfa765 100644 --- a/scripts/download-radical.ts +++ b/scripts/download-radical.ts @@ -1,54 +1,54 @@ -import path from 'path' +import path from 'path'; -import axios from 'axios' -import fs from 'fs/promises' -import { nanoid } from 'nanoid' +import axios from 'axios'; +import fs from 'fs/promises'; +import { nanoid } from 'nanoid'; -const ROOTDIR = 'docs/assets' +const ROOTDIR = 'docs/assets'; const log = { data: {} as Record, filename: path.join(ROOTDIR, 'download-radical.json'), async load() { - this.data = JSON.parse(await fs.readFile(this.filename, 'utf-8')) - return this.data + this.data = JSON.parse(await fs.readFile(this.filename, 'utf-8')); + return this.data; }, async dump() { - await fs.writeFile(this.filename, JSON.stringify(this.data, null, 2)) - } -} + await fs.writeFile(this.filename, JSON.stringify(this.data, null, 2)); + }, +}; async function doDownloadRadical(url: string, note?: string) { const r = await axios.get(url, { - responseType: 'arraybuffer' - }) + responseType: 'arraybuffer', + }); - const filename = `${nanoid()}.png` - await fs.writeFile(path.join(ROOTDIR, filename), r.data) + const filename = `${nanoid()}.png`; + await fs.writeFile(path.join(ROOTDIR, filename), r.data); - console.info(filename) + console.info(filename); if (note) { - log.data[filename] = note + log.data[filename] = note; } } async function downloadFromList(toBeDownloaded: Record) { - const chunkSize = 50 - const entries = Object.entries(toBeDownloaded) + const chunkSize = 50; + const entries = Object.entries(toBeDownloaded); for (let i = 0; i < entries.length; i += chunkSize) { await Promise.all( - entries.slice(i, i + chunkSize).map(([k, v]) => doDownloadRadical(k, v)) - ) + entries.slice(i, i + chunkSize).map(([k, v]) => doDownloadRadical(k, v)), + ); } } async function main() { - await log.load() + await log.load(); - const [, , url, ...parts] = process.argv + const [, , url, ...parts] = process.argv; if (url) { - await doDownloadRadical(url, parts.join(' ')) + await doDownloadRadical(url, parts.join(' ')); } else { await downloadFromList({ 'https://git.polv.cc/attachments/d76f268a-8b40-4bba-ab24-e10de2e69f78': @@ -120,13 +120,13 @@ async function main() { 'https://aws1.discourse-cdn.com/wanikanicommunity/original/4X/4/3/9/43978d49ccdc893a5ddd768790a9a84e0ad8c965.png': 'Safety roof', 'https://aws1.discourse-cdn.com/wanikanicommunity/original/4X/d/2/d/d2d27d66900c320ad70ee40a0985ee355a757f1e.png': - 'I have a scooter (Have scooter)' - }) + 'I have a scooter (Have scooter)', + }); } - await log.dump() + await log.dump(); } if (require.main === module) { - main() + main(); } diff --git a/scripts/wk-api/beautify-radicals.ts b/scripts/wk-api/beautify-radicals.ts index ffc6244..6a9c80f 100644 --- a/scripts/wk-api/beautify-radicals.ts +++ b/scripts/wk-api/beautify-radicals.ts @@ -1,30 +1,28 @@ -import fs from 'fs' +import fs from 'fs'; -import yaml from 'js-yaml' -import { toKatakana } from 'wanakana' - -import { subjects } from './dump-subjects' -import { IKanji, IRadical } from './shared' +import { IKanji, IRadical, WaniKani } from '@/wanikani'; +import yaml from 'js-yaml'; +import { toKatakana } from 'wanakana'; interface IItem { - document_url: string - level: number - meaning: string - reading?: string + document_url: string; + level: number; + meaning: string; + reading?: string; components: { - [source: string]: string[] - } - similar: string[] + [source: string]: string[]; + }; + similar: string[]; image?: { - src: string - content_type: string - width: string | undefined - height: string | undefined - } + src: string; + content_type: string; + width: string | undefined; + height: string | undefined; + }; } interface IBeautifiedRadicals { - [id: string]: IItem + [id: string]: IItem; } export const radicals = { @@ -32,59 +30,59 @@ export const radicals = { filename: 'radicals.yaml', load() { this.data = yaml.load( - fs.readFileSync(this.filename, 'utf-8') - ) as IBeautifiedRadicals - return this.data + fs.readFileSync(this.filename, 'utf-8'), + ) as IBeautifiedRadicals; + return this.data; }, dump(d: IBeautifiedRadicals) { - this.data = d - this.finalize() + this.data = d; + this.finalize(); }, finalize() { fs.writeFileSync( this.filename, yaml.dump(this.data, { - skipInvalid: true - }) - ) - } -} + skipInvalid: true, + }), + ); + }, +}; async function main() { - const rs = subjects.load() + const rs = await new WaniKani().subjects(); - const idMap = new Map() - radicals.data = {} + const idMap = new Map(); + radicals.data = {}; rs.filter((r) => r.object === 'radical').map((r0) => { - const r = r0 as IRadical + const r = r0 as IRadical; const d = { document_url: r.data.document_url, level: r.data.level, meaning: r.data.meanings.filter((m) => m.primary)[0]!.meaning, components: {}, sup: r.data.amalgamation_subject_ids.map((it) => it.toString()), - similar: [] - } + similar: [], + }; - let id = r.data.characters + let id = r.data.characters; if (id) { - radicals.data[id] = d + radicals.data[id] = d; } else { - id = d.meaning + id = d.meaning; const st = (im: { - content_type: string - metadata: { dimensions?: string } + content_type: string; + metadata: { dimensions?: string }; }) => im.content_type === 'image/svg+xml' ? 5000 : im.metadata.dimensions ? Math.min(...im.metadata.dimensions.split('x').map(Number)) - : 0 - const im = r.data.character_images.sort((a, b) => st(a) - st(b))[0]! - const dim = im.metadata.dimensions?.split('x') || [] + : 0; + const im = r.data.character_images.sort((a, b) => st(a) - st(b))[0]!; + const dim = im.metadata.dimensions?.split('x') || []; radicals.data[id] = { ...d, @@ -92,17 +90,17 @@ async function main() { src: im.url, content_type: im.content_type, width: dim[0], - height: dim[1] - } - } + height: dim[1], + }, + }; } - idMap.set(r.id, id) - }) + idMap.set(r.id, id); + }); rs.filter((r) => r.object === 'kanji').map((r0) => { - const r = r0 as IKanji - const reading = r.data.readings.filter((m) => m.primary)[0]! + const r = r0 as IKanji; + const reading = r.data.readings.filter((m) => m.primary)[0]!; const d = { document_url: r.data.document_url, level: r.data.level, @@ -112,13 +110,13 @@ async function main() { ? toKatakana(reading.reading) : reading.reading, components: { - wanikani: r.data.component_subject_ids.map((it) => it.toString()) + wanikani: r.data.component_subject_ids.map((it) => it.toString()), }, - similar: r.data.visually_similar_subject_ids.map((it) => it.toString()) - } + similar: r.data.visually_similar_subject_ids.map((it) => it.toString()), + }; - const id = r.data.characters - idMap.set(r.id, id) + const id = r.data.characters; + idMap.set(r.id, id); // const prev = radicals.data[id] // if (prev) { @@ -133,22 +131,22 @@ async function main() { // } // } - radicals.data[id] = d - }) + radicals.data[id] = d; + }); for (const [k, v] of Object.entries(radicals.data)) { if (v.components?.['wanikani']) { v.components['wanikani'] = v.components['wanikani'] .map((s) => idMap.get(Number(s)) || s) - .filter((s) => s !== k) + .filter((s) => s !== k); } - v.similar = v.similar.map((s) => idMap.get(Number(s)) || s) + v.similar = v.similar.map((s) => idMap.get(Number(s)) || s); // v.sup = v.sup.map((s) => idMap.get(Number(s)) || s) } - radicals.finalize() + radicals.finalize(); } if (require.main === module) { - main() + main(); } diff --git a/scripts/wk-api/build-radicals.ts b/scripts/wk-api/build-radicals.ts index d13aab2..3f8842d 100644 --- a/scripts/wk-api/build-radicals.ts +++ b/scripts/wk-api/build-radicals.ts @@ -1,39 +1,39 @@ -import fs from 'fs' +import fs from 'fs'; -import { radicals } from './beautify-radicals' +import { radicals } from './beautify-radicals'; async function main() { - const map = radicals.load() + const map = radicals.load(); fs.writeFileSync( '../../_radicals.md', Object.entries(map) .map(([k, v]) => { - const headers = [k] + const headers = [k]; if (v.meaning !== k) { - headers.push(v.meaning) + headers.push(v.meaning); } if (v.reading) { - headers.push(v.reading) + headers.push(v.reading); } - headers.push(String(v.level)) + headers.push(String(v.level)); - const rows = [`## ${headers.join(', ')}`, ''] + const rows = [`## ${headers.join(', ')}`, '']; if (v.image) { rows.push( `${k}`, - '' - ) + '', + ); } - return rows.join('\n') + return rows.join('\n'); }) - .join('\n\n') - ) + .join('\n\n'), + ); } if (require.main === module) { - main() + main(); } diff --git a/scripts/wk-api/dump-subjects.ts b/scripts/wk-api/dump-subjects.ts index 0cac60b..9dbd8e5 100644 --- a/scripts/wk-api/dump-subjects.ts +++ b/scripts/wk-api/dump-subjects.ts @@ -1,53 +1,9 @@ -import fs from 'fs' - -import axios from 'axios' - -import { IKanji, IRadical } from './shared' - -export const subjects = { - data: [] as (IKanji | IRadical)[], - filename: 'wanikani.json', - load() { - this.data = JSON.parse(fs.readFileSync(this.filename, 'utf-8')) - return this.data - }, - dump(d: (IKanji | IRadical)[]) { - this.data = d - this.finalize() - }, - finalize() { - fs.writeFileSync(this.filename, JSON.stringify(this.data)) - } -} +import { WaniKani } from '@/wanikani'; async function main() { - const wk = axios.create({ - baseURL: 'https://api.wanikani.com/v2/', - headers: { - Authorization: `Bearer ${process.env['WANIKANI_API_KEY']}` - } - }) - - const data: (IKanji | IRadical)[] = [] - - let nextURL = '/subjects?types=radical,kanji' - while (nextURL) { - const r = await wk - .get<{ - pages: { - next_url?: string - } - data: (IKanji | IRadical)[] - }>(nextURL) - .then((r) => r.data) - data.push(...r.data) - console.info(r.pages.next_url) - nextURL = r.pages.next_url || '' - } - - subjects.dump(data) + await new WaniKani().subjects({ force: true }); } if (require.main === module) { - main() + main(); } diff --git a/scripts/wk-api/shared.ts b/scripts/wk-api/shared.ts deleted file mode 100644 index 0914343..0000000 --- a/scripts/wk-api/shared.ts +++ /dev/null @@ -1,49 +0,0 @@ -export interface IKanji { - id: number - /** Actual type */ - object: 'kanji' - url: string - data_updated_at: string - data: { - level: number - document_url: string - characters: string - meanings: { - meaning: string - primary: boolean - }[] - readings: { - reading: string - primary: boolean - type: 'kunyomi' | 'onyomi' - }[] - component_subject_ids: number[] - visually_similar_subject_ids: number[] - } -} - -export interface IRadical { - id: number - /** Actual type */ - object: 'radical' - url: string - data_updated_at: string - data: { - level: number - document_url: string - characters: string | null - character_images: { - url: string - metadata: { - inline_styles?: boolean - dimensions?: string - } - content_type: string - }[] - meanings: { - meaning: string - primary: boolean - }[] - amalgamation_subject_ids: number[] - } -} diff --git a/src/ankiconnect.ts b/src/ankiconnect.ts new file mode 100644 index 0000000..d17ffe8 --- /dev/null +++ b/src/ankiconnect.ts @@ -0,0 +1,11 @@ +import axios, { AxiosInstance } from 'axios'; + +export class AnkiConnect { + $axios: AxiosInstance; + + constructor(public url = 'http://localhost:8765') { + this.$axios = axios.create({ + baseURL: url, + }); + } +} diff --git a/src/wanikani.ts b/src/wanikani.ts new file mode 100644 index 0000000..13437bc --- /dev/null +++ b/src/wanikani.ts @@ -0,0 +1,112 @@ +import fs from 'fs'; + +import axios, { AxiosInstance } from 'axios'; + +export class WaniKani { + $axios: AxiosInstance; + + constructor(public apiKey = process.env['WANIKANI_API_KEY']!) { + this.$axios = axios.create({ + baseURL: 'https://api.wanikani.com/v2/', + headers: { + Authorization: `Bearer ${process.env['WANIKANI_API_KEY']}`, + }, + }); + } + + async subjects({ + force, + }: { + force?: boolean; + } = {}) { + const subjects = { + data: [] as (IKanji | IRadical)[], + filename: 'wanikani.json', + load() { + this.data = JSON.parse(fs.readFileSync(this.filename, 'utf-8')); + return this.data; + }, + dump(d: (IKanji | IRadical)[]) { + this.data = d; + this.finalize(); + }, + finalize() { + fs.writeFileSync(this.filename, JSON.stringify(this.data)); + }, + }; + + let data = subjects.load(); + if (data.length && !force) { + return data; + } + data = []; + + let nextURL = '/subjects?types=radical,kanji'; + while (nextURL) { + const r = await this.$axios + .get<{ + pages: { + next_url?: string; + }; + data: (IKanji | IRadical)[]; + }>(nextURL) + .then((r) => r.data); + data.push(...r.data); + console.info(r.pages.next_url); + nextURL = r.pages.next_url || ''; + } + + subjects.dump(data); + return data; + } +} + +export interface IKanji { + id: number; + /** Actual type */ + object: 'kanji'; + url: string; + data_updated_at: string; + data: { + level: number; + document_url: string; + characters: string; + meanings: { + meaning: string; + primary: boolean; + }[]; + readings: { + reading: string; + primary: boolean; + type: 'kunyomi' | 'onyomi'; + }[]; + component_subject_ids: number[]; + visually_similar_subject_ids: number[]; + }; +} + +export interface IRadical { + id: number; + /** Actual type */ + object: 'radical'; + url: string; + data_updated_at: string; + data: { + level: number; + document_url: string; + characters: string | null; + character_images: { + url: string; + metadata: { + inline_styles?: boolean; + dimensions?: string; + }; + content_type: string; + }[]; + meanings: { + meaning: string; + primary: boolean; + }[]; + amalgamation_subject_ids: number[]; + }; +} diff --git a/tsconfig.json b/tsconfig.json index c2234c7..e36be5a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,10 +27,10 @@ "module": "commonjs", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": { - // "@/*": ["src/*"] - // }, /* Specify a set of entries that re-map imports to additional lookup locations. */ + "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + "paths": { + "@/*": ["src/*"] + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */