├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── README.md ├── des.ts ├── dist ├── captcha.d.ts ├── captcha.js ├── des.d.ts ├── des.js ├── index.d.ts ├── index.js ├── recognize.d.ts └── recognize.js ├── index.ts ├── package.json ├── recognize.ts ├── tsconfig.json └── yarn.lock /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: npm-publish 2 | on: push 3 | 4 | jobs: 5 | publish: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v1 9 | - uses: actions/setup-node@v1 10 | with: 11 | node-version: 12 12 | - run: npm install --dev 13 | - uses: JS-DevTools/npm-publish@v1 14 | with: 15 | token: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | *.traineddata 4 | test.* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Welcome to node-hustpass 👋

2 |

3 | 4 | Version 5 | 6 | 7 | License: MIT 8 | 9 |

10 | 11 | > HUSTPass auth library for Node.js. Node.js 华科统一身份认证库。 12 | 13 | ## Requirement 14 | 15 | None. 16 | 17 | ## Install 18 | 19 | ```sh 20 | yarn add node-hustpass 21 | ``` 22 | 23 | ## Usage 24 | 25 | ```javascript 26 | const { init } = require("node-hustpass"); 27 | 28 | (async () => { 29 | const { fetch, jar, login } = await init(); 30 | 31 | await login({ 32 | username: "U201X12345", 33 | password: "QZBSQZZH", 34 | url: 35 | "https://pass.hust.edu.cn/cas/login?service=http%3A%2F%2Fhubs.hust.edu.cn%2Fhustpass.action" 36 | }); 37 | /** 38 | * do something cool here! 39 | * you can simply use this `fetch` method, 40 | * or pass `jar` into your own request library. 41 | */ 42 | })(); 43 | ``` 44 | 45 | ## Author 46 | 47 | 👤 **maniacata** 48 | 49 | 50 | ## Show your support 51 | 52 | Give a ⭐️ if this project helped you! 53 | 54 | *** 55 | _This README was generated with ❤️ by [readme-md-generator](https://github.com/kefranabg/readme-md-generator)_ 56 | -------------------------------------------------------------------------------- /des.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was originally written by winderica 3 | */ 4 | 5 | import { createCipheriv } from "crypto"; 6 | 7 | const magicTable = [ 8 | 0, 9 | 1, 10 | 2, 11 | 6, 12 | 38, 13 | 37, 14 | 36, 15 | 7, 16 | 8, 17 | 9, 18 | 10, 19 | 14, 20 | 46, 21 | 45, 22 | 44, 23 | 15, 24 | 16, 25 | 17, 26 | 18, 27 | 22, 28 | 54, 29 | 53, 30 | 52, 31 | 23, 32 | 24, 33 | 25, 34 | 26, 35 | 30, 36 | 62, 37 | 61, 38 | 60, 39 | 31, 40 | 32, 41 | 33, 42 | 34, 43 | 35, 44 | 5, 45 | 4, 46 | 3, 47 | 39, 48 | 40, 49 | 41, 50 | 42, 51 | 43, 52 | 13, 53 | 12 54 | /** 55 | * This method was originally written by winderica 56 | * @author winderica 57 | * @license MIT 58 | * @see {@link https://github.com/winderica/DailyReport} 59 | * @param {string} plain 60 | * @param {string} k1 61 | * @param {string} k2 62 | * @param {string} k3 63 | * @description RSA is actually DES, their implementation of which is not surprisingly incorrect. 64 | */, 65 | 11, 66 | 47, 67 | 48, 68 | 49, 69 | 50, 70 | 51, 71 | 21, 72 | 20, 73 | 19, 74 | 55, 75 | 56, 76 | 57, 77 | 58, 78 | 59, 79 | 29, 80 | 28, 81 | 27, 82 | 63, 83 | ]; 84 | 85 | const toBuffer = (t: string) => 86 | Buffer.from(t.padEnd(Math.ceil(t.length / 4) * 4, "\0"), "utf16le").swap16(); 87 | const toBits = (str: string) => 88 | [...toBuffer(str)].flatMap((i) => 89 | i.toString(2).padStart(8, "0").split("").map(Number) 90 | ); 91 | const desEncrypt = (plain: Buffer, key: string) => { 92 | const k = magicTable 93 | .map((i) => toBits(key)[i]) 94 | .join("") 95 | .match(/.{1,8}/g)! 96 | .map((i) => parseInt(i, 2)); 97 | const cipher = createCipheriv("DES-ECB", Buffer.from(k), null).setAutoPadding( 98 | false 99 | ); 100 | return Buffer.concat([cipher.update(plain), cipher.final()]); 101 | }; 102 | 103 | /** 104 | * This method was originally written by winderica 105 | * @author winderica 106 | * @license MIT 107 | * @see {@link https://github.com/winderica/DailyReport} 108 | * @param {string} plain 109 | * @param {string} k1 110 | * @param {string} k2 111 | * @param {string} k3 112 | * @description RSA is actually DES, their implementation of which is not surprisingly incorrect. 113 | */ 114 | 115 | export const desEEE = (plain: string, k1: string, k2: string, k3: string) => { 116 | return desEncrypt(desEncrypt(desEncrypt(toBuffer(plain), k1), k2), k3) 117 | .toString("hex") 118 | .toUpperCase(); 119 | }; 120 | -------------------------------------------------------------------------------- /dist/captcha.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module "gm" { 3 | interface State { 4 | selectFrame(frame: number): this; 5 | } 6 | } 7 | export declare const readCaptcha: (captcha: Buffer) => Promise; 8 | -------------------------------------------------------------------------------- /dist/captcha.js: -------------------------------------------------------------------------------- 1 | import { createWorker } from "tesseract.js"; 2 | import * as gm from "gm"; 3 | const im = gm.subClass({ imageMagick: true }); 4 | const processImage = (buffer) => new Promise((resolve, reject) => { 5 | im(buffer) 6 | .selectFrame(1) 7 | .threshold(90, true) 8 | .in("-morphology", "close:2", "1x4: 0,1,1,0") 9 | .crop(84, 20, 0, 18) // original image size: 90x58 10 | .normalize() 11 | .toBuffer("png", (err, buffer) => { 12 | if (err) 13 | reject(err); 14 | if (buffer) 15 | resolve(buffer); 16 | }); 17 | }); 18 | export const readCaptcha = async (captcha) => { 19 | const after = await processImage(captcha); 20 | const worker = createWorker({ 21 | langPath: "http://cdn.outsiders.top", 22 | }); 23 | await worker.load(); 24 | await worker.loadLanguage("eng"); 25 | await worker.initialize("eng", 0 /* TESSERACT_ONLY */); 26 | await worker.setParameters({ 27 | tessedit_pageseg_mode: "7" /* SINGLE_LINE */, 28 | tessedit_char_whitelist: "0123456789", 29 | }); 30 | const { data: { text }, } = await worker.recognize(after); 31 | await worker.terminate(); 32 | const code = text.replace(/[ \n]/g, ""); 33 | if (code.length !== 4) 34 | return false; 35 | return code; 36 | }; 37 | -------------------------------------------------------------------------------- /dist/des.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was originally written by winderica 3 | */ 4 | /** 5 | * This method was originally written by winderica 6 | * @author winderica 7 | * @license MIT 8 | * @see {@link https://github.com/winderica/DailyReport} 9 | * @param {string} plain 10 | * @param {string} k1 11 | * @param {string} k2 12 | * @param {string} k3 13 | * @description RSA is actually DES, their implementation of which is not surprisingly incorrect. 14 | */ 15 | export declare const desEEE: (plain: string, k1: string, k2: string, k3: string) => string; 16 | -------------------------------------------------------------------------------- /dist/des.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was originally written by winderica 3 | */ 4 | import { createCipheriv } from "crypto"; 5 | const magicTable = [ 6 | 0, 7 | 1, 8 | 2, 9 | 6, 10 | 38, 11 | 37, 12 | 36, 13 | 7, 14 | 8, 15 | 9, 16 | 10, 17 | 14, 18 | 46, 19 | 45, 20 | 44, 21 | 15, 22 | 16, 23 | 17, 24 | 18, 25 | 22, 26 | 54, 27 | 53, 28 | 52, 29 | 23, 30 | 24, 31 | 25, 32 | 26, 33 | 30, 34 | 62, 35 | 61, 36 | 60, 37 | 31, 38 | 32, 39 | 33, 40 | 34, 41 | 35, 42 | 5, 43 | 4, 44 | 3, 45 | 39, 46 | 40, 47 | 41, 48 | 42, 49 | 43, 50 | 13, 51 | 12 52 | /** 53 | * This method was originally written by winderica 54 | * @author winderica 55 | * @license MIT 56 | * @see {@link https://github.com/winderica/DailyReport} 57 | * @param {string} plain 58 | * @param {string} k1 59 | * @param {string} k2 60 | * @param {string} k3 61 | * @description RSA is actually DES, their implementation of which is not surprisingly incorrect. 62 | */ , 63 | 11, 64 | 47, 65 | 48, 66 | 49, 67 | 50, 68 | 51, 69 | 21, 70 | 20, 71 | 19, 72 | 55, 73 | 56, 74 | 57, 75 | 58, 76 | 59, 77 | 29, 78 | 28, 79 | 27, 80 | 63, 81 | ]; 82 | const toBuffer = (t) => Buffer.from(t.padEnd(Math.ceil(t.length / 4) * 4, "\0"), "utf16le").swap16(); 83 | const toBits = (str) => [...toBuffer(str)].flatMap((i) => i.toString(2).padStart(8, "0").split("").map(Number)); 84 | const desEncrypt = (plain, key) => { 85 | const k = magicTable 86 | .map((i) => toBits(key)[i]) 87 | .join("") 88 | .match(/.{1,8}/g) 89 | .map((i) => parseInt(i, 2)); 90 | const cipher = createCipheriv("DES-ECB", Buffer.from(k), null).setAutoPadding(false); 91 | return Buffer.concat([cipher.update(plain), cipher.final()]); 92 | }; 93 | /** 94 | * This method was originally written by winderica 95 | * @author winderica 96 | * @license MIT 97 | * @see {@link https://github.com/winderica/DailyReport} 98 | * @param {string} plain 99 | * @param {string} k1 100 | * @param {string} k2 101 | * @param {string} k3 102 | * @description RSA is actually DES, their implementation of which is not surprisingly incorrect. 103 | */ 104 | export const desEEE = (plain, k1, k2, k3) => { 105 | return desEncrypt(desEncrypt(desEncrypt(toBuffer(plain), k1), k2), k3) 106 | .toString("hex") 107 | .toUpperCase(); 108 | }; 109 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | import { CookieJar, MemoryCookieStore } from "tough-cookie"; 2 | import nodeFetch from "node-fetch"; 3 | interface InitOptions { 4 | jar?: CookieJar; 5 | redirect?: boolean; 6 | } 7 | interface LoginOptions { 8 | username: string; 9 | password: string; 10 | url: string; 11 | } 12 | export declare const init: (options?: InitOptions) => { 13 | jar: CookieJar; 14 | store: MemoryCookieStore; 15 | fetch: typeof nodeFetch; 16 | login: (options: LoginOptions) => Promise; 17 | }; 18 | export {}; 19 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | import { URLSearchParams } from "url"; 2 | import { CookieJar, MemoryCookieStore } from "tough-cookie"; 3 | import nodeFetch from "node-fetch"; 4 | import { desEEE } from "./des.js"; 5 | import { recognize } from "./recognize.js"; 6 | import withCookie from "fetch-cookie"; 7 | const regex = { 8 | action: /action="(.*?)"/, 9 | lt: /LT-.*?-cas/, 10 | ticket: /ST-.*?-cas/, 11 | }; 12 | export const init = (options = {}) => { 13 | var _a, _b; 14 | const redirect = (_a = options.redirect) !== null && _a !== void 0 ? _a : true; 15 | const store = new MemoryCookieStore(); 16 | const jar = (_b = options.jar) !== null && _b !== void 0 ? _b : new CookieJar(store); 17 | const fetch = withCookie(nodeFetch, jar); 18 | const login = async (options) => { 19 | const { username, password, url } = options; 20 | let resp = await fetch(url); 21 | const html = await resp.text(); 22 | const lt = html.match(regex.lt)[0]; 23 | const action = html.match(regex.action)[1]; 24 | let code = false; 25 | while (!code) { 26 | resp = await fetch(`https://pass.hust.edu.cn/cas/code?${Math.random()}`); 27 | const captcha = await resp.arrayBuffer(); 28 | code = await recognize(captcha); 29 | } 30 | const form = { 31 | ul: username.length + "", 32 | pl: password.length + "", 33 | lt, 34 | code, 35 | rsa: desEEE(username + password + lt, "1", "2", "3"), 36 | execution: "e1s1", 37 | _eventId: "submit", 38 | }; 39 | resp = await fetch(`https://pass.hust.edu.cn${action}`, { 40 | method: "POST", 41 | redirect: "manual", 42 | body: new URLSearchParams(form), 43 | }); 44 | const ticket = resp.headers.get("location"); 45 | if (!ticket) { 46 | throw Error("Login failed!"); 47 | } 48 | if (redirect) { 49 | await fetch(ticket); 50 | } 51 | return ticket; 52 | }; 53 | return { jar, store, fetch, login }; 54 | }; 55 | -------------------------------------------------------------------------------- /dist/recognize.d.ts: -------------------------------------------------------------------------------- 1 | export declare const recognize: (ab: ArrayBuffer) => Promise; 2 | -------------------------------------------------------------------------------- /dist/recognize.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was originally written by winderica 3 | */ 4 | import sharp from "sharp"; 5 | // https://github.com/winderica/DailyReport/blob/4ab18c3850e60ba3074db4f2fcb76695c0335ab8/assets/digits.png 6 | const digitsStr = `iVBORw0KGgoAAAANSUhEUgAAALQAAAAUBAMAAADW/wrvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAwUExURQAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFulh5UAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFiSURBVEjHhZULEsMgCESXG8D9L9sYgV1snXamieHzJIAE8fwAi/3zvfCUPHdEqZZoCUqWSlAkmse2lHYSCUo0DjTSiOjipDtKlyJTL0fvmtv6MJ5o1y2exYvef0G/6+ciyarNYrxEJXLvkV75gMrha+hMeiXCK0c+0LTcT8EQM0xDrw6HQucdNtG4oO2GPvqBIk+7BtKy8rF2W16epTT06je6CxtdoVJLy7Jl4j+aDVZhsfjp2c0TbFmrKuGK7ufyCkGzCSFBC9reywVdwSa7qxN55LwvLMBoayaEGdQ4CNLmYUK7xHnjG9kqTpVA0dq06POqPYPhJWLLcG5o7az/aDkS49Wgou+Dc0NLQJ0PGxm1n2gN+jzwIXNCE36g34C8pkpItyck5EBv1+Bw7cwad+RMWw/dqj1cMcelJEYkMlwbfXhhy+KKti/0+FrowMK0QfRn5wsdQo75iaIO8wi0JuIDUWRHd/3sb80AAAAASUVORK5CYII=`; 7 | const transpose = (data, width) => { 8 | return [...new Array(width).keys()].map((i) => [...data].filter((_, j) => j % width === i)); 9 | }; 10 | const match = (pixels, digits) => { 11 | const scores = [...new Array(10)].map(() => 0); 12 | for (let i = 0; i < 180; i++) { 13 | for (let j = 0; j < 20; j++) { 14 | if (pixels[i % 18][j] === digits[i][j]) { 15 | scores[~~(i / 18)]++; 16 | } 17 | } 18 | } 19 | return scores.indexOf(Math.max(...scores)); 20 | }; 21 | export const recognize = async (ab) => { 22 | const buffer = new Uint8Array(ab); 23 | const data = await sharp(buffer, { page: 1 }) 24 | .extract({ left: 0, top: 19, width: 87, height: 20 }) 25 | .toColourspace("b-w") 26 | .threshold(254) 27 | .raw() 28 | .toBuffer(); 29 | const digitsImage = Buffer.from(digitsStr, "base64"); 30 | const digits = transpose(await sharp(digitsImage).toColourspace("b-w").raw().toBuffer(), 180); 31 | const pixels = [[...new Array(20)].map(() => 255), ...transpose(data, 87)]; 32 | return [...new Array(4).keys()] 33 | .map((i) => match(pixels.slice(i * 22, i * 22 + 18), digits)) 34 | .join(""); 35 | }; 36 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { URLSearchParams } from "url"; 2 | import { CookieJar, MemoryCookieStore } from "tough-cookie"; 3 | import nodeFetch from "node-fetch"; 4 | import { desEEE } from "./des.js"; 5 | import { recognize } from "./recognize.js"; 6 | import withCookie from "fetch-cookie"; 7 | 8 | const regex = { 9 | action: /action="(.*?)"/, 10 | lt: /LT-.*?-cas/, 11 | ticket: /ST-.*?-cas/, 12 | }; 13 | 14 | interface InitOptions { 15 | jar?: CookieJar; 16 | redirect?: boolean; 17 | } 18 | interface LoginOptions { 19 | username: string; 20 | password: string; 21 | url: string; 22 | } 23 | 24 | export const init = (options: InitOptions = {}) => { 25 | const redirect = options.redirect ?? true; 26 | const store = new MemoryCookieStore(); 27 | const jar = options.jar ?? new CookieJar(store); 28 | const fetch = withCookie(nodeFetch, jar) as typeof nodeFetch; 29 | 30 | const login = async (options: LoginOptions) => { 31 | const { username, password, url } = options; 32 | let resp = await fetch(url); 33 | const html = await resp.text(); 34 | const lt = (html.match(regex.lt) as string[])[0]; 35 | const action = (html.match(regex.action) as string[])[1]; 36 | let code: boolean | string = false; 37 | while (!code) { 38 | resp = await fetch(`https://pass.hust.edu.cn/cas/code?${Math.random()}`); 39 | const captcha = await resp.arrayBuffer(); 40 | code = await recognize(captcha); 41 | } 42 | const form = { 43 | ul: username.length + "", 44 | pl: password.length + "", 45 | lt, 46 | code, 47 | rsa: desEEE(username + password + lt, "1", "2", "3"), 48 | execution: "e1s1", 49 | _eventId: "submit", 50 | }; 51 | resp = await fetch(`https://pass.hust.edu.cn${action}`, { 52 | method: "POST", 53 | redirect: "manual", 54 | body: new URLSearchParams(form), 55 | }); 56 | const ticket = resp.headers.get("location"); 57 | if (!ticket) { 58 | throw Error("Login failed!"); 59 | } 60 | if (redirect) { 61 | await fetch(ticket); 62 | } 63 | return ticket; 64 | }; 65 | 66 | return { jar, store, fetch, login }; 67 | }; 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-hustpass", 3 | "version": "2.2.1", 4 | "description": "HUSTPass auth library for Node.js. Node.js 华科统一身份认证库。", 5 | "main": "./dist/index.js", 6 | "typings": "./dist/index.d.ts", 7 | "type": "module", 8 | "author": "maniacata", 9 | "license": "MIT", 10 | "files": [ 11 | "dist" 12 | ], 13 | "scripts": { 14 | "prepublish": "yarn build", 15 | "build": "tsc" 16 | }, 17 | "dependencies": { 18 | "fetch-cookie": "^2.0.5", 19 | "gm": "^1.23.1", 20 | "node-fetch": "^3.2.5", 21 | "sharp": "^0.30.6", 22 | "tesseract.js": "^2.1.5", 23 | "tough-cookie": "^4.0.0" 24 | }, 25 | "devDependencies": { 26 | "@types/gm": "^1.18.12", 27 | "@types/node": "^13.9.0", 28 | "@types/node-fetch": "^2.6.1", 29 | "@types/sharp": "^0.30.2", 30 | "@types/tough-cookie": "^4.0.2", 31 | "typescript": "^3.8.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /recognize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was originally written by winderica 3 | */ 4 | import sharp from "sharp"; 5 | 6 | // https://github.com/winderica/DailyReport/blob/4ab18c3850e60ba3074db4f2fcb76695c0335ab8/assets/digits.png 7 | const digitsStr = `iVBORw0KGgoAAAANSUhEUgAAALQAAAAUBAMAAADW/wrvAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAwUExURQAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFulh5UAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFiSURBVEjHhZULEsMgCESXG8D9L9sYgV1snXamieHzJIAE8fwAi/3zvfCUPHdEqZZoCUqWSlAkmse2lHYSCUo0DjTSiOjipDtKlyJTL0fvmtv6MJ5o1y2exYvef0G/6+ciyarNYrxEJXLvkV75gMrha+hMeiXCK0c+0LTcT8EQM0xDrw6HQucdNtG4oO2GPvqBIk+7BtKy8rF2W16epTT06je6CxtdoVJLy7Jl4j+aDVZhsfjp2c0TbFmrKuGK7ufyCkGzCSFBC9reywVdwSa7qxN55LwvLMBoayaEGdQ4CNLmYUK7xHnjG9kqTpVA0dq06POqPYPhJWLLcG5o7az/aDkS49Wgou+Dc0NLQJ0PGxm1n2gN+jzwIXNCE36g34C8pkpItyck5EBv1+Bw7cwad+RMWw/dqj1cMcelJEYkMlwbfXhhy+KKti/0+FrowMK0QfRn5wsdQo75iaIO8wi0JuIDUWRHd/3sb80AAAAASUVORK5CYII=`; 8 | 9 | const transpose = (data: Buffer, width: number) => { 10 | return [...new Array(width).keys()].map((i) => 11 | [...data].filter((_, j) => j % width === i) 12 | ); 13 | }; 14 | 15 | const match = (pixels: number[][], digits: number[][]) => { 16 | const scores = [...new Array(10)].map(() => 0); 17 | for (let i = 0; i < 180; i++) { 18 | for (let j = 0; j < 20; j++) { 19 | if (pixels[i % 18][j] === digits[i][j]) { 20 | scores[~~(i / 18)]++; 21 | } 22 | } 23 | } 24 | return scores.indexOf(Math.max(...scores)); 25 | }; 26 | 27 | export const recognize = async (ab: ArrayBuffer) => { 28 | const buffer = new Uint8Array(ab); 29 | const data = await sharp(buffer, { page: 1 }) 30 | .extract({ left: 0, top: 19, width: 87, height: 20 }) 31 | .toColourspace("b-w") 32 | .threshold(254) 33 | .raw() 34 | .toBuffer(); 35 | const digitsImage = Buffer.from(digitsStr, "base64"); 36 | const digits = transpose( 37 | await sharp(digitsImage).toColourspace("b-w").raw().toBuffer(), 38 | 180 39 | ); 40 | const pixels = [[...new Array(20)].map(() => 255), ...transpose(data, 87)]; 41 | return [...new Array(4).keys()] 42 | .map((i) => match(pixels.slice(i * 22, i * 22 + 18), digits)) 43 | .join(""); 44 | }; 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", // ts-node doesn't recognize "esnext" 4 | "module": "es2020", 5 | "esModuleInterop": true, 6 | "moduleResolution": "node", 7 | "strict": true, 8 | "declaration": true, 9 | "outDir": "dist" 10 | }, 11 | "ts-node": { 12 | "esm": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/gm@^1.18.7": 6 | version "1.18.7" 7 | resolved "https://registry.npmjs.org/@types/gm/-/gm-1.18.7.tgz#03a90d5140496b94c3ce406df62a1ba3defb594a" 8 | integrity sha512-7gOsCmtTqq3NSKinO2WsYA2uC2RVcW0wlPWXsMDP3rPNoV8Ba+8+OZhfkVstCjar2Yud0RM3jwAIv/h2cLTcSA== 9 | dependencies: 10 | "@types/node" "*" 11 | 12 | "@types/node-fetch@^2.5.5": 13 | version "2.5.10" 14 | resolved "https://registry.npm.taobao.org/@types/node-fetch/download/@types/node-fetch-2.5.10.tgz#9b4d4a0425562f9fcea70b12cb3fcdd946ca8132" 15 | integrity sha1-m01KBCVWL5/OpwsSyz/N2UbKgTI= 16 | dependencies: 17 | "@types/node" "*" 18 | form-data "^3.0.0" 19 | 20 | "@types/node@*": 21 | version "14.14.41" 22 | resolved "https://registry.npm.taobao.org/@types/node/download/@types/node-14.14.41.tgz?cache=0&sync_timestamp=1618543947232&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fnode%2Fdownload%2F%40types%2Fnode-14.14.41.tgz#d0b939d94c1d7bd53d04824af45f1139b8c45615" 23 | integrity sha1-0Lk52Uwde9U9BIJK9F8RObjEVhU= 24 | 25 | "@types/node@^13.9.0": 26 | version "13.9.0" 27 | resolved "https://registry.npmjs.org/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589" 28 | integrity sha512-0ARSQootUG1RljH2HncpsY2TJBfGQIKOOi7kxzUY6z54ePu/ZD+wJA8zI2Q6v8rol2qpG/rvqsReco8zNMPvhQ== 29 | 30 | "@types/tough-cookie@^2.3.6": 31 | version "2.3.6" 32 | resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.6.tgz#c880579e087d7a0db13777ff8af689f4ffc7b0d5" 33 | integrity sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ== 34 | 35 | array-parallel@~0.1.3: 36 | version "0.1.3" 37 | resolved "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz#8f785308926ed5aa478c47e64d1b334b6c0c947d" 38 | integrity sha1-j3hTCJJu1apHjEfmTRszS2wMlH0= 39 | 40 | array-series@~0.1.5: 41 | version "0.1.5" 42 | resolved "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz#df5d37bfc5c2ef0755e2aa4f92feae7d4b5a972f" 43 | integrity sha1-3103v8XC7wdV4qpPkv6ufUtaly8= 44 | 45 | asynckit@^0.4.0: 46 | version "0.4.0" 47 | resolved "https://registry.nlark.com/asynckit/download/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 48 | integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= 49 | 50 | bmp-js@^0.1.0: 51 | version "0.1.0" 52 | resolved "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz#e05a63f796a6c1ff25f4771ec7adadc148c07233" 53 | integrity sha1-4Fpj95amwf8l9Hcex62twUjAcjM= 54 | 55 | combined-stream@^1.0.8: 56 | version "1.0.8" 57 | resolved "https://registry.nlark.com/combined-stream/download/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 58 | integrity sha1-w9RaizT9cwYxoRCoolIGgrMdWn8= 59 | dependencies: 60 | delayed-stream "~1.0.0" 61 | 62 | cross-spawn@^4.0.0: 63 | version "4.0.2" 64 | resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz#7b9247621c23adfdd3856004a823cbe397424d41" 65 | integrity sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE= 66 | dependencies: 67 | lru-cache "^4.0.1" 68 | which "^1.2.9" 69 | 70 | debug@^3.1.0: 71 | version "3.2.6" 72 | resolved "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 73 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 74 | dependencies: 75 | ms "^2.1.1" 76 | 77 | delayed-stream@~1.0.0: 78 | version "1.0.0" 79 | resolved "https://registry.npm.taobao.org/delayed-stream/download/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 80 | integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= 81 | 82 | es6-denodeify@^0.1.1: 83 | version "0.1.5" 84 | resolved "https://registry.npmjs.org/es6-denodeify/-/es6-denodeify-0.1.5.tgz#31d4d5fe9c5503e125460439310e16a2a3f39c1f" 85 | integrity sha1-MdTV/pxVA+ElRgQ5MQ4WoqPznB8= 86 | 87 | fetch-cookie@^0.7.3: 88 | version "0.7.3" 89 | resolved "https://registry.npmjs.org/fetch-cookie/-/fetch-cookie-0.7.3.tgz#b8d023f421dd2b2f4a0eca9cd7318a967ed4eed8" 90 | integrity sha512-rZPkLnI8x5V+zYAiz8QonAHsTb4BY+iFowFBI1RFn0zrO343AVp9X7/yUj/9wL6Ef/8fLls8b/vGtzUvmyAUGA== 91 | dependencies: 92 | es6-denodeify "^0.1.1" 93 | tough-cookie "^2.3.3" 94 | 95 | file-type@^12.4.1: 96 | version "12.4.2" 97 | resolved "https://registry.npmjs.org/file-type/-/file-type-12.4.2.tgz#a344ea5664a1d01447ee7fb1b635f72feb6169d9" 98 | integrity sha512-UssQP5ZgIOKelfsaB5CuGAL+Y+q7EmONuiwF3N5HAH0t27rvrttgi6Ra9k/+DVaY9UF6+ybxu5pOXLUdA8N7Vg== 99 | 100 | form-data@^3.0.0: 101 | version "3.0.1" 102 | resolved "https://registry.npm.taobao.org/form-data/download/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" 103 | integrity sha1-69U3kbeDVqma+aMA1CgsTV65dV8= 104 | dependencies: 105 | asynckit "^0.4.0" 106 | combined-stream "^1.0.8" 107 | mime-types "^2.1.12" 108 | 109 | gm@^1.23.1: 110 | version "1.23.1" 111 | resolved "https://registry.npmjs.org/gm/-/gm-1.23.1.tgz#2edeeb958084d0f8ea7988e5d995b1c7dfc14777" 112 | integrity sha1-Lt7rlYCE0PjqeYjl2ZWxx9/BR3c= 113 | dependencies: 114 | array-parallel "~0.1.3" 115 | array-series "~0.1.5" 116 | cross-spawn "^4.0.0" 117 | debug "^3.1.0" 118 | 119 | idb-keyval@^3.2.0: 120 | version "3.2.0" 121 | resolved "https://registry.npmjs.org/idb-keyval/-/idb-keyval-3.2.0.tgz#cbbf354deb5684b6cdc84376294fc05932845bd6" 122 | integrity sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ== 123 | 124 | is-electron@^2.2.0: 125 | version "2.2.0" 126 | resolved "https://registry.npmjs.org/is-electron/-/is-electron-2.2.0.tgz#8943084f09e8b731b3a7a0298a7b5d56f6b7eef0" 127 | integrity sha512-SpMppC2XR3YdxSzczXReBjqs2zGscWQpBIKqwXYBFic0ERaxNVgwLCHwOLZeESfdJQjX0RDvrJ1lBXX2ij+G1Q== 128 | 129 | is-url@^1.2.4: 130 | version "1.2.4" 131 | resolved "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" 132 | integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== 133 | 134 | isexe@^2.0.0: 135 | version "2.0.0" 136 | resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 137 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 138 | 139 | lru-cache@^4.0.1: 140 | version "4.1.5" 141 | resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" 142 | integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== 143 | dependencies: 144 | pseudomap "^1.0.2" 145 | yallist "^2.1.2" 146 | 147 | mime-db@1.47.0: 148 | version "1.47.0" 149 | resolved "https://registry.npm.taobao.org/mime-db/download/mime-db-1.47.0.tgz?cache=0&sync_timestamp=1617306524622&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-db%2Fdownload%2Fmime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" 150 | integrity sha1-jLMT5Zll08Bc+/iYkVomevRqM1w= 151 | 152 | mime-types@^2.1.12: 153 | version "2.1.30" 154 | resolved "https://registry.npm.taobao.org/mime-types/download/mime-types-2.1.30.tgz?cache=0&sync_timestamp=1617340186693&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fmime-types%2Fdownload%2Fmime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" 155 | integrity sha1-bnvotMR5gl+F7WMmaV23P5MF1i0= 156 | dependencies: 157 | mime-db "1.47.0" 158 | 159 | ms@^2.1.1: 160 | version "2.1.2" 161 | resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 162 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 163 | 164 | node-fetch@^2.6.0: 165 | version "2.6.1" 166 | resolved "https://registry.npm.taobao.org/node-fetch/download/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" 167 | integrity sha1-BFvTI2Mfdu0uK1VXM5RBa2OaAFI= 168 | 169 | opencollective-postinstall@^2.0.2: 170 | version "2.0.2" 171 | resolved "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89" 172 | integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw== 173 | 174 | pseudomap@^1.0.2: 175 | version "1.0.2" 176 | resolved "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" 177 | integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= 178 | 179 | psl@^1.1.28: 180 | version "1.7.0" 181 | resolved "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c" 182 | integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ== 183 | 184 | punycode@^2.1.1: 185 | version "2.1.1" 186 | resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 187 | integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== 188 | 189 | regenerator-runtime@^0.13.3: 190 | version "0.13.5" 191 | resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" 192 | integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== 193 | 194 | resolve-url@^0.2.1: 195 | version "0.2.1" 196 | resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" 197 | integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= 198 | 199 | tesseract.js-core@^2.2.0: 200 | version "2.2.0" 201 | resolved "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-2.2.0.tgz#6ef78051272a381969fac3e45a226e85022cffef" 202 | integrity sha512-a8L+OJTbUipBsEDsJhDPlnLB0TY1MkTZqw5dqUwmiDSjUzwvU7HWLg/2+WDRulKUi4LE+7PnHlaBlW0k+V0U0w== 203 | 204 | tesseract.js@^2.1.1: 205 | version "2.1.1" 206 | resolved "https://registry.npmjs.org/tesseract.js/-/tesseract.js-2.1.1.tgz#5c50fc95542ce8d834cb952bfb75a8fc85f1441d" 207 | integrity sha512-utg0A8UzT1KwBvZf+UMGmM8LU6izeol6yIem0Z44+7Qqd/YWgRVQ99XOG18ApTOXX48lGE++PDwlcZYkv0ygRQ== 208 | dependencies: 209 | bmp-js "^0.1.0" 210 | file-type "^12.4.1" 211 | idb-keyval "^3.2.0" 212 | is-electron "^2.2.0" 213 | is-url "^1.2.4" 214 | node-fetch "^2.6.0" 215 | opencollective-postinstall "^2.0.2" 216 | regenerator-runtime "^0.13.3" 217 | resolve-url "^0.2.1" 218 | tesseract.js-core "^2.2.0" 219 | zlibjs "^0.3.1" 220 | 221 | tough-cookie@^2.3.3: 222 | version "2.5.0" 223 | resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" 224 | integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== 225 | dependencies: 226 | psl "^1.1.28" 227 | punycode "^2.1.1" 228 | 229 | typescript@^3.8.3: 230 | version "3.8.3" 231 | resolved "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" 232 | integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== 233 | 234 | which@^1.2.9: 235 | version "1.3.1" 236 | resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 237 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 238 | dependencies: 239 | isexe "^2.0.0" 240 | 241 | yallist@^2.1.2: 242 | version "2.1.2" 243 | resolved "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" 244 | integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= 245 | 246 | zlibjs@^0.3.1: 247 | version "0.3.1" 248 | resolved "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz#50197edb28a1c42ca659cc8b4e6a9ddd6d444554" 249 | integrity sha1-UBl+2yihxCymWcyLTmqd3W1ERVQ= 250 | --------------------------------------------------------------------------------