ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 마비노기 API 활용 - 경매장 봇 구현
    메신저봇r 2024. 10. 19. 02:31

    압살맨 오픈채팅 바로가기


    압살맨 유튜브 바로가기


    압살맨 후원하기


    시작

    WinHttp 와 오토핫키로 프로그램을 구현하다보니 느낀 한계점이 너무나 명확했다.

    그래서 24시간 반응형을 만들고 싶어도 컴퓨터가 켜져있지 않으면 사용을 못하다보니 불편함이 많았다.

    그리고 가볍게 사용하기에는 프로그램을 켜야한다는 점에서 결코 가볍지 못했고 컴퓨터를 끼고있지 않으면 사실상 사용하기 어렵다는 점도 한몫한다.


    본문

    요즘 메신저봇을 만드는 재미에 푹 빠졌다.

     

    마비노기를 최근에 좀 하다가 타이밍이 딱 맞게 API 를 업데이트 해줬다.

    그에 맞게 나도 api 를 좀 활용해보려고 한다.

    const NEXON_API_KEY = "your_api_key";
    const AUCTION_LIST_API_URL = "https://open.api.nexon.com/mabinogi/v1/auction/list";
    const AUCTION_HISTORY_API_URL = "https://open.api.nexon.com/mabinogi/v1/auction/history";
    
    function response(room, msg, sender, isGroupChat, replier, imageDB, packageName) {
        if (msg.startsWith(".ㄱ ")) {
            const itemName = encodeURIComponent(msg.slice(5).trim());
            searchAuctionIntegrated(itemName, replier);
        }
    }
    
    function searchAuctionIntegrated(itemName, replier) {
        try {
            const listUrl = AUCTION_LIST_API_URL + "?item_name=" + itemName;
            const historyUrl = AUCTION_HISTORY_API_URL + "?item_name=" + itemName;
    
            const listResponse = org.jsoup.Jsoup.connect(listUrl)
                .header("accept", "application/json")
                .header("x-nxopen-api-key", NEXON_API_KEY)
                .ignoreContentType(true)
                .execute()
                .body();
    
            const historyResponse = org.jsoup.Jsoup.connect(historyUrl)
                .header("accept", "application/json")
                .header("x-nxopen-api-key", NEXON_API_KEY)
                .ignoreContentType(true)
                .execute()
                .body();
    
            const listData = JSON.parse(listResponse);
            const historyData = JSON.parse(historyResponse);
    
            if (listData.auction_item && listData.auction_item.length > 0) {
                const lowestPriceItem = findLowestPriceItem(listData.auction_item);
                const totalItemCount = calculateTotalItemCount(listData.auction_item);
                const totalListings = listData.auction_item.length;
    
                let totalSold = 0;
                let averagePrice = 0;
                let transactionCount = 0;
    
                if (historyData.auction_history && historyData.auction_history.length > 0) {
                    totalSold = calculateTotalSold(historyData.auction_history);
                    averagePrice = calculateAveragePrice(historyData.auction_history);
                    transactionCount = historyData.auction_history.length;
                }
    
                const result = formatIntegratedResult(decodeURIComponent(itemName), lowestPriceItem, totalItemCount, totalListings, totalSold, averagePrice, transactionCount);
                replier.reply(result);
            } else {
                replier.reply("검색 결과가 없습니다.");
            }
        } catch (error) {
            replier.reply("검색 중 오류가 발생했습니다: " + error.message);
        }
    }
    
    function findLowestPriceItem(items) {
        return items.reduce((lowest, current) => 
            current.auction_price_per_unit < lowest.auction_price_per_unit ? current : lowest
        );
    }
    
    function calculateTotalItemCount(items) {
        return items.reduce((total, item) => total + item.item_count, 0);
    }
    
    function calculateTotalSold(history) {
        return history.reduce((total, item) => total + item.item_count, 0);
    }
    
    function calculateAveragePrice(history) {
        const totalPrice = history.reduce((total, item) => total + (item.auction_price_per_unit * item.item_count), 0);
        const totalCount = history.reduce((total, item) => total + item.item_count, 0);
        return totalCount > 0 ? totalPrice / totalCount : 0;
    }
    
    function formatIntegratedResult(itemName, lowestPriceItem, totalItemCount, totalListings, totalSold, averagePrice, transactionCount) {
        const expirationTime = calculateExpirationTime(lowestPriceItem.date_auction_expire);
        const totalPrice = lowestPriceItem.auction_price_per_unit * lowestPriceItem.item_count;
    
        return "['" + itemName + "' 검색 결과]\n" +
               "개당가격: " + numberWithCommas(lowestPriceItem.auction_price_per_unit) + "\n" +
               "총 가격: " + numberWithCommas(totalPrice) + " (" + lowestPriceItem.item_count + "개)\n" +
               "만료일: " + expirationTime + "\n" +
               "총 등록된 매물: " + numberWithCommas(totalItemCount) + "개\n" +
               "총 등록 개수: " + totalListings + "건\n" +
               "총 판매 개수: " + numberWithCommas(totalSold) + "개\n" +
               "평균 판매가: " + numberWithCommas(Math.round(averagePrice)) + "\n" +
               "거래 건수: " + transactionCount + "건";
    }
    
    function calculateExpirationTime(expireDate) {
        const now = new Date();
        const expire = new Date(expireDate);
        const diffHours = Math.floor((expire - now) / (1000 * 60 * 60));
    
        if (diffHours < 0) {
            return "만료됨";
        } else if (diffHours < 24) {
            return diffHours + "시간 후";
        } else {
            const days = Math.floor(diffHours / 24);
            const hours = diffHours % 24;
            return days + "일 " + hours + "시간 후";
        }
    }
    
    function numberWithCommas(x) {
        return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    }

     

    마비노기 경매장 검색 봇

    .ㄱ [아이템명]

     

    을 입력하면 최저가와 매물 수량, 최근 1시간 판매가 판매수량 을 알려준다.

    댓글