diff --git a/.gitignore b/.gitignore index 88bb1ee..2226905 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# .env +.env + # compiled output /lib /node_modules diff --git a/README.md b/README.md index 049fabe..dea3912 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,6 @@ async function main() { 생년월일: '2001-01-01', 입영일: '2022-02-14', 전화번호: '01012341234', - - // 아래 값들은 현재 직접 뽑아야 함 - // TODO: 사이트에서 긁어오게 작업하기 - 생년월일Code: '08IyuIy6/tXS/vveGiNc+Q==', // birth - 입영부대TypeCode: '0000140001', // trainUnitTypeCd - 입영부대EduId: '14030', // trainUnitEduSeq - 훈련병Id: '', // traineeMgrSeq }, { 작성자: '장지훈', diff --git a/jest.config.js b/jest.config.js index c39c6b4..e17d5ab 100644 --- a/jest.config.js +++ b/jest.config.js @@ -5,6 +5,7 @@ module.exports = { transform: { '^.+\\.(t|j)s$': 'ts-jest', }, + setupFiles: ['dotenv/config'], collectCoverageFrom: ['**/*.(t|j)s', '!**/index.ts'], coveragePathIgnorePatterns: ['/index.ts'], moduleNameMapper: { diff --git a/package.json b/package.json index 05cffef..405747d 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@kiwi-lib/eslint-config": "^1", "@types/jest": "^27.4.1", "@types/node": "^17.0.21", + "dotenv": "^16.0.0", "jest": "^27.5.1", "rimraf": "^3.0.2", "ts-jest": "^27.1.3", diff --git a/src/core/string/extractInnerText.ts b/src/core/string/extractInnerText.ts new file mode 100644 index 0000000..3057fb7 --- /dev/null +++ b/src/core/string/extractInnerText.ts @@ -0,0 +1,25 @@ +export function extractInnerText( + target: string, + lefts: string[], + right: string, +): string { + let beforeStartIdx = -1; + let startIdx = -1; + for (const left of lefts) { + startIdx = target.indexOf(left, beforeStartIdx); + + if (startIdx < 0) { + break; + } + + startIdx += left.length; + beforeStartIdx = startIdx; + } + + const endIdx = target.indexOf(right, startIdx); + if (startIdx < 0 || endIdx < 0) { + return ''; + } + + return target.substring(startIdx, endIdx); +} diff --git a/src/core/string/extractInnerTexts.ts b/src/core/string/extractInnerTexts.ts new file mode 100644 index 0000000..3f608b8 --- /dev/null +++ b/src/core/string/extractInnerTexts.ts @@ -0,0 +1,24 @@ +export function extractInnerTexts( + target: string, + left: string, + right: string, +): string[] { + const extractedTexts = []; + let offset = 0; + + while (target.includes(left) && target.includes(right)) { + const startIdx = target.indexOf(left, offset); + const endIdx = target.indexOf(right, startIdx); + + if (startIdx === -1 || endIdx === -1) { + break; + } + + const extractedText = target.substring(startIdx + left.length, endIdx); + + extractedTexts.push(extractedText); + offset = endIdx + right.length; + } + + return extractedTexts; +} diff --git a/src/core/string/index.ts b/src/core/string/index.ts new file mode 100644 index 0000000..3ed63fd --- /dev/null +++ b/src/core/string/index.ts @@ -0,0 +1,2 @@ +export * from './extractInnerText'; +export * from './extractInnerTexts'; diff --git a/src/services/the-camp/requesters/fetch-soldiers/fetch-soldiers.requester.e2e.spec.ts b/src/services/the-camp/requesters/fetch-soldiers/fetch-soldiers.requester.e2e.spec.ts new file mode 100644 index 0000000..b9d6ced --- /dev/null +++ b/src/services/the-camp/requesters/fetch-soldiers/fetch-soldiers.requester.e2e.spec.ts @@ -0,0 +1,12 @@ +import { loginRequester } from '../login'; +import { fetchSoldiersRequester } from './fetch-soldiers.requester'; + +describe.skip('FetchSoldiersRequester e2e', () => { + it('성공', async () => { + const session = await loginRequester.request({ + id: '', + password: '', + }); + await fetchSoldiersRequester.request(session); + }); +}); diff --git a/src/services/the-camp/requesters/fetch-soldiers/fetch-soldiers.requester.ts b/src/services/the-camp/requesters/fetch-soldiers/fetch-soldiers.requester.ts new file mode 100644 index 0000000..b37d9e2 --- /dev/null +++ b/src/services/the-camp/requesters/fetch-soldiers/fetch-soldiers.requester.ts @@ -0,0 +1,44 @@ +import { TheCampSession } from '@common/types'; +import axios, { AxiosRequestConfig } from 'axios'; + +import { FetchSoldierRawInfo, parseSoldiers } from './parse-soldiers'; + +export class FetchSoldiersRequester { + constructor(private readonly parse = parseSoldiers) {} + + async request(session: TheCampSession): Promise { + const response = await axios.get( + 'https://www.thecamp.or.kr/eduUnitCafe/viewEduUnitCafeMain.do', + this.createOptions(session), + ); + + return this.parse(response); + } + + private createOptions(session: TheCampSession): AxiosRequestConfig { + return { + headers: { + Accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', + 'User-Agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.70 Whale/3.13.131.27 Safari/537.36', + Host: 'www.thecamp.or.kr', + Origin: 'https://www.thecamp.or.kr', + Referer: 'https://www.thecamp.or.kr/eduUnitCafe/viewEduUnitCafeMain.do', + 'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Whale";v="3"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'Sec-Fetch-Site': 'same-origin', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Dest': 'empty', + Cookie: session.cookies + .map(({ key, value }) => `${key}=${value}`) + .join('; '), + }, + }; + } +} + +export const fetchSoldiersRequester = new FetchSoldiersRequester(); diff --git a/src/services/the-camp/requesters/fetch-soldiers/index.ts b/src/services/the-camp/requesters/fetch-soldiers/index.ts new file mode 100644 index 0000000..d7f5954 --- /dev/null +++ b/src/services/the-camp/requesters/fetch-soldiers/index.ts @@ -0,0 +1,2 @@ +export * from './fetch-soldiers.requester'; +export { FetchSoldierRawInfo as FetchSoldierRawInfo } from './parse-soldiers'; diff --git a/src/services/the-camp/requesters/fetch-soldiers/parse-soldiers.spec.ts b/src/services/the-camp/requesters/fetch-soldiers/parse-soldiers.spec.ts new file mode 100644 index 0000000..22a6143 --- /dev/null +++ b/src/services/the-camp/requesters/fetch-soldiers/parse-soldiers.spec.ts @@ -0,0 +1,61 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; + +import { parseSoldiers } from './parse-soldiers'; + +describe('parseSoldiers', () => { + it('성공', () => { + const data = readFileSync(join(__dirname, 'test/성공.txt'), 'utf-8'); + + expect(parseSoldiers({ data } as any)).toEqual([ + { + 입영부대Code: '20020191700', + 입영부대EduId: '6506', + 군인Code: '6142000', + 입영일: '20210302', + 생년월일: '20010928', + 이름: '김민석', + }, + { + 입영부대Code: '20121190200', + 입영부대EduId: '6611', + 군인Code: '3051000', + 입영일: '20210329', + 생년월일: '20010822', + 이름: '이예건', + }, + { + 입영부대Code: '20220280600', + 입영부대EduId: '6629', + 군인Code: '2050000', + 입영일: '20210406', + 생년월일: '20010814', + 이름: '이지원', + }, + { + 입영부대Code: '20020191700', + 입영부대EduId: '6698', + 군인Code: '6142000', + 입영일: '20210426', + 생년월일: '20010405', + 이름: '김민수', + }, + { + 입영부대Code: '20220280100', + 입영부대EduId: '12117', + 군인Code: '2031000', + 입영일: '20220125', + 생년월일: '20011020', + 이름: '김명훈', + }, + { + 입영부대Code: '20020191700', + 입영부대EduId: '14030', + 군인Code: '6142000', + 입영일: '20220214', + 생년월일: '20011126', + 이름: '이상택', + }, + ]); + }); +}); diff --git a/src/services/the-camp/requesters/fetch-soldiers/parse-soldiers.ts b/src/services/the-camp/requesters/fetch-soldiers/parse-soldiers.ts new file mode 100644 index 0000000..427e29d --- /dev/null +++ b/src/services/the-camp/requesters/fetch-soldiers/parse-soldiers.ts @@ -0,0 +1,47 @@ +import { extractInnerTexts } from '@core/string'; +import { AxiosResponse } from 'axios'; + +export interface FetchSoldierRawInfo { + 입영부대Code: string; // trainUnitCd + 입영부대EduId: string; // trainUnitEduSeq + 군인Code: string; // unitCd + 입영일: string; // enterDate + 생년월일: string; // birthDay + 이름: string; // name +} + +export function parseSoldiers({ + data, +}: AxiosResponse): FetchSoldierRawInfo[] { + const fnCafeCreateCheckLines = extractInnerTexts( + data, + 'javascript:fn_cafeMainLink2(', + '보고싶은군인', + ); + return fnCafeCreateCheckLines.map(parseSoldier); +} + +// fnLine: fn_cafeCreateCheck(...) +function parseSoldier(fnLine: string): FetchSoldierRawInfo { + const [fn_cafeMainLink2, fn_findArmyArrngmtResult] = fnLine.split( + 'javascript:fn_findArmyArrngmtResult(', + ); + const [입영부대Code, 입영부대EduId] = fn_cafeMainLink2 + .split(');')[0] + .replaceAll("'", '') + .split(','); + + const [군인Code, 입영일, 생년월일, 이름] = fn_findArmyArrngmtResult + .split(');')[0] + .replaceAll("'", '') + .split(','); + + return { + 입영부대Code, + 입영부대EduId, + 군인Code, + 입영일: 입영일.replaceAll('.', ''), + 생년월일, + 이름, + }; +} diff --git "a/src/services/the-camp/requesters/fetch-soldiers/test/\354\204\261\352\263\265.txt" "b/src/services/the-camp/requesters/fetch-soldiers/test/\354\204\261\352\263\265.txt" new file mode 100644 index 0000000..56314a0 --- /dev/null +++ "b/src/services/the-camp/requesters/fetch-soldiers/test/\354\204\261\352\263\265.txt" @@ -0,0 +1,3412 @@ + + + + +메인 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + +
+
+ + + + + + +
+ +
+ +
+ + +
+
+ + + + + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 183 + + + +

