닌텐도 구매를 구매하기 위해 온라인 쇼핑몰 새로고침을 하기도 힘들고.. 직장에서 눈치도보이고..
닌텐도 구매를 위한 알림이 제작기.
1) 알림이 선택 기준
1) javascript - 개발하기 평소 관심이 많았고 웹 개발시 종종 사용했던 언어 ( node js 너무 좋아.. )
2) java - 자바 .. spring.. 때 너무 디었어.. 넌 패스
3) c/c++ - 회사에서 서버 프로그램시 사용하고 있지만 개발 ,배포 환경 구성이 윈도우에서 하기엔 쫌..
4) c# - 초면이라 실례하겠습니다..
5) python - 와.. 이걸로 개발하면 정말 금방 끝나겠다..
근데 너무 쉬워.. 흠..
1) Visual studio Code : 여러가지 추가기능을 사용하여 갖가지 언어를 모두 개발할 수 있음.
2) Power... 패스.. 그냥 1번..
알림이로 적당한 텔레그램 봇 선택!
( 요즘 N번방 사건도 있고 텔레그램 이미지가 좀 그렇긴 하지만. 참 많은 기능들을 제공해준다.. 개꿀.. )
https://core.telegram.org/bots/api
javascript 언어로 선택! ( node js )
선택 이유 : 다음에 만들어볼 웹 기반 .. (비밀) 을 위해.
telegram-bot
- src
--- getProduct.js
--- main.js
-.gitignore
-package.json
메인을 작성하기 전 텔레그램 봇 api 기본 사용방법등을 확인하였다.
구글에 telegram bot 검색하면 많은 자료들을 볼 수 있다.
자 이제 main.js 코딩을 시작 해보자..
const getPod = require("./getProduct");
const TelegramBot = require('node-telegram-bot-api');
const schedule = require("node-schedule");
const log = console.log;
const token = '발급받은 토큰';
const bot = new TelegramBot(token, {polling: true});
// 텔레그램 Chat ID
const chatId = "내 chatid"
// 쿠팡
const url = "https://www.coupang.com/vp/products/1384804427?sourceType=share&itemId=2419615336&vendorItemId=70413795361%26%23160%3B&isAddedCart=";
// const url =" ";
let rsPod = new Object();
// SSG
const url2 = "http://shinsegaemall.ssg.com/item/itemView.ssg?itemId=1000042289500&ckwhere=linkprice";
let rsPod2 = new Object();
// E-MART
const url3 = "http://emart.ssg.com/item/itemView.ssg?itemId=1000042290732&ckwhere=linkprice";
let rsPod3 = new Object();
// T몬
const url4 = "http://www.tmon.co.kr/deal/3238163078?keyword=%EB%8B%8C%ED%85%90%EB%8F%84%EC%8A%A4%EC%9C%84%EC%B9%98+%EB%8F%99%EB%AC%BC%EC%9D%98%EC%88%B2+%EC%97%90%EB%94%94%EC%85%98&tl_area=SALDEAL&tl_ord=5&searchClick=DL%7CND%7CBM&thr=re";
let rsPod4 = new Object();
// 아이엠 게임
const url5 = "https://smartstore.naver.com/imgame/products/4850935441#revw";
let rsPod5 = new Object();
// TV-shhoping
const url6 = "http://www.shinsegaetvshopping.com/display/detail/10573631";
let rsPod6 = new Object();
HTML 을 요청할때 request 를 사용할지 Axios 를 사용할지 고민하다가 그냥 예전에 한번 사용해본 axios 를 사용하였다.( 예전코드 복붙.. )
받아온 HTML을 쉽게 파싱하려면 어떤 라이브러리를 써야하나 고민중 cheerio 라는 라이브러리를 발견.. 이를 사용해 보았다.
개발 도중 Date() 날짜를 포맷팅하여 출력해 줄 필요가 있어 인터넷에 떠도는 코드를 하나 가져와 보았다..
const axios = require("axios");
const cheerio = require("cheerio");
const v = require("voca");
const log = console.log;
// 쿠팡
const url = "https://www.coupang.com/vp/products/1384804427?itemId=2419615336&vendorItemId=70413795361&q=%EB%8B%8C%ED%85%90%EB%8F%84+%EC%8A%A4%EC%9C%84%EC%B9%98+%EB%B3%B8%EC%B2%B4&itemsCount=36&searchId=0350beaa6d34414c876ae47c329bbb8d&rank=1&isAddedCart=";
// const url = "";
// SSG
const url2 = "http://shinsegaemall.ssg.com/item/itemView.ssg?itemId=1000042289500&ckwhere=linkprice";
// E-MART
const url3 = "http://emart.ssg.com/item/itemView.ssg?itemId=1000042290732&ckwhere=linkprice";
// T몬
const url4 = "http://www.tmon.co.kr/deal/3238163078?keyword=%EB%8B%8C%ED%85%90%EB%8F%84%EC%8A%A4%EC%9C%84%EC%B9%98+%EB%8F%99%EB%AC%BC%EC%9D%98%EC%88%B2+%EC%97%90%EB%94%94%EC%85%98&tl_area=SALDEAL&tl_ord=6&searchClick=DL%7CND%7CBM&thr=re";
// 아이엠 게임
const url5 = "https://smartstore.naver.com/imgame/products/4850935441#revw";
// TV-shhoping
const url6 = "http://www.shinsegaetvshopping.com/display/detail/10573631";
const getProdctInfo = { };
const rsObjData = new Object();
rsObjData.onError = false;
rsObjData.rsFlag = false;
rsObjData.errString = "-";
rsObjData.strDate = "";
/* date formatter() */
Date.prototype.format = function(f) {
if (!this.valueOf()) return " ";
var weekName = ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"];
var d = this;
return f.replace(/(yyyy|yy|MM|dd|E|hh|mm|ss|a\/p)/gi, function($1) {
switch ($1) {
case "yyyy": return d.getFullYear();
case "yy": return (d.getFullYear() % 1000).zf(2);
case "MM": return (d.getMonth() + 1).zf(2);
case "dd": return d.getDate().zf(2);
case "E": return weekName[d.getDay()];
case "HH": return d.getHours().zf(2);
case "hh": return ((h = d.getHours() % 12) ? h : 12).zf(2);
case "mm": return d.getMinutes().zf(2);
case "ss": return d.getSeconds().zf(2);
case "a/p": return d.getHours() < 12 ? "오전" : "오후";
default: return $1;
}
});
};
String.prototype.string = function(len){var s = '', i = 0; while (i++ < len) { s += this; } return s;};
String.prototype.zf = function(len){return "0".string(len - this.length) + this;};
Number.prototype.zf = function(len){return this.toString().zf(len);};
/* HTML 요청 */
const getHtml = async (ul) => {
try {
return await axios.get(ul);
} catch (error) {
console.error(error);
log("에러 시간 : " + new Date().format("yyyy년 MM월 dd일 a/p hh시 mm분 ss초"));
}
};
/* 에러 결과 */
const urlError = (str) => {
rsObjData.onError = true;
rsObjData.errString = str + " URL 요청 실패";
return rsObjData;
}
const lengthError = (str) => {
rsObjData.onError = true;
rsObjData.errString = str + " 요청 상품정보 없음";
return rsObjData;
}
/* 정상 결과 */
const succData = (diff, str) => {
let rsStr = v.trim(diff);
rsObjData.onError = false;
rsObjData.errString = "-";
rsObjData.rsFlag = v.startsWith(rsStr, str);
rsObjData.strDate = new Date().format("yyyy년 MM월 dd일 a/p hh시 mm분 ss초");
// log(rsStr);
return rsObjData;
}
/* 제품1 */
getProdctInfo.getProdct1Html = () => getHtml(url).then(html => {
if(html.data == undefined) return urlError("Coupang");
const $ = cheerio.load(html.data);
const $bodyList = $("div.oos-label");
if ($bodyList.length == 0) return lengthError("Coupang");
let rsStr = $bodyList[0].children[0].data;
return succData(rsStr, '일시');
});
/* 제품2 */
getProdctInfo.getProdct2Html = () => getHtml(url2).then(html => {
if(html.data == undefined) return urlError("SSG");
const $ = cheerio.load(html.data);
const $bodyList = $("div.cdtl_btn_wrap2 ul").children("li");
if ($bodyList.length == 0) return lengthError("SSG");
let rsStr = $bodyList.find("span.tx_ko").text();
return succData(rsStr, '품절');
});
/* 제품3 */
getProdctInfo.getProdct3Html = () => getHtml(url3).then(html => {
if(html.data == undefined) return urlError("E-MART");
const $ = cheerio.load(html.data);
const $bodyList = $("div.cdtl_btn_wrap2 ul").children("li");
if ($bodyList.length == 0) return lengthError("E-MART");
let rsStr = $bodyList.find("span.tx_ko").text();
return succData(rsStr, '품절');
});
/* 제품4 */
getProdctInfo.getProdct4Html = () => getHtml(url4).then(html => {
if(html.data == undefined) return urlError("T-Mon");
const $ = cheerio.load(html.data);
const $bodyList = $("div.purchase_list_box ul").children("li.col2");
if ($bodyList.length == 0) return lengthError("T-Mon");
let rsStr = $bodyList.find("button.btn_big").text();
return succData(rsStr, '장바구니매진');
});
/* 제품5 */
getProdctInfo.getProdct5Html = () => getHtml(url5).then(html => {
if(html.data == undefined) return urlError("I-am");
const $ = cheerio.load(html.data);
const $bodyList = $("div.not_goods").children("p");
if ($bodyList.length == 0) return lengthError("I-am");
let rsStr = $bodyList.find("em.fc_point").text();
return succData(rsStr, '구매하실');
});
/* 제품6 */
getProdctInfo.getProdct6Html = () => getHtml(url6).then(html => {
if(html.data == undefined) return urlError("T-V");
const $ = cheerio.load(html.data);
const $bodyList = $("div.button-group--1").children("span");
if ($bodyList.length == 0) return lengthError("T-V");
let rsStr = $bodyList.find("span._text").text();
return succData(rsStr, '판매 준비중');
});
module.exports = getProdctInfo;
getProduct.js 의 모듈을 가져와 코딩을 마무리 한다.
텔레그램 API 에 있는 기본 예제를 활용하여 메신저로 '/S' , '/help' 등 요청시 응답 메시지를 추가 하였다.
마지막으로 주기적인 웹 페이지 정보를 가져와야 하는데 단순 for 문으로는.. 처리할 수 없다. ( 단일 스레드이기 때문 )
이를 처리하기위해 "node-schedule" 를 사용하여 주기적인 요청기능을 넣었다.
너무 자주 요청하면 그러니까 10초만 ... ㅋ
/* 상품 정보들을 가져온다. */
const getPodData = () => {
/* 최초 실행 1회 */
getPod.getProdct1Html().then( rs => {
rsPod = rs;
if (rsPod.onError) {
bot.sendMessage(chatId, rsPod.errString);
} else if (!rsPod.rsFlag) {
bot.sendMessage(chatId, url);
}
});
getPod.getProdct2Html().then( rs => {
rsPod2 = rs;
if (rsPod2.onError) {
bot.sendMessage(chatId, rsPod2.errString);
} else if (!rsPod2.rsFlag) {
bot.sendMessage(chatId, url2);
}
});
getPod.getProdct3Html().then( rs => {
rsPod3 = rs;
if (rsPod3.onError) {
bot.sendMessage(chatId, rsPod3.errString);
} else if (!rsPod3.rsFlag) {
bot.sendMessage(chatId, url3);
}
});
getPod.getProdct4Html().then( rs => {
rsPod4 = rs;
if (rsPod4.onError) {
bot.sendMessage(chatId, rsPod4.errString);
} else if (!rsPod4.rsFlag) {
bot.sendMessage(chatId, url4);
}
});
getPod.getProdct5Html().then( rs => {
rsPod5 = rs;
if (rsPod5.onError) {
bot.sendMessage(chatId, rsPod5.errString);
} else if (!rsPod5.rsFlag) {
bot.sendMessage(chatId, url5);
}
});
getPod.getProdct6Html().then( rs => {
rsPod6 = rs;
if (rsPod6.onError) {
bot.sendMessage(chatId, rsPod6.errString);
} else if (!rsPod6.rsFlag) {
bot.sendMessage(chatId, url6);
}
});
}
/* 스케쥴러 */
schedule.scheduleJob('*/10 * * * * *', () => {
getPodData();
});
bot.onText(/\/S/, (msg, match) => {
let rsStr = '품절';
if(!rsPod2.rsFlag) rsStr = '재고있음';
bot.sendMessage(chatId, "[SSG]" + "\n최종확인 : " + rsPod2.strDate + "\n재고상태 : " + rsStr);
});
bot.onText(/\/E/, (msg, match) => {
let rsStr = '품절';
if(!rsPod3.rsFlag) rsStr = '재고있음';
bot.sendMessage(chatId, "[E-MART]" + "\n최종확인 : " + rsPod3.strDate + "\n재고상태 : " + rsStr);
});
bot.onText(/\/T/, (msg, match) => {
let rsStr = '품절';
if(!rsPod4.rsFlag) rsStr = '재고있음';
bot.sendMessage(chatId, "[T-MON]" + "\n최종확인 : " + rsPod4.strDate + "\n재고상태 : " + rsStr);
});
bot.onText(/\/I/, (msg, match) => {
let rsStr = '품절';
if(!rsPod5.rsFlag) rsStr = '재고있음';
bot.sendMessage(chatId,"[I-AM]" + "\n최종확인 : " + rsPod5.strDate + "\n재고상태 : " + rsStr);
});
bot.onText(/\/G/, (msg, match) => {
let rsStr = '품절';
if(!rsPod6.rsFlag) rsStr = '재고있음';
bot.sendMessage(chatId,"[S-TV]" + "\n최종확인 : " + rsPod6.strDate + "\n재고상태 : " + rsStr);
});
bot.onText(/\/list/, (msg, match) => {
bot.sendMessage(chatId, "쿠팡 : " + url + "\n SSG : " + url2 + "\n 이마트 : "+ url3+ "\n 티몬 : "+url4+ "\n 아이엠게임 : "+url5+ "\n S-TV : "+url6);
});
bot.onText(/\/help/, (msg, match) => {
const rsStr = " /list : url 목록 조회 \n"
+ " /C : 쿠팡 \n /S : SSG 몰 \n /E : 이마트 \n /T : 티몬 \n /I : 아이엠 \n /G : S-TV";
bot.sendMessage(chatId, rsStr);
});
/* main Start */
getPodData();
/help , /S, /T URL 리스트 조회 및, 각종 상품 상태 조회 정상이다.
이제 상품정보가 변경될 경우 URL 을 정상으로 던저주는지.. 잘 확인만 되면 된다..