Table of Contents
νλ ¨μμμ κ°μ₯ λ΅λ΅ν κ²μ μΈλΆμμ λ¨μ μ΄λΌλ λ§μ μμ£Ό λ€μλ€. κ·Έλμ μ λν μΉκ΅¬λ€μκ² λ΄μ€λ μ½μ거리λ₯Ό μμ½ν΄μ μΈν°λ· νΈμ§λ‘ 보λ΄μ£Όκ³€ νλλ°, λ§€μΌ μ£Όμ λ΄μ€λ₯Ό μμ½νλ κ²μ μμΈλ‘ μμ΄ λ§μ΄ κ°λ μΌμ΄μλ€. 무μλ³΄λ€ μΈν°λ· νΈμ§ λ°μ‘μ μ§μνλ μλΉμ€μΈ λ μΊ νμ μΈν°νμ΄μ€κ° λ무 λΆνΈν΄μ μ¬μ©νκΈ° μ½μ§ μμλ€.
κ·Έλ¬λ μ€ λμκ²λ κ΅°μ¬κ΅μ‘ μμ§ ν΅μ§μκ° μκ³ , 4μ£Όκ°μ κ³ λ¦½μ μμ μ€λν λ¬Έμ μλ€. κ·Έλ κ² μ μλ₯Ό μλ μΌμμΌ, λ§€μΌ μΈν°λ· νΈμ§λ‘ λ΄μ€λ₯Ό 보λ΄μ£Όλ νλ‘κ·Έλ¨μ λ§λ€κ² λλ€.
λ μΊ ν λΌμ΄λΈλ¬λ¦¬
νλ ¨λ³μκ² νΈμ§λ₯Ό 보λ΄λ €λ©΄ λ°λμ λ μΊ νλ₯Ό κ±°μ³μΌ νλ€. λ μΊ νκ° μ€ν APIλ₯Ό μ§μνλ©΄ μ°Έ μ’κ² μ§λ§ μνκΉκ²λ κ·Έλ μ§ μμλ€. κ²°κ΅ ν΄λΌμ΄μΈνΈμμ HTTP μμ²μ ꡬμ±ν΄ μ§μ λ μΊ ν μλ²λ‘ 보λ΄λ λ°©μμΌλ‘ ꡬννλ€.
νλ‘μ νΈ κ΅¬μ‘°λ κ°λ¨ν κ³ννλ€. src
λλ ν 리 νμμ models
λλ ν 리μ κ°μ’
μΈν°νμ΄μ€λ₯Ό λκ³ , services
λλ ν 리μλ μ€μ μμ²μ 보λ΄κ³ κ°μ μ»λ ν¨μλ€μ λͺ¨μλ€. utils
λλ ν 리μλ μ¬λ¬ μλΉμ€μμ λ°λ³΅μ μΌλ‘ μ¬μ©λλ ν¨μλ₯Ό λκΈ°λ‘ νλ€.
.
+- examples
+- src
| +- models
| +- services
| +- utils
+- test
ꡬ체μ μΌλ‘ μ΄λλ‘ μμ²μ 보λ΄μΌ νλμ§ μμλ΄κΈ° μν΄ νλ ¨μμ μλ μ§μΈ(μ ννλ μΉκ΅¬μ νκ΅ νλ°°μλ€.)μκ² μΈν°λ· νΈμ§λ₯Ό λ°μ‘νκ³ λΈλΌμ°μ μ κ°λ°μ λꡬμμ λ€νΈμν¬ λ‘κ·Έλ₯Ό νμΈνλ€.
https://www.thecamp.or.kr/pcws/message/letter/insert.do
μ μμ²μ 보λ΄λ©΄ λλ€λ κ²μ μκ² λλ€. μμ² λ³Έλ¬Έμλ unit_code
, group_id
λ± μ μ μλ ν€λ€μ΄ μμκ³ , ν¬μ€νΈλ§¨μΌλ‘ ν
μ€νΈν μμ²μλ λ‘κ·ΈμΈ μ 보λ₯Ό μ°Ύμ μ μλ€λ μλ΅μ΄ λμμλ€.
λ‘κ·ΈμΈμ λν μλ΅ ν€λλ₯Ό μ΄ν΄λ³΄λ JSESSIONID
μ SCOUTER
μΏ ν€κ° μμλ€. HTTPλ 무μν(Stateless) νλ‘ν μ½μ΄κΈ° λλ¬Έμ λ
립μ μΈ μμ μμ²κ³Ό μλ΅μΌλ‘ μ°κ²°μ΄ λκΈ΄λ€.[1] λ‘κ·ΈμΈν μ¬μ©μλ₯Ό μλ³νκΈ° μν΄μλ μΈμ
μ 보λ₯Ό μ μ§ν΄μΌ νλλ°, ν°μΊ£ μλ²λ μ΄λ₯Ό μν΄ JSESSIONID
μ SCOUTER
μΏ ν€λ₯Ό μ¬μ©νλ€. μΏ ν€λ ν΄λΌμ΄μΈνΈλ¨μ μ μ₯λλ λ°μ΄ν°μ΄κΈ° λλ¬Έμ κ°μ μ μ§ν μ μμΌλ©°, μμ² ν€λμ μΏ ν€λ₯Ό ν¬ν¨νλ©΄ μΈμ
μ μλ³νλ κ²μ΄ κ°λ₯νλ€.
μλ²μΈ‘μμ μμ²μ λΉλκΈ°μ μΌλ‘ μ²λ¦¬νκΈ° λλ¬Έμ request-promiseλ₯Ό μ¬μ©νμ¬ μΈμ μΏ ν€λ₯Ό μ»μ΄μ€λ λ‘κ·ΈμΈ ν¨μλ₯Ό μμ±νλ€. μ΄λλ λ¨μνκ² μΏ ν€λ₯Ό λ°ννλ κ±Έλ‘ λλλλ°, μ§κΈ μκ°ν΄λ³΄λ©΄ μΈμ μΈμ€ν΄μ€μ νλ‘νΌν°λ‘ ν λΉν΄μ λ€λ₯Έ μλΉμ€μμ λ°λ‘ μ¬μ©νλλ‘ λ§λ€λ©΄ λ νΈνμ κ² κ°λ€.
interface Cookie {
scouter: string;
jsessionid: string;
}
async function login(id: string, password: string) {
let result: Cookie | null = null;
const options = {
uri: buildRequestUrl('common/login.do'),
method: 'POST',
json: true,
body: {
'user-id': id,
'user-pwd': password,
subsType: 1,
},
};
await requestPromise.post(options, (err, res, body) => {
// ...
});
return result as Cookie;
}
μ΄μ΄μ μ¬μ©μκ° κ°μ ν μΉ΄ν μ€μμ νΈμ§λ₯Ό λ°μ νλ ¨λ³μ΄ μν μΉ΄νλ₯Ό μ°ΎμμΌνλ€. λ§μ½ νλ ¨λ³μ΄ 25μ°λ 5μ€λμ μν΄μλ€λ©΄ λ μΊ νμμ 25μ°λ 5μ€λ μΉ΄νμ κ°μ νκ³ , μ΄ μΉ΄νμμ νΈμ§λ₯Ό 보λ΄λ λ°©μμ΄λ€.
μΉ΄ν 리μ€νΈ μμ² ν€λλ₯Ό ν΅ν΄ μμ λ³Έ unit_code
μ group_id
λ κ°κ° μ°λ/μ¬λ¨ μλ³ μ½λ, μΉ΄ν μμ΄λλΌλ κ²μ μκ² λλ€. κ·Έλ¦¬κ³ μμ κ°μ λ°©μμΌλ‘ μΈν°νμ΄μ€μ ν¨μλ₯Ό λ§λ€μλ€. ν€λμ λ‘κ·ΈμΈμΌλ‘ μ»μ μΈμ
μΏ ν€λ₯Ό λ΄μ μμ²μ 보λ΄λ©΄ μ¬μ©μκ° κ°μ
ν μΉ΄ν 리μ€νΈλ₯Ό κ°μ Έμ¨λ€. νλ ¨λ³μ μμκ³Ό μ
μ λ μ§λ₯Ό νλΌλ―Έν°λ‘ λ겨주면 ν΄λΉ νλ ¨λ³μ΄ μν μΉ΄νλ§ νμν΄ λ°ννλ€.
interface Group {
unitName: string; // μ°λ/μ¬λ¨ μ΄λ¦
fullName: string; // μΉ΄ν μ 체 μ΄λ¦
enterDate: string; // νλ ¨λ³ μ
μ λ μ§ (YYYYMMDD)
groupId: string; // μΉ΄ν μλ³ μ½λ
groupName: string; // μΉ΄ν μ΄λ¦
groupImage: string; // μΉ΄ν λν μ΄λ―Έμ§
accessDate: string; // μμ² λ μ§
unitCode: string; // μ°λ/μ¬λ¨ μλ³ μ½λ
unitType: number; // μ‘κ΅°νλ ¨μ(1)/μ¬λ¨μ κ΅λ(2) μ¬λΆ
grade: number;
}
async function fetchGroups(cookies: Cookie, unitName?: string, enterDate?: string) {
// ...
return result as Group[];
}
μλ΅μ΄ μ€λ€μ΄ν¬ μΌμ΄μ€(snake_case)λ‘ λ¨μ΄μ§μ§λ§ μ½λμ μΊλ© μΌμ΄μ€(camelCase)λ₯Ό ν¬κΈ°νκ³ μΆμ§ μμλ€. λλ¬Έμ μλ΅μ μν λ³λμ μΈν°νμ΄μ€λ₯Ό λ§λ€μλλ°, μμ΄ λ§μ΄ κ°μ§λ μμ§λ§ μλͺ¨μ μΈ μΌμ΄μλ€. λκ° λ§€νν΄μ£Όλ ν΄μ΄ μμΌλ©΄ μ’κ² λ€λ μκ°μ΄ λ€μλ€.
interface GroupResponse {
unit_name: string;
full_name: string;
enter_date: string;
group_id: string;
group_name: string;
group_image: string;
access_date: string;
unit_code: string;
unit_type: number;
grade: number;
}
μ€μ νΈμ§ μ μ‘μ κ°λ¨νλ€. νλ ¨λ³μ μ΄λ¦, μλ μμΌ, νλ ¨λ³κ³Όμ κ΄κ³ μ 보μ ν¨κ» μμ μ»μ μΉ΄ν μ 보λ€μ λ΄μ μμ²μ 보λ΄λ©΄ μΈν°λ· νΈμ§κ° λ°μ‘λλ€.
μ΄λ κ² the-camp-libλ₯Ό 'μκΈ°μ λͺ©μ λ¬μ±μ μν μ΅μνμ μμ€βκΉμ§ λ§λ€μ΄ npm ν¨ν€μ§λ‘ λ°°ν¬νλ€.
νλ ¨λ³μ μν λ°μΌλ¦¬ λ΄μ€
μΈν°λ· νΈμ§ λ΄μ©μ λ€μλ΄μ€μ RSSλ₯Ό μ΄μ©ν΄ ꡬμ±νλ€. μΈν°λ· νΈμ§μ κΈμ μ νμ΄ 2000μμ΄κΈ° λλ¬Έμ μ΅λν κ²½μ μ μΌλ‘ λ΄μ©μ ꡬμ±ν΄μΌ νλ€. λ¨Όμ κΈ°μ¬μ 첫 λ¬Έμ₯λ§ μλΌλλ€.
content = content.slice(0, content.indexOf('λ€.') + 1);
κ·Έλ¦¬κ³ κΈ°μ¬ μμ λΆλ λ°μ΄λΌμΈκ³Ό λΆνμν 문ꡬλ₯Ό μ§μ λ€.
content = content.replace(/^(\*κ·Έλ¦Ό\d\*)?(\(|\[|γ)\s?.*=.*\s?(\)|\]|γ)\s?/, '')
.replace(/^[κ°-ν£]{2,4}\s(κΈ°μ|νΉνμ)\s=\s/, '');
npm install the-camp-lib
μΌλ‘ μμ λ§λ ν¨ν€μ§λ₯Ό μ€μΉνκ³ , μ€μ νΈμ§λ₯Ό 보λ΄λ μ½λλ₯Ό μμ±νλ€.
async function setMessage() {
// ...
}
(async () => {
// ...
const cookie = await thecamp.login(id, password);
const [group] = await thecamp.fetchGroups(cookie, unitName, enterDate);
const trainee = {
traineeName,
unitCode: group.unitCode,
groupId: group.groupId,
relationship: thecamp.Relationship.FATHER,
};
const date = new Date();
const message = {
title: `${date.getMonth()}μ ${date.getDate()}μΌ (${date.getHours()}μ ${date.getMinutes()}λΆ) - λ€μλ΄μ€ μ’
ν©`,
content: (await setMessage()),
};
await thecamp.sendMessage(cookie, trainee, message);
})();
νλ ¨λ³μ μ΄λ¦μ΄λ μλ
μμΌ, μμ λ± νλ ¨λ³μ λν μ¬μ μ 보λ .env
νμΌμ μ΄μ©ν΄ κ΄λ¦¬νλ€. μ
μ μ μλ μμμ μ μ μκΈ° λλ¬Έμ μΉκ΅¬μκ² λμ€μ μμμ΄ λμ€λ©΄ κ°μ λ£μ΄λ¬λΌκ³ λΆνν΄λλ€. νΈμ§λ₯Ό 보λ΄λ κ³μ μ μλ²μ§μ κ³μ μ μ¬μ©νλ€. λ΄κ° λμκ² νΈμ§λ₯Ό 보λ΄λ λ κ² κ°κΈ°λ νμ§λ§, λ§μ νλ νλ ¨μμ λ€μ΄κ°μ λ¬Έμ κ° μκΈ°λ©΄ λλ²κΉ
μ ν μ μκΈ° λλ¬Έμ μ΅λν μμ ν λ°©λ²μ νν΄μΌ νλ€.
κ·Έλ κ² λ§λ νμΌμ κ°μΈ μλ²μ μ¬λ¦¬κ³ , ν¬λ‘ (Cron)μ μ΄μ©ν΄ ν루 λ λ²μ© νμΌμ μ€ννλλ‘ νλ€.
0 0 * * * cd ~/projects/daily-news-for-trainee && ts-node index.ts
0 18 * * * cd ~/projects/daily-news-for-trainee && ts-node index.ts
νΈμ§λ₯Ό λ λ²μ΄λ λ³΄λΈ μ΄μ λ λ λ§μ λ΄μ€λ₯Ό λ°κΈ° μν¨μ΄κΈ°λ νκ³ , ν λ²λ§ 보λ΄λ©΄ μΈνΈμ΄ λλ½λ μ μλ€λ μ΄μΌκΈ°λ₯Ό λ€μκΈ° λλ¬Έμ΄κΈ°λ νλ€. νν 0μ, 18μμΈ μ΄μ λ λ±ν μμλ€. λμ€μμΌ μκ² λμ§λ§, νΈμ§λ₯Ό λλ μ£Όλ μκ°μ΄ λμ²΄λ‘ 17μ μ μ΄λΌμ 18μλ μ’μ μ νμ΄ μλμλ€.
daily-news-for-traineeκΉμ§ μμ±νλ©° νλ ¨μμ λ€μ΄κ°κΈ° μ ν μ μλ μΌμ λͺ¨λ λλ§μ³€λ€.
μ€ν
8μ 29μΌ λ Όμ° μ‘κ΅°νλ ¨μμ μ μνλ€. νλ‘κ·Έλ¨μ λ¬Έμ κ° μμ΄λ κ³ μΉ μ μλ μν©μ΄λ€λ³΄λ λ΄μ€κ° μ μ¬κΉλ΄ λΆμν΄νλ©° μΌμ£ΌμΌμ 보λλ€. μΌμ£ΌμΌ λ€ μΈμ¬λ΄λΉ νλ ¨λ³λ€μ΄ μΈν°λ· νΈμ§λ₯Ό λλ μ£ΌκΈ° μμνκ³ , μ±κ³΅μ μΌλ‘ λ΄μ€κ° λμ°©νλ€!
λλΆμ λ§€μΌ λ ν΅μ© λ΄μ€λ₯Ό λ°μ보며 λ°κΉ₯ μμμ μ μ μμλ€. μ
μ λ°λ‘ μ λ λ°€μ μμμΉ λͺ»ν λ¬Έμ κ° μ겨μ node_modules
λλ ν 리μ νΈλμ€νμΌλ μλ°μ€ν¬λ¦½νΈ μ½λλ₯Ό μ§μ μμ νλ μ§κΉμ§ νλλ°, λ€νν μ λμνλ€.
λͺ κ°μ§ μ€μν κ²μ΄ μλ€λ©΄ λ μ§κ° 0μλΆν° μμν΄μ μ λͺ©μ λ μ§κ° 9μμ΄ μλλΌ 8μλ‘ μ°νλμλ€λ μ , λΆνμν 문ꡬλ₯Ό μμ νλ μ κ·ννμμ μλͺ» μμ±νμ¬ κΈ°μ¬μ μΌλΆκΉμ§ λ λ €λ²λ¦¬λ κ²½μ°κ° μμλ€λ μ μ΄λ€.
λν μΌλ°μ μΌλ‘ κΈ°μ¬μ 첫 λ¬Έμ₯μ μ λͺ©μ κ·Έλλ‘ λ°λ³΅νκΈ° λλ¬Έμ 첫 λ¬Έμ₯μ 보μ¬μ£Όλ건 λ΄μ© νμ μ ν° λμμ΄ λμ§ μμλ€. κ°λ Ή β14νΈ νν βκ°μ§ν€β λ² νΈλ¨ λ€λμ μλ©Έβ κΈ°μ¬μ 첫 λ¬Έμ₯μ "λ² νΈλ¨ λ€λ λΆκ·Όμμ λ°μν μ 14νΈ νν 'κ°μ§ν€βκ° μλμ§λ₯Ό μκ³ μλ©Έλλ€"κ° λλ μμ΄λ€. μ’ ν© λ΄μ€λΌμ κ΄μ¬μ΄ μλ λ΄μ€κ° λ§μλ κ²λ μμ¬μ λ€. μΉμ λ³λ‘ λ΄μ€λ₯Ό λ°λ‘ 보λμΌλ©΄ λ μ’μμ κ² κ°λ€. μ°λ¦¬ λΆλμλ€μ μ£Όλ‘ μ°μ λ΄μ€λ₯Ό μνλλ°, μ’ ν©μλ μ°μ λ΄μ€κ° λ€μ΄κ°λ μΌμ΄ μμλ€.
κ²°μ μ μΌλ‘ 맀μΌμ€λ λ΄μ€λ³΄λ€ μ¬λμ΄ λ³΄λΈ νΈμ§κ° ν¨μ¬ μ’μλ€. νΈμ§λ₯Ό λ°μλλ° λ΄μ€λ©΄ μ€λ§κ°μ΄ λ ν¬λ€.
κ·Έλλ κ·Ήλλ‘ μ νμ μΈ νκ²½μμ λμ λ¬Έμ λ₯Ό ν΄κ²°ν΄λ³Έ κ²½νμ΄ κ½€ μ¬λ°μλ€. κ°λ°ν μ μλ μκ°μ΄ λ§μ§ μμμ λ μ¬λ°λ κ² κ°κΈ°λ νλ€. μ΄λ²μ μ§μ μ°ΎμλΈ λ¬Έμ λ€μ κ°μ ν΄ λμ€μ μ λνλ μΉκ΅¬μκ² λ λμ λ΄μ€λ₯Ό 보λ΄μ€μΌκ² λ€.
- [1]
MDN web docs, βAn overview of HTTPβ, 2019. β©οΈ