+
2022-09-01
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 김민석 + + + + + + 상병 4호봉 + + + + + +
+
+ + 입영부대 육군훈련소-논산 + + + + 입영 52주차 + +
+
+ + + + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 210 + + + +

+
2022-09-28
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이예건 + + + + + + 상병 4호봉 + + + + + +
+
+ + 입영부대 51사단-화성 + + + + 입영 48주차 + +
+
+ + + + +
+ +
+ 경기 화성시 매송면 + -0.3℃ + 69% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 217 + + + +

+
2022-10-05
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이지원 + + + + + + 상병 3호봉 + + + + + +
+
+ + 입영부대 50사단-대구 + + + + 입영 47주차 + +
+
+ + + + +
+ +
+ 대구 북구 + 5.7℃ + 56% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 237 + + + +

+
2022-10-25
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 김민수 + + + + + + 상병 3호봉 + + + + + +
+
+ + 입영부대 육군훈련소-논산 + + + + 입영 44주차 + +
+
+ + + + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 273 + + + +

+
2022-11-30
+ + + + +
+
+ + +
+ +
+ + +
+
+ + + + +
+ + 육군 + + + 김기현 + + 상병 + + + +
+
+ + 병사 + + + 소속부대 육군훈련소 + +
+
+ + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 322 + + + +

