diff --git a/package.json b/package.json index 500fec9..d36b075 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,13 @@ "prepack": "pnpm build", "test": "node -r ts-node/register --test tests/*.test.ts" }, - "keywords": ["comcigan", "parser", "typescript", "school", "korean"], + "keywords": [ + "comcigan", + "parser", + "typescript", + "school", + "korean" + ], "author": "Starcea ", "license": "LGPL-3.0-or-later", "packageManager": "pnpm@9.4.0+sha512.f549b8a52c9d2b8536762f99c0722205efc5af913e77835dbccc3b0b0b2ca9e7dc8022b78062c17291c48e88749c70ce88eb5a74f1fa8c4bf5e18bb46c8bd83a", @@ -28,10 +34,12 @@ "typescript": "^5.5.2" }, "dependencies": { - "axios": "^1.7.2", - "iconv-lite": "^0.6.3" + "iconv-lite": "^0.6.3", + "undici": "^6.20.1" }, "release": { - "branches": ["release"] + "branches": [ + "release" + ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a84ab76..f9d80a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,12 +8,12 @@ importers: .: dependencies: - axios: - specifier: ^1.7.2 - version: 1.7.2 iconv-lite: specifier: ^0.6.3 version: 0.6.3 + undici: + specifier: ^6.20.1 + version: 6.20.1 devDependencies: '@biomejs/biome': specifier: ^1.8.3 @@ -327,16 +327,10 @@ packages: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axios@1.7.2: - resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -410,10 +404,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} @@ -498,10 +488,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -619,15 +605,6 @@ packages: resolution: {integrity: sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==} engines: {node: '>=18'} - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -635,10 +612,6 @@ packages: resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} engines: {node: '>=14'} - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - from2@2.3.0: resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} @@ -1012,14 +985,6 @@ packages: resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - mime@4.0.3: resolution: {integrity: sha512-KgUb15Oorc0NEKPbvfa0wRU+PItIEZmiv+pyAO2i0oTIVTJhlzMclU7w4RXWQrSOVH5ax/p/CkIO7KI4OyFJTQ==} engines: {node: '>=16'} @@ -1279,9 +1244,6 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -1579,6 +1541,10 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici@6.20.1: + resolution: {integrity: sha512-AjQF1QsmqfJys+LXfGTNum+qw4S88CojRInG/6t31W/1fk6G59s92bnAvGz5Cmur+kQv2SURXEvvudLmbrE8QA==} + engines: {node: '>=18.17'} + unicode-emoji-modifier-base@1.0.0: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} engines: {node: '>=4'} @@ -1983,20 +1949,10 @@ snapshots: is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 - asynckit@0.4.0: {} - available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 - axios@1.7.2: - dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - balanced-match@1.0.2: {} before-after-hook@3.0.2: {} @@ -2079,10 +2035,6 @@ snapshots: color-name@1.1.4: {} - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - compare-func@2.0.0: dependencies: array-ify: 1.0.0 @@ -2172,8 +2124,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - delayed-stream@1.0.0: {} - diff@4.0.2: {} dir-glob@3.0.1: @@ -2348,8 +2298,6 @@ snapshots: semver-regex: 4.0.5 super-regex: 1.0.0 - follow-redirects@1.15.6: {} - for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -2359,12 +2307,6 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - form-data@4.0.0: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - from2@2.3.0: dependencies: inherits: 2.0.4 @@ -2724,12 +2666,6 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - mime@4.0.3: {} mimic-fn@4.0.0: {} @@ -2884,8 +2820,6 @@ snapshots: proto-list@1.2.4: {} - proxy-from-env@1.1.0: {} - queue-microtask@1.2.3: {} rc@1.2.8: @@ -3260,6 +3194,8 @@ snapshots: undici-types@5.26.5: {} + undici@6.20.1: {} + unicode-emoji-modifier-base@1.0.0: {} unicorn-magic@0.1.0: {} diff --git a/src/client.ts b/src/client.ts index 353a741..579aad9 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,6 +1,6 @@ -import axios from 'axios' import { BASE_URL, USER_AGENT } from './constants' import DataManager from './data' +import HTTP from './http' import School from './models/School' import type { Timetable } from './models/Timetable' import { mergeMap } from './utils/array' @@ -8,24 +8,22 @@ import { encodeBase64, encodeEUCKR } from './utils/encode' import { parseResponse } from './utils/parse' export default class Comcigan { - private readonly rest = axios.create({ + private readonly http = new HTTP({ baseURL: BASE_URL, - headers: { - 'User-Agent': USER_AGENT, - }, + headers: { 'User-Agent': USER_AGENT }, }) - private readonly dataManager = new DataManager(this.rest) + private readonly dataManager = new DataManager(this.http) /** 학교를 검색합니다. */ async searchSchools(schoolName: string): Promise { const { mainRoute, searchRoute } = await this.dataManager.getData() - const res = await this.rest.get( - `${mainRoute}?${searchRoute}l${encodeEUCKR(schoolName)}`, - ) + const res = await this.http + .get(`${mainRoute}?${searchRoute}l${encodeEUCKR(schoolName)}`) + .then((res) => res.body.text()) const { 학교검색: data } = parseResponse<{ 학교검색: [number, string, string, number][] - }>(res.data) + }>(res) return data.map( ([regionCode, regionName, schoolName, schoolCode]) => @@ -48,10 +46,12 @@ export default class Comcigan { dayCode, subjectCode, } = await this.dataManager.getData() - const res = await this.rest.get( - `${mainRoute}_T?${encodeBase64(`${timetableRoute}_${schoolCode}_0_1`)}`, - ) - const data = parseResponse(res.data) + const res = await this.http + .get( + `${mainRoute}_T?${encodeBase64(`${timetableRoute}_${schoolCode}_0_1`)}`, + ) + .then((res) => res.body.text()) + const data = parseResponse(res) const teachers = data[`자료${teacherCode}`] as string[] const teachersLen = Math.floor(Math.log10(teachers.length - 1)) + 1 diff --git a/src/data.ts b/src/data.ts index cd21c06..f55abaa 100644 --- a/src/data.ts +++ b/src/data.ts @@ -1,6 +1,6 @@ -import type { AxiosInstance } from 'axios' import { decode } from 'iconv-lite' import { RegExes } from './constants' +import type HTTP from './http' interface Data { mainRoute: string @@ -18,13 +18,11 @@ export default class DataManager { private _lastFetchDate = 0 - constructor(private readonly rest: AxiosInstance) {} + constructor(private readonly http: HTTP) {} private async fetchData(): Promise { - const res = await this.rest.get('/st', { - responseType: 'arraybuffer', - }) - const data = decode(Buffer.from(res.data), 'euc-kr') + const res = await this.http.get('/st').then((res) => res.body.arrayBuffer()) + const data = decode(Buffer.from(res), 'euc-kr') const main = RegExes.MainRoute.exec(data) if (!main) throw new Error('Failed to fetch main route') diff --git a/src/http.ts b/src/http.ts new file mode 100644 index 0000000..3ee8579 --- /dev/null +++ b/src/http.ts @@ -0,0 +1,20 @@ +import { request } from 'undici' + +export default class HTTP { + private readonly baseURL: string + private readonly headers: Record + + constructor({ + baseURL, + headers, + }: { baseURL: string; headers: Record }) { + this.baseURL = baseURL + this.headers = headers + } + + async get(url: string) { + return request(`${new URL(url, this.baseURL)}`, { + headers: this.headers, + }) + } +}