search.js 18.8 KB
const Discord = require("discord.js");
exports.run = async (client, msg, args, prefix) => {
    if (args[0]) { // 명령어 뒤에 입력값이 있을 경우 (ex. !<명령어> <채팅>)
        // 검색어 한 문장으로 합치기
        const search = args.join(' ');
        // console.log(search);

        // 검색 대기 시간이 길어질 수 있으므로 사용자에게 진행상황을 알려줄 필요가 있다
        msg.reply("검색중 ...");

        //include Puppeteer Library
        const puppeteer = require('puppeteer');

        const withBrowser = async (fn) => {
            const browser = await puppeteer.launch({ headless: true });
            try {
                return await fn(browser);
            } finally {
                await browser.close();
            }
        };

        const withPage = (browser) => async (fn) => {
            const page = await browser.newPage();
            try {
                return await fn(page);
            } finally {
                await page.close();
            }
        };

        const urls = ['http://www.bestpen.kr/shop/shopbrand.html?&search=', 'http://www.pencafe.co.kr/shop/shopbrand.html?&search=', 'https://blueblack.co.kr/product/search.html?&keyword=', 'http://japan9.co.kr/shop/shopbrand.html?&search='];
        const results = await withBrowser(async (browser) => {
            return Promise.all(urls.map(async (url) => {
                return withPage(browser)(async (page) => {
                    // 각 사이트별 제품 검색  
                    var result = '';
                    site: switch (urls.indexOf(url)) {
                        case 0:
                            // 베스트펜
                            try {
                                var bestpen_url = url + search + "&sort=price";
                                await page.goto(bestpen_url);
                                // console.log("bestpen crawling");

                                // 페이지 로딩 대기
                                await page.waitForSelector('#searchWrap > div > div.item-wrap > div:nth-child(2) > dl:nth-child(4) > dd > ul > li.prd-name > a');
                            } catch { ; } // 사이트 링크에 이상이 생겼거나 검색에 문제가 생겼을 경우 프로그램이 종료되는 것을 방지

                            // 검색 결과 가져오기 (최대 4개)
                            searchLoop: for (var i = 1; i <= 4; i++) {
                                try {
                                    var title = await page.$eval('#searchWrap > div > div.item-wrap > div:nth-child(2) > dl:nth-child(' + i + ') > dd > ul > li.prd-name > a', element => {
                                        return element.textContent;
                                    });
                                    // console.log("베스트펜 검색 결과 제품명 : ", title);

                                    var link = await page.$eval('#searchWrap > div > div.item-wrap > div:nth-child(2) > dl:nth-child(' + i + ') > dt > a', element => {
                                        return element.href;
                                    });
                                    // console.log("베스트펜 검색 링크 : ", link);

                                    // 링크 구조상 &search 뒷부분은 제품 링크를 띄우는데 영향을 미치지 않음
                                    // -> 글자수 제한(1024)도 있으므로 제거
                                    link = link.slice(0, link.indexOf('&search'));

                                    try {
                                        var price = await page.$eval('#searchWrap > div > div.item-wrap > div:nth-child(2) > dl:nth-child(' + i + ') > dd > ul > li.prd-price > p:nth-child(2) > span.price', element => {
                                            return element.textContent;
                                        });
                                        // console.log("베스트펜 검색 결과 가격 : ", price);
                                    } catch {
                                        // 품절이라서 금액정보가 없을 경우 "SOLD OUT"으로 표시
                                        price = "SOLD OUT";
                                    }

                                    result += `[${title}](${link}) - ${price}` + '\n';
                                } catch {
                                    if (i == 1) {
                                        // 제품 정보가 아예 없을 경우 "검색결과 없음"으로 표시
                                        result += "검색결과 없음" + '\n';
                                        break searchLoop;
                                    } else {
                                        // 제품 개수가 4개 이하인 경우
                                        break searchLoop;
                                    }
                                }
                            }
                            break site;
                        case 1:
                            // 펜카페
                            try {
                                var pencafe_url = url + search + "&sort=price";
                                await page.goto(pencafe_url);
                                // console.log("pencafe crawling");

                                // 페이지 로딩 대기
                                await page.waitForSelector('#searchWrap > div > div.item-wrap > div:nth-child(2) > dl:nth-child(4) > dd > ul > li.prd-name > a');
                            } catch { ; } // 사이트 링크에 이상이 생겼거나 검색에 문제가 생겼을 경우 프로그램이 종료되는 것을 방지

                            // 검색 결과 가져오기 (최대 4개)
                            searchLoop: for (var i = 1; i <= 4; i++) {
                                try {
                                    var title = await page.$eval('#searchWrap > div > div.item-wrap > div:nth-child(2) > dl:nth-child(' + i + ') > dd > ul > li.prd-name > a', element => {
                                        return element.textContent;
                                    });
                                    // console.log("펜카페 검색 결과 제품명 : ", title);

                                    // 펜카페 구조상 제품명 앞에 할인률(ex. 17%)이 붙는 경우가 많음 -> 제거
                                    if (title.indexOf('%') != -1) { title = title.slice(title.indexOf('%') + 1); }
                                    // 펜카페 구조상 제품명 뒤에 사족(ex. (색상선택/금장~~~))이 붙는 경우가 많음 -> 제거
                                    if (title.lastIndexOf(')') == title.length - 1) { title = title.slice(0, title.lastIndexOf('(')); }

                                    var link = await page.$eval('#searchWrap > div > div.item-wrap > div:nth-child(2) > dl:nth-child(' + i + ') > dd > ul > li.prd-name > a', element => {
                                        return element.href;
                                    });
                                    // console.log("펜카페 검색 링크 : ", link);

                                    // 링크 구조상 &search 뒷부분은 제품 링크를 띄우는데 영향을 미치지 않음
                                    // -> 글자수 제한(1024)도 있으므로 제거
                                    link = link.slice(0, link.indexOf('&search'));

                                    try {
                                        var price = await page.$eval('#searchWrap > div > div.item-wrap > div:nth-child(2) > dl:nth-child(' + i + ') > dd > ul > li.prd-price > span', element => {
                                            return element.textContent;
                                        });
                                        // console.log("펜카페 검색 결과 가격 : ", price);
                                    } catch {
                                        // 품절이라서 금액정보가 없을 경우 "SOLD OUT"으로 표시
                                        price = "SOLD OUT";
                                    }

                                    result += `[${title}](${link}) - ${price}` + '\n';
                                } catch {
                                    if (i == 1) {
                                        // 제품 정보가 아예 없을 경우 "검색결과 없음"으로 표시
                                        result += "검색결과 없음" + '\n';
                                        break searchLoop;
                                    } else {
                                        // 제품 개수가 4개 이하인 경우
                                        break searchLoop;
                                    }
                                }
                            }
                            break site;
                        case 2:
                            // 블루블랙
                            try {
                                var blueblack_url = url + search + "&order_by=priceasc";
                                await page.goto(blueblack_url);
                                // console.log("blublack crawling");

                                // 페이지 로딩 대기
                                await page.waitForSelector('#contents > div:nth-child(4) > ul > li:nth-child(4) > div.description > p.name > a > span:nth-child(2)');
                            } catch { ; } // 사이트 링크에 이상이 생겼거나 검색에 문제가 생겼을 경우 프로그램이 종료되는 것을 방지

                            // 검색 결과 가져오기 (최대 4개)
                            searchLoop: for (var i = 1; i <= 4; i++) {
                                try {
                                    var title = await page.$eval('#contents > div:nth-child(4) > ul > li:nth-child(' + i + ') > div.description > p.name > a > span:nth-child(2)', element => {
                                        return element.textContent;
                                    });
                                    // console.log("블루블랙 검색 결과 제품명 : ", title);

                                    var link = await page.$eval('#contents > div:nth-child(4) > ul > li:nth-child(' + i + ') > div.description > p.name > a', element => {
                                        return element.href;
                                    });
                                    // console.log("블루블랙 검색 링크 : ", link);

                                    // 링크 구조상 &search 뒷부분은 제품 링크를 띄우는데 영향을 미치지 않음
                                    // -> 글자수 제한(1024)도 있으므로 제거
                                    link = link.slice(0, link.indexOf('&cate_no'));

                                    try {
                                        // 품절 아이콘이 있는지 확인
                                        await page.$eval('#contents > div:nth-child(4) > ul > li:nth-child(' + i + ') > div.description > div.status > div > img', element => {
                                            return element.getAttribute("src");
                                        });

                                        var price = 'SOLD OUT';
                                    } catch {
                                        try {
                                            // 품절 아이콘이 없을 경우
                                            var price = await page.$eval('#contents > div:nth-child(4) > ul > li:nth-child(' + i + ') > div.description > ul > li:nth-child(3) > span:nth-child(2)', element => {
                                                return element.textContent;
                                            });
                                        } catch {
                                            var price = '0원';
                                        }
                                    }
                                    // console.log("블루블랙 검색 결과 가격 : ", price);

                                    result += `[${title}](${link}) - ${price}` + '\n';
                                } catch {
                                    if (i == 1) {
                                        // 제품 정보가 아예 없을 경우 "검색결과 없음"으로 표시
                                        result += "검색결과 없음" + '\n';
                                        break searchLoop;
                                    } else {
                                        // 제품 개수가 4개 이하인 경우
                                        break searchLoop;
                                    }
                                }
                            }
                            break site;
                        case 3:
                            // 재팬나인 
                            try {
                                await page.goto(url);
                                // console.log("japannine crawling");

                                var japannine_url = url + search + "&sort=price";
                                await page.goto(japannine_url);
                                // console.log("japannine crawling");

                                // 페이지 로딩 대기
                                await page.waitForSelector('#mk_search_production > tbody > tr:nth-child(17) > td:nth-child(3) > a');
                            } catch { ; } // 사이트 링크에 이상이 생겼거나 검색에 문제가 생겼을 경우 프로그램이 종료되는 것을 방지

                            // 검색 결과 가져오기 (최대 4개)
                            searchLoop: for (var i = 1; i <= 4; i++) {
                                try {
                                    var title = await page.$eval('#mk_search_production > tbody > tr:nth-child(' + (5 + ((i - 1) * 4)) + ') > td:nth-child(3) > a', element => {
                                        return element.textContent;
                                    });
                                    // console.log("재팬나인 검색 결과 제품명 : ", title);

                                    // 재팬나인 구조상 제품명 앞에 항상 '/n'이 붙는다 -> 제거
                                    title = title.slice(1);
                                    // 재팬나인 구조상 제품명 앞과 뒤에 [쿠폰적용 || 주문예약상품]이 붙는 경우가 많음 -> 제거
                                    if (title.indexOf('[') == 0) { title = title.slice(title.indexOf(']') + 1); }
                                    if (title.lastIndexOf(']') == title.length - 1) { title = title.slice(0, title.lastIndexOf('[')); }
                                    if (title.indexOf('{') == 0) { title = title.slice(title.indexOf('}') + 1); }
                                    if (title.lastIndexOf('}') == title.length - 1) { title = title.slice(0, title.lastIndexOf('{')); }

                                    var link = await page.$eval('#mk_search_production > tbody > tr:nth-child(' + (5 + ((i - 1) * 4)) + ') > td:nth-child(3) > a', element => {
                                        return element.href;
                                    });
                                    // console.log("재팬나인 검색 링크 : ", link);

                                    // 링크 구조상 &search 뒷부분은 제품 링크를 띄우는데 영향을 미치지 않음
                                    // -> 글자수 제한(1024)도 있으므로 제거
                                    link = link.slice(0, link.indexOf('&search'));

                                    try {
                                        // 품절 아이콘이 있는지 확인
                                        var img_src = await page.$eval('#mk_search_production > tbody > tr:nth-child(' + (5 + ((i - 1) * 4)) + ') > td:nth-child(6) > img', element => {
                                            return element.getAttribute("src");
                                        });

                                        if (img_src.indexOf('no_amount0') != -1) { var price = 'SOLD OUT'; }
                                    } catch {
                                        // 품절 아이콘이 없을 경우
                                        var price = await page.$eval('#mk_search_production > tbody > tr:nth-child(' + (5 + ((i - 1) * 4)) + ') > td.brandprice > span', element => {
                                            return element.textContent;
                                        });

                                        // 재팬나인 구조상 금액 뒤에 (옵션에 따라 변동)이 붙는 경우가 있다 -> 제거
                                        if (price.indexOf('(') != -1) { price = price.slice(0, price.indexOf('원') + 1); }
                                    }
                                    // console.log("재팬나인 검색 결과 가격 : ", price);

                                    result += `[${title}](${link}) - ${price}` + '\n';
                                } catch {
                                    if (i == 1) {
                                        // 제품 정보가 아예 없을 경우 "검색결과 없음"으로 표시
                                        result += "검색결과 없음" + '\n';
                                        break searchLoop;
                                    } else {
                                        // 제품 개수가 4개 이하인 경우
                                        break searchLoop;
                                    }
                                }
                            }
                            break site;
                    }

                    return result;
                });
            }));
        });

        // 검색 결과 챗봇에 출력
        let Commands = new Discord.MessageEmbed()

            .setTitle(`${search}에 대한 검색 결과`)
            .setColor("E5D49A")
            // 베스트펜 검색 결과 (ex. 제품명(링크) - 금액)
            .addField('베스트펜', `${results[0].slice(0, 1023)}`)
            // 펜카페 검색 결과
            .addField('펜카페', `${results[1].slice(0, 1023)}`)
            // 블루블랙 검색 결과
            .addField('블루블랙', `${results[2].slice(0, 1023)}`)
            // 재팬나인 검색 결과
            .addField('재팬나인', `${results[3].slice(0, 1023)}`);

        msg.reply({ embeds: [Commands] });
    } else {
        msg.reply("검색어가 없습니다. 검색어를 추가해서 다시 입력해주세요.");
    }
};

exports.config = {
    name: '문구',

    aliases: ['문구류', '가격비교', 'stationery', 'search'],
    category: ['Stationery'],
    des: ['채팅 내용에 대한 검색결과를 보여줍니다.'],
    use: ['!문구 <채팅>']
};