+
2023-01-18
+ + + + +
+
+ + +
+ +
+ + +
+
+ + + + +
+ + 육군 + + + 김태수 + + 일병 + + + +
+
+ + 병사 + + + 소속부대 21사단 + +
+
+ + +
+ +
+ 강원 양구군 남면 + -3.9℃ + 87% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

수료일 + + + D + - + 6 + + + +

+
2023-07-24
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 김명훈 + + + + + + 훈련병 + + + + + +
+
+ + 입영부대 31사단-광주 + + + 현역 / 상근 22-2기(1. 25. 입영) + + + 입영 5주차 + +
+
+ + + + +
+ +
+ 광주 북구 + 2.5℃ + 74% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 529 + + + +

+
2023-08-13
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상택 + + + + + + 훈련병 + + + + + +
+
+ + 입영부대 육군훈련소-논산 + + + + 입영 2주차 + +
+
+ + + + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 529 + + + +

+
2023-08-13
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상택 + + + + + + 훈련병 + + + + + +
+
+ + + + 입영 2주차 + +
+
+ + + + +
+ +
+ + + % + + +
+
+ + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 529 + + + +

+
2023-08-13
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상태 + + + + + + 훈련병 + + + + + +
+
+ + + + 입영 2주차 + +
+
+ + + + +
+ +
+ + + % + + +
+
+ + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 528 + + + +

