닌텐도 구매를 위한 알림이 - beginning

0

닌텐도

목록 보기
1/1
post-thumbnail

닌텐도 대란....

  • 닌텐도 구매를 구매하기 위해 온라인 쇼핑몰 새로고침을 하기도 힘들고.. 직장에서 눈치도보이고..

  • 닌텐도 구매를 위한 알림이 제작기.

개발 환경

  • LG 그램 노트북 ( 윈도우 10 )
  • 사양 : i5 CPU 에 램은 8기가, 하드가 무려 500GB
  • 기타 : 코딩하기 좋은.. 키보드와, 웹 서핑하기 좋은 마우스...( !? )

본격 개발 하기전에.. !!

1. 알리미 선택

1) 알림이 선택 기준

  • 휴대폰으로 알림을 받을 수 있어야 함 ( 기본 ?! )
  • 알림이 전용 앱이 있어야 함. ( 다른 알림과 구분하기 위함)
  • 기본적으로 제공해주는 기능이 많았으면 좋겠음. ( 별도로 알림이 앱까지 만들고 싶지 않아... )

2. 개발 언어 선택

1) javascript - 개발하기 평소 관심이 많았고 웹 개발시 종종 사용했던 언어 ( node js 너무 좋아.. )

2) java - 자바 .. spring.. 때 너무 디었어.. 넌 패스

3) c/c++ - 회사에서 서버 프로그램시 사용하고 있지만 개발 ,배포 환경 구성이 윈도우에서 하기엔 쫌..

4) c# - 초면이라 실례하겠습니다..

5) python - 와.. 이걸로 개발하면 정말 금방 끝나겠다..
근데 너무 쉬워.. 흠..

3. 개발 도구

1) Visual studio Code : 여러가지 추가기능을 사용하여 갖가지 언어를 모두 개발할 수 있음.

2) Power... 패스.. 그냥 1번..

결론

  • 알림이로 적당한 텔레그램 봇 선택!
    ( 요즘 N번방 사건도 있고 텔레그램 이미지가 좀 그렇긴 하지만. 참 많은 기능들을 제공해준다.. 개꿀.. )
    https://core.telegram.org/bots/api

  • javascript 언어로 선택! ( node js )
    선택 이유 : 다음에 만들어볼 웹 기반 .. (비밀) 을 위해.


코딩 시작

1. 프로젝트 구조

 telegram-bot
   - src
    --- getProduct.js
    --- main.js
    -.gitignore
    -package.json
  • getProduct.js 에서 요청하고 파싱하고.. main.js 에서 알림이로 전송~ 하는 로직등 구성. ( 매우단순 간단하다. )

1단계 : main.js 기초 공사

  • 메인을 작성하기 전 텔레그램 봇 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();
  • 알림에 사용할 url 및 텔레그램에서 사용할 기본값 셋팅.

2) Axios/HTML 파싱 getProduct.js

  • 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;

3) main.js 마무리 코딩

  • 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();
 

4) 테스트

  • 테스트는 PC 에 텔레그램을 설치하여 해보았다.

  • /help , /S, /T URL 리스트 조회 및, 각종 상품 상태 조회 정상이다.

  • 이제 상품정보가 변경될 경우 URL 을 정상으로 던저주는지.. 잘 확인만 되면 된다..

5) 후기...

  • 위 기능들을 만들어 놓고 막상 URL 알림을 받아 눌러보면.. 품절인 상태다..
    음.. 사람들이 더 빨리 사가는걸까..? 왜만든거지.. 싶다.. ㅠㅠ..
profile
rs = woo + sun; console.log("♥" + rs);

0개의 댓글