add src folder

This commit is contained in:
polv committed 2022-04-26 08:12:15 +07:00
1 parent 79673e55fe
commit 16f3e28690
9 files changed
+232 -204

No files matched your search

+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"semi": false, "semi": true,
"arrowParens": "always", "arrowParens": "always",
"singleQuote": true, "singleQuote": true,
"trailingComma": "none" "trailingComma": "all"
} }
+27 -27
View File
@@ -1,54 +1,54 @@
import path from 'path' import path from 'path';
import axios from 'axios' import axios from 'axios';
import fs from 'fs/promises' import fs from 'fs/promises';
import { nanoid } from 'nanoid' import { nanoid } from 'nanoid';
const ROOTDIR = 'docs/assets' const ROOTDIR = 'docs/assets';
const log = { const log = {
data: {} as Record<string, string>, data: {} as Record<string, string>,
filename: path.join(ROOTDIR, 'download-radical.json'), filename: path.join(ROOTDIR, 'download-radical.json'),
async load() { async load() {
this.data = JSON.parse(await fs.readFile(this.filename, 'utf-8')) this.data = JSON.parse(await fs.readFile(this.filename, 'utf-8'));
return this.data return this.data;
}, },
async dump() { 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) { async function doDownloadRadical(url: string, note?: string) {
const r = await axios.get(url, { const r = await axios.get(url, {
responseType: 'arraybuffer' responseType: 'arraybuffer',
}) });
const filename = `${nanoid()}.png` const filename = `${nanoid()}.png`;
await fs.writeFile(path.join(ROOTDIR, filename), r.data) await fs.writeFile(path.join(ROOTDIR, filename), r.data);
console.info(filename) console.info(filename);
if (note) { if (note) {
log.data[filename] = note log.data[filename] = note;
} }
} }
async function downloadFromList(toBeDownloaded: Record<string, string>) { async function downloadFromList(toBeDownloaded: Record<string, string>) {
const chunkSize = 50 const chunkSize = 50;
const entries = Object.entries(toBeDownloaded) const entries = Object.entries(toBeDownloaded);
for (let i = 0; i < entries.length; i += chunkSize) { for (let i = 0; i < entries.length; i += chunkSize) {
await Promise.all( 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() { async function main() {
await log.load() await log.load();
const [, , url, ...parts] = process.argv const [, , url, ...parts] = process.argv;
if (url) { if (url) {
await doDownloadRadical(url, parts.join(' ')) await doDownloadRadical(url, parts.join(' '));
} else { } else {
await downloadFromList({ await downloadFromList({
'https://git.polv.cc/attachments/d76f268a-8b40-4bba-ab24-e10de2e69f78': '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': 'https://aws1.discourse-cdn.com/wanikanicommunity/original/4X/4/3/9/43978d49ccdc893a5ddd768790a9a84e0ad8c965.png':
'Safety roof', 'Safety roof',
'https://aws1.discourse-cdn.com/wanikanicommunity/original/4X/d/2/d/d2d27d66900c320ad70ee40a0985ee355a757f1e.png': '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) { if (require.main === module) {
main() main();
} }
+59 -61
View File
@@ -1,30 +1,28 @@
import fs from 'fs' import fs from 'fs';
import yaml from 'js-yaml' import { IKanji, IRadical, WaniKani } from '@/wanikani';
import { toKatakana } from 'wanakana' import yaml from 'js-yaml';
import { toKatakana } from 'wanakana';
import { subjects } from './dump-subjects'
import { IKanji, IRadical } from './shared'
interface IItem { interface IItem {
document_url: string document_url: string;
level: number level: number;
meaning: string meaning: string;
reading?: string reading?: string;
components: { components: {
[source: string]: string[] [source: string]: string[];
} };
similar: string[] similar: string[];
image?: { image?: {
src: string src: string;
content_type: string content_type: string;
width: string | undefined width: string | undefined;
height: string | undefined height: string | undefined;
} };
} }
interface IBeautifiedRadicals { interface IBeautifiedRadicals {
[id: string]: IItem [id: string]: IItem;
} }
export const radicals = { export const radicals = {
@@ -32,59 +30,59 @@ export const radicals = {
filename: 'radicals.yaml', filename: 'radicals.yaml',
load() { load() {
this.data = yaml.load( this.data = yaml.load(
fs.readFileSync(this.filename, 'utf-8') fs.readFileSync(this.filename, 'utf-8'),
) as IBeautifiedRadicals ) as IBeautifiedRadicals;
return this.data return this.data;
}, },
dump(d: IBeautifiedRadicals) { dump(d: IBeautifiedRadicals) {
this.data = d this.data = d;
this.finalize() this.finalize();
}, },
finalize() { finalize() {
fs.writeFileSync( fs.writeFileSync(
this.filename, this.filename,
yaml.dump(this.data, { yaml.dump(this.data, {
skipInvalid: true skipInvalid: true,
}) }),
) );
} },
} };
async function main() { async function main() {
const rs = subjects.load() const rs = await new WaniKani().subjects();
const idMap = new Map<number, string>() const idMap = new Map<number, string>();
radicals.data = {} radicals.data = {};
rs.filter((r) => r.object === 'radical').map((r0) => { rs.filter((r) => r.object === 'radical').map((r0) => {
const r = r0 as IRadical const r = r0 as IRadical;
const d = { const d = {
document_url: r.data.document_url, document_url: r.data.document_url,
level: r.data.level, level: r.data.level,
meaning: r.data.meanings.filter((m) => m.primary)[0]!.meaning, meaning: r.data.meanings.filter((m) => m.primary)[0]!.meaning,
components: {}, components: {},
sup: r.data.amalgamation_subject_ids.map((it) => it.toString()), sup: r.data.amalgamation_subject_ids.map((it) => it.toString()),
similar: [] similar: [],
} };
let id = r.data.characters let id = r.data.characters;
if (id) { if (id) {
radicals.data[id] = d radicals.data[id] = d;
} else { } else {
id = d.meaning id = d.meaning;
const st = (im: { const st = (im: {
content_type: string content_type: string;
metadata: { dimensions?: string } metadata: { dimensions?: string };
}) => }) =>
im.content_type === 'image/svg+xml' im.content_type === 'image/svg+xml'
? 5000 ? 5000
: im.metadata.dimensions : im.metadata.dimensions
? Math.min(...im.metadata.dimensions.split('x').map(Number)) ? Math.min(...im.metadata.dimensions.split('x').map(Number))
: 0 : 0;
const im = r.data.character_images.sort((a, b) => st(a) - st(b))[0]! const im = r.data.character_images.sort((a, b) => st(a) - st(b))[0]!;
const dim = im.metadata.dimensions?.split('x') || [] const dim = im.metadata.dimensions?.split('x') || [];
radicals.data[id] = { radicals.data[id] = {
...d, ...d,
@@ -92,17 +90,17 @@ async function main() {
src: im.url, src: im.url,
content_type: im.content_type, content_type: im.content_type,
width: dim[0], 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) => { rs.filter((r) => r.object === 'kanji').map((r0) => {
const r = r0 as IKanji const r = r0 as IKanji;
const reading = r.data.readings.filter((m) => m.primary)[0]! const reading = r.data.readings.filter((m) => m.primary)[0]!;
const d = { const d = {
document_url: r.data.document_url, document_url: r.data.document_url,
level: r.data.level, level: r.data.level,
@@ -112,13 +110,13 @@ async function main() {
? toKatakana(reading.reading) ? toKatakana(reading.reading)
: reading.reading, : reading.reading,
components: { 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 const id = r.data.characters;
idMap.set(r.id, id) idMap.set(r.id, id);
// const prev = radicals.data[id] // const prev = radicals.data[id]
// if (prev) { // 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)) { for (const [k, v] of Object.entries(radicals.data)) {
if (v.components?.['wanikani']) { if (v.components?.['wanikani']) {
v.components['wanikani'] = v.components['wanikani'] v.components['wanikani'] = v.components['wanikani']
.map((s) => idMap.get(Number(s)) || s) .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) // v.sup = v.sup.map((s) => idMap.get(Number(s)) || s)
} }
radicals.finalize() radicals.finalize();
} }
if (require.main === module) { if (require.main === module) {
main() main();
} }
+14 -14
View File
@@ -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() { async function main() {
const map = radicals.load() const map = radicals.load();
fs.writeFileSync( fs.writeFileSync(
'../../_radicals.md', '../../_radicals.md',
Object.entries(map) Object.entries(map)
.map(([k, v]) => { .map(([k, v]) => {
const headers = [k] const headers = [k];
if (v.meaning !== k) { if (v.meaning !== k) {
headers.push(v.meaning) headers.push(v.meaning);
} }
if (v.reading) { 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) { if (v.image) {
rows.push( rows.push(
`<img src="${v.image.src}" alt="${k}"${ `<img src="${v.image.src}" alt="${k}"${
v.image.width ? ` width="${v.image.width}"` : '' v.image.width ? ` width="${v.image.width}"` : ''
}${v.image.height ? ` height="${v.image.height}"` : ''} />`, }${v.image.height ? ` height="${v.image.height}"` : ''} />`,
'' '',
) );
} }
return rows.join('\n') return rows.join('\n');
}) })
.join('\n\n') .join('\n\n'),
) );
} }
if (require.main === module) { if (require.main === module) {
main() main();
} }
+3 -47
View File
@@ -1,53 +1,9 @@
import fs from 'fs' import { WaniKani } from '@/wanikani';
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))
}
}
async function main() { async function main() {
const wk = axios.create({ await new WaniKani().subjects({ force: true });
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)
} }
if (require.main === module) { if (require.main === module) {
main() main();
} }
-49
View File
@@ -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[]
}
}
+11
View File
@@ -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,
});
}
}
+112
View File
@@ -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[];
};
}
+4 -4
View File
@@ -27,10 +27,10 @@
"module": "commonjs", /* Specify what module code is generated. */ "module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */ // "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "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. */ "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": { "paths": {
// "@/*": ["src/*"] "@/*": ["src/*"]
// }, /* Specify a set of entries that re-map imports to additional lookup locations. */ }, /* 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. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ // "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. */ // "types": [], /* Specify type package names to be included without being referenced in a source file. */