+
2023-08-12
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상택 + + + + + + 훈련병 + + + + + +
+
+ + 입영부대 육군훈련소-논산 + + + + 입영 2주차 + +
+
+ + + + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 529 + + + +

+
2023-08-13
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상택 + + + + + + 훈련병 + + + + + +
+
+ + + + 입영 2주차 + +
+
+ + + + +
+ +
+ + + % + + +
+
+ + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 529 + + + +

+
2023-08-13
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상가 + + + + + + 훈련병 + + + + + +
+
+ + 입영부대 육군훈련소-논산 + + + + 입영 2주차 + +
+
+ + + + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 529 + + + +

+
2023-08-13
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상나 + + + + + + 훈련병 + + + + + +
+
+ + 입영부대 육군훈련소-논산 + + + + 입영 2주차 + +
+
+ + + + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 529 + + + +

+
2023-08-13
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상다 + + + + + + 훈련병 + + + + + +
+
+ + 입영부대 육군훈련소-논산 + + + + 입영 2주차 + +
+
+ + + + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 529 + + + +

+
2023-08-13
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상라 + + + + + + 훈련병 + + + + + +
+
+ + 입영부대 육군훈련소-논산 + + + + 입영 2주차 + +
+
+ + + + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 529 + + + +

+
2023-08-13
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상마 + + + + + + 훈련병 + + + + + +
+
+ + 입영부대 육군훈련소-논산 + + + + 입영 2주차 + +
+
+ + + + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + +
+ + + + + + + +
+
+ +
+ +
+ +

전역일 + + + D + - + 529 + + + +

+
2023-08-13
+ + + + +
+
+ + +
+ +
+ + +
+
+ + +
+ + 육군 + + + 이상바 + + + + + + 훈련병 + + + + + +
+
+ + 입영부대 육군훈련소-논산 + + + + 입영 2주차 + +
+
+ + + + +
+ +
+ 충남 논산시 연무읍 + -1.7℃ + 95% + + +
+
+ 2022-03-02 00:00 + 제공:기상청 +
+ +
+
+ + + + + +
+ +
+ + + + + + + + + + +
+ +
+ + + + + + +
+ + +
+
지금 보고 싶은 군인을 등록하고
소식을 나눠보세요!
+ +
+ + 추가하기 +
+ + +
+ +
+
+
+
+
+ +
+ + + + + + +
+
+

공지사항

+ 더보기 +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
분류제목작성자등록일
더캠프공지 + 더캠프 약관 변경 사전 안내 + 더캠프2021.10.22
더캠프공지 + 더캠프 게시물 관리 규정 개정에 따른 사전 안내 + 더캠프2021.09.23
더캠프공지 + 더캠프 개인정보처리방침, 청소년보호정책 개정에 따른 사전 안내 + 더캠프2021.08.17
더캠프공지 + 더캠프 토크게시판 불펌방지 기능 업데이트 21.01.26 + 더캠프2021.01.26
더캠프공지 + 입영부대 변경에 따른 카페가입 방법 안내 (5사단→22사단) + 더캠프2020.12.11
+
+ + +
+ + + + + + +
+

혜택

+ + +
+ + + +
+

진행중 이벤트

+
    + +
+
+ +
+ +
+
+ + + + + + + + + + + + + + diff --git a/src/services/the-camp/requesters/fetch-unit-soldiers/fetch-unit-soldiers.requester.e2e.spec.ts b/src/services/the-camp/requesters/fetch-unit-soldiers/fetch-unit-soldiers.requester.e2e.spec.ts new file mode 100644 index 0000000..00afd7e --- /dev/null +++ b/src/services/the-camp/requesters/fetch-unit-soldiers/fetch-unit-soldiers.requester.e2e.spec.ts @@ -0,0 +1,19 @@ +import { 입영부대CodeMap } from '../../../../core/types'; +import { loginRequester } from '../login'; +import { fetchUnitSoldiersRequester } from './fetch-unit-soldiers.requester'; + +describe.skip('FetchUnitSoldiersRequester e2e', () => { + it('성공', async () => { + const session = await loginRequester.request({ + id: process.env.ID!, + password: process.env.PASSWORD!, + }); + await fetchUnitSoldiersRequester.request( + { + 입영부대Code: 입영부대CodeMap['육군훈련소-논산'], + 입영부대EduId: '14030', + }, + session, + ); + }); +}); diff --git a/src/services/the-camp/requesters/fetch-unit-soldiers/fetch-unit-soldiers.requester.ts b/src/services/the-camp/requesters/fetch-unit-soldiers/fetch-unit-soldiers.requester.ts new file mode 100644 index 0000000..bf21e1e --- /dev/null +++ b/src/services/the-camp/requesters/fetch-unit-soldiers/fetch-unit-soldiers.requester.ts @@ -0,0 +1,72 @@ +import { TheCampSession } from '@common/types'; +import { Parameter } from '@core/http'; +import axios, { AxiosRequestConfig } from 'axios'; + +import { + FetchUnitSoldierRawInfo, + parseUnitSoldiers, +} from './parse-unit-soldiers'; + +export class FetchUnitSoldiersRequester { + constructor(private readonly parse = parseUnitSoldiers) {} + + async request( + dto: FetchSoldierDto, + session: TheCampSession, + ): Promise { + const response = await axios.post( + 'https://www.thecamp.or.kr/consolLetter/viewConsolLetterMain.do', + this.createPayload(dto), + this.createOptions(session), + ); + + return this.parse(response); + } + + private createPayload({ + 입영부대Code, + 입영부대EduId, + }: FetchSoldierDto): string { + return new Parameter({ + trainUnitCd: 입영부대Code, + trainUnitEduSeq: 입영부대EduId, + divType: '1', + noticeMgrSeq: '', + trainUnitTypeCd: '', + trainCafeContentSeq: '', + enterPageType: 'main', + }).toString(); + } + + private createOptions(session: TheCampSession): AxiosRequestConfig { + return { + headers: { + Accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', + 'Content-Type': 'application/x-www-form-urlencoded', + 'User-Agent': + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.70 Whale/3.13.131.27 Safari/537.36', + Host: 'www.thecamp.or.kr', + Origin: 'https://www.thecamp.or.kr', + Referer: 'https://www.thecamp.or.kr/eduUnitCafe/viewEduUnitCafeMain.do', + 'sec-ch-ua': '" Not A;Brand";v="99", "Chromium";v="98", "Whale";v="3"', + 'sec-ch-ua-mobile': '?0', + 'sec-ch-ua-platform': '"macOS"', + 'Sec-Fetch-Site': 'same-origin', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Dest': 'empty', + Cookie: session.cookies + .map(({ key, value }) => `${key}=${value}`) + .join('; '), + }, + }; + } +} +export interface FetchSoldierDto { + 입영부대Code: string; + 입영부대EduId: string; +} + +export const fetchUnitSoldiersRequester = new FetchUnitSoldiersRequester(); diff --git a/src/services/the-camp/requesters/fetch-unit-soldiers/index.ts b/src/services/the-camp/requesters/fetch-unit-soldiers/index.ts new file mode 100644 index 0000000..21ec33c --- /dev/null +++ b/src/services/the-camp/requesters/fetch-unit-soldiers/index.ts @@ -0,0 +1,2 @@ +export * from './fetch-unit-soldiers.requester'; +export { FetchUnitSoldierRawInfo } from './parse-unit-soldiers'; diff --git a/src/services/the-camp/requesters/fetch-unit-soldiers/parse-unit-soldiers.spec.ts b/src/services/the-camp/requesters/fetch-unit-soldiers/parse-unit-soldiers.spec.ts new file mode 100644 index 0000000..d4f9f52 --- /dev/null +++ b/src/services/the-camp/requesters/fetch-unit-soldiers/parse-unit-soldiers.spec.ts @@ -0,0 +1,14 @@ +import { readFileSync } from 'fs'; +import { join } from 'path'; + +import { parseUnitSoldiers } from './parse-unit-soldiers'; + +describe('parseUnitSoldiers', () => { + it('성공', () => { + const data = readFileSync(join(__dirname, 'test/성공.txt'), 'utf-8'); + + expect(parseUnitSoldiers({ data } as any)).toEqual({ + 훈련병Id: '1561180', + }); + }); +}); diff --git a/src/services/the-camp/requesters/fetch-unit-soldiers/parse-unit-soldiers.ts b/src/services/the-camp/requesters/fetch-unit-soldiers/parse-unit-soldiers.ts new file mode 100644 index 0000000..2cff72e --- /dev/null +++ b/src/services/the-camp/requesters/fetch-unit-soldiers/parse-unit-soldiers.ts @@ -0,0 +1,26 @@ +import { AxiosResponse } from 'axios'; + +import { extractInnerText } from '../../../../core/string'; + +export interface FetchUnitSoldierRawInfo { + // 생년월일: string; + // 이름: string; + 훈련병Id: string; +} + +// 주의: 한 부대에 여러 인편 케이스 못찾음 +export function parseUnitSoldiers( + response: AxiosResponse, +): FetchUnitSoldierRawInfo[] { + const 훈련병Id = extractInnerText( + response.data, + ['if(', '> 0) {', "traineeMgrSeq = '"], + "';", + ); + + return [ + { + 훈련병Id, + }, + ]; +} diff --git "a/src/services/the-camp/requesters/fetch-unit-soldiers/test/\354\204\261\352\263\265.txt" "b/src/services/the-camp/requesters/fetch-unit-soldiers/test/\354\204\261\352\263\265.txt" new file mode 100644 index 0000000..4cda646 --- /dev/null +++ "b/src/services/the-camp/requesters/fetch-unit-soldiers/test/\354\204\261\352\263\265.txt" @@ -0,0 +1,1153 @@ + + + + + +위문편지 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + +
+ + + + + + + +
+
+
2교육대 6중대
+
    +
  • + + 입영일 + 2022.02.14 +
  • +
  • + + 수료일 + 2022.03.21 +
  • +
  • + 멤버 + 1,326 +
  • +
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + + +
+ + + +
+ + + + + +
+
+ 위문편지함 + +
+ + + + + +
+ +
+ +
+ + + +
+
+ +
+ +
+ +
+ + +
+
+ + + + + + + + + + + + + + + + + +
제목등록일상태
+
+ +
+ + + +
+
+ +
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + diff --git a/src/services/the-camp/requesters/index.ts b/src/services/the-camp/requesters/index.ts index abb8013..4c9287f 100644 --- a/src/services/the-camp/requesters/index.ts +++ b/src/services/the-camp/requesters/index.ts @@ -1,3 +1,5 @@ +export * from './fetch-soldiers'; +export * from './fetch-unit-soldiers'; export * from './login'; export * from './register-cafe'; export * from './register-soldier'; diff --git a/src/services/the-camp/requesters/register-cafe/register-cafe.requester.e2e.spec.ts b/src/services/the-camp/requesters/register-cafe/register-cafe.requester.e2e.spec.ts index a520cf8..6ac7cd9 100644 --- a/src/services/the-camp/requesters/register-cafe/register-cafe.requester.e2e.spec.ts +++ b/src/services/the-camp/requesters/register-cafe/register-cafe.requester.e2e.spec.ts @@ -10,7 +10,7 @@ describe.skip('RegisterCafeRequester e2e', () => { await registerCafeRequester.request( { 이름: '홍길동', - 생년월일Code: '08IyuIy6/tXS/vveGiNc+Q==', + 생년월일: '2001-01-01', 입영일: '2022-02-14', 군종: '육군', 입영부대: '육군훈련소-논산', diff --git a/src/services/the-camp/requesters/register-cafe/register-cafe.requester.ts b/src/services/the-camp/requesters/register-cafe/register-cafe.requester.ts index 441c6be..c3ad0e2 100644 --- a/src/services/the-camp/requesters/register-cafe/register-cafe.requester.ts +++ b/src/services/the-camp/requesters/register-cafe/register-cafe.requester.ts @@ -31,14 +31,14 @@ export class RegisterCafeRequester { 관계, 입영부대, 입영일, - 생년월일Code, + 생년월일, 입영부대TypeCode, }: RegisterCafeDto): string { return new Parameter({ regOrder: '', name: 이름, enterDate: 입영일.replaceAll('-', ''), - birth: 생년월일Code, + birth: 생년월일.replaceAll('-', ''), trainUnitTypeCd: 입영부대TypeCode, trainUnitCd: 입영부대CodeMap[입영부대], grpCd: 군종CodeMap[군종], @@ -80,7 +80,7 @@ export interface RegisterCafeDto { // yyyy-MM-dd 입영일: string; - 생년월일Code: string; + 생년월일: string; 입영부대TypeCode: string; } diff --git a/src/services/the-camp/the-camp.service.e2e.spec.ts b/src/services/the-camp/the-camp.service.e2e.spec.ts index 0fe2dda..34f8984 100644 --- a/src/services/the-camp/the-camp.service.e2e.spec.ts +++ b/src/services/the-camp/the-camp.service.e2e.spec.ts @@ -22,7 +22,7 @@ describe.skip('TheCampService e2e', () => { await theCampService.registerCafe( { 이름: '홍길동', - 생년월일Code: '08IyuIy6/tXS/vveGiNc+Q==', + 생년월일: '2001-01-01', 입영일: '2022-02-14', 군종: '육군', 입영부대: '육군훈련소-논산', diff --git a/src/services/the-camp/the-camp.service.ts b/src/services/the-camp/the-camp.service.ts index b7bdec1..1445bd6 100644 --- a/src/services/the-camp/the-camp.service.ts +++ b/src/services/the-camp/the-camp.service.ts @@ -2,6 +2,11 @@ import { TheCampSession } from '@common/types'; import { Credential } from '@core/types'; import { + FetchSoldierDto, + FetchSoldierRawInfo, + fetchSoldiersRequester as _fetchSoldiersRequester, + FetchUnitSoldierRawInfo, + fetchUnitSoldiersRequester as _fetchSoldierRequester, loginRequester as _loginRequester, RegisterCafeDto, registerCafeRequester as _registerCafeRequester, @@ -16,6 +21,8 @@ export class TheCampService { private readonly loginRequester = _loginRequester, private readonly registerSoldierRequester = _registerSoldierRequester, private readonly registerCafeRequester = _registerCafeRequester, + private readonly fetchSoldiersRequester = _fetchSoldiersRequester, + private readonly fetchSoldierRequester = _fetchSoldierRequester, private readonly sendLetterRequester = _sendLetterRequester, ) {} @@ -40,6 +47,20 @@ export class TheCampService { await this.registerCafeRequester.request(dto, session); } + async fetchSoldiers(session: TheCampSession): Promise { + return await this.fetchSoldiersRequester.request(session); + } + + // 주의: 한 부대에 여러 인편 케이스 못찾음 + async fetchUnitSoldiers( + dto: FetchSoldierDto, + session: TheCampSession, + ): Promise { + // TODO: 한 부대에 여러 인편 케이스 찾아서 대응하기 + const soldiers = await this.fetchSoldierRequester.request(dto, session); + return soldiers[0]; + } + async sendLetter(dto: SendLetterDto, session: TheCampSession): Promise { await this.sendLetterRequester.request(dto, session); } diff --git a/yarn.lock b/yarn.lock index 8886c36..091fdff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1260,6 +1260,11 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" +dotenv@^16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411" + integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q== + electron-to-chromium@^1.4.71: version "1.4.75" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.75.tgz#d1ad9bb46f2f1bf432118c2be21d27ffeae82fdd"