Browse Source

add src folder

main
parent
commit
16f3e28690
9 changed files with 232 additions and 204 deletions
  1. +2
    -2
      .prettierrc.json
  2. +27
    -27
      scripts/download-radical.ts
  3. +59
    -61
      scripts/wk-api/beautify-radicals.ts
  4. +14
    -14
      scripts/wk-api/build-radicals.ts
  5. +3
    -47
      scripts/wk-api/dump-subjects.ts
  6. +0
    -49
      scripts/wk-api/shared.ts
  7. +11
    -0
      src/ankiconnect.ts
  8. +112
    -0
      src/wanikani.ts
  9. +4
    -4
      tsconfig.json

+ 2
- 2
.prettierrc.json View File

@ -1,6 +1,6 @@
{
"semi": false,
"semi": true,
"arrowParens": "always",
"singleQuote": true,
"trailingComma": "none"
"trailingComma": "all"
}

+ 27
- 27
scripts/download-radical.ts View File

@ -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<string, string>,
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<string, string>) {
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();
}

+ 59
- 61
scripts/wk-api/beautify-radicals.ts View File

@ -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<number, string>()
radicals.data = {}
const idMap = new Map<number, string>();
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();
}

+ 14
- 14
scripts/wk-api/build-radicals.ts 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() {
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(
`<img src="${v.image.src}" alt="${k}"${
v.image.width ? ` width="${v.image.width}"` : ''
}${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) {
main()
main();
}

+ 3
- 47
scripts/wk-api/dump-subjects.ts View File

@ -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();
}

+ 0
- 49
scripts/wk-api/shared.ts 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
- 0
src/ankiconnect.ts 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
- 0
src/wanikani.ts 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
tsconfig.json View File

@ -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. */

Loading…
Cancel
Save