상세 컨텐츠

본문 제목

Golang을 이용하여 Http Client - 세븐나이츠2 쿠폰 입력

Language/..1

by yiaw 2021. 11. 4. 21:38

본문

요즘 즐겨하는 게임 세븐나이츠2에서 대보물시대라는 쿠폰 이벤트를 진행한다. 

forum.netmarble.com/sk2/view/13/746266

 

[이벤트] 대보물시대 이벤트! 안내 - 세븐나이츠2 공식 포럼

  안녕하세요. 새로운 세븐나이츠의 시작! <세븐나이츠2>의 공식 소통왕 GM포넷입니다. 드.디.어! 세븐나이츠2에서도 시작되는 대 보물 시대!  루비, 토파즈, 문스톤, 전설 영...

forum.netmarble.com

무려 쿠폰이 100개 이걸 언제 일일이 입력하고 있을까 .. 그냥 Golang 연습겸 올려보았다. 

 

자 먼저 HTTP Client를 작성하기전 서버가 어떤 메시지를 받는지 부터 알아봐야 한다. 

아래 페이지에 접속하자 (Chrom , Edge) 

couponview.netmarble.com/coupon/sknightsmmo/1290

 

대 보물 시대 이벤트 - Netmarble

드디어 시작된 대 보물 시대! 곳곳에 숨겨진 쿠폰을 찾아 입력해보세요!

couponview.netmarble.com

<F12>를 누르면 아래와 같은 화면이 나올것이다.

1번 회원번호입력 (실제 회원번호를 입력해주가 예외처리 방식이 글자길이인지는 모르지만 아무거나 입력하니까 안된다.. )

2번 쿠폰 입력 (얘는 아무거나 입력 가능)

3번 확인 (POPUP 창 뜨면 그냥 확인 눌러서 닫자)

4번 Netwok 탭을 눌러 

5번 화면창을 보자 ! 

 

위 순서대로 진행하면 최종적으로 5번 화면에 아래와 같이 나올 것이다.

가볍게 apply를 클릭해보자 ! 

방대한 양이의 데이터가 나오겠지만 우리는 Header 와 Data 부분만 확인 해보자 

Header는 가볍게 Post로 요청하며 요청 대상이 어딘지 URL에 나와있다. 

쿠폰 실패 했는데 200 ok!! ㅋㅋ 아마 에러 코드는 Response Body에 담겨 있는것 같다. 

 

 

Body 영역은 From Data의 view source를 클릭하면 위와 같이 Data Source 부분이 나온다. 

key=value 형식이며 구분은 &문자로 하는것으로 보여진다. 

wordId와 nickname 의 value는 없어 보인다. 

 

물론 더 확인해볼 정보들이 있지만 구현에 있어 문제는 없음으로 넘어가자 

 

 

세상 제일 간단한 curl로 http request를 전송시 아래와 같다.

 

$ curl -k -XPOST https://couponview.netmarble.com/coupon/sknightsmmo/1290/apply -d '\
pid=회원코드&channelCode=100&couponCode=쿠폰'
#회원코드와 쿠폰은 본인 회원코드와 사용할 쿠폰을 입력하면 된다.

 

자 이제 이 내용을 Go로 개발해보자 설명은 코드 인라인 주석으로 확인 

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strings"
)

const Ip string = "http://couponview.netmarble.com"
const Url string = "/coupon/sknightsmmo/1290/apply"
const ChannelCode string = "100"
const IdListIni string = "./idlist.ini"
const CouponIni string = "./coupon.ini"

// http response body를 읽어오는 함수 
func ReadBody(resp *http.Response) []byte {
	resBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Printf("ReadAll Fail... err=%v\n", err)
	}
	defer resp.Body.Close()

	return resBody
}

// http resopnse 메시지 구조체 
// Golang에서는 TAG 개념을 이용하여 JSON 변환시 필드명을 변경 할 수있다.
type Data struct {
	ResultCode       string `json:"resultCode,omitempty"`
	CouponSetting    string `json:"couponSetting,omitempty"`
	RewardItem       string `json:"rewardItem,omitempty"`
	ResultMessage    string `json:"resultMessage,omitempty"`
	ResultSubMessage string `json:"resultSubMessage,omitempty"`
}

func main() {
	var IdList []string
	var IdName []string
	var CouponList []string
	var CouponDesc []string

	var FailCnt int = 0
	var AlreadyCnt int = 0
	var SuccCnt int = 0
    // 우리 길드원 분들을 위한 .. IDLIST 파일 읽어오기 위한 파일 OPEN 함수
	f1, err := os.Open(IdListIni)
	if err != nil {
		fmt.Printf("os.Open() Fail... err=%v\n", err)
		return
	}
    // defer 매크로는 본 함수가 return 될 경우 defer가 마지막에 등록된 애들부터 순차적으로 실행 된다.
    // STACK
	defer f1.Close()

    // 파일의 내용을 읽기 위한 Buffer
	reader1 := bufio.NewReader(f1)

	for {
		line, isPrefix, err := reader1.ReadLine()
		if isPrefix || err != nil {
			break
		}
        // ID 계정 코드 형식이며 구분자는 공백  
		str := strings.Split(string(line), " ")
		if len(str) != 2 {
			fmt.Printf("Conpon INI Read Fail... line=%s\n", line)
			continue
		}
        
        // 각각 List에 공백을 지운 문자열을 추가 
		IdName = append(IdName, strings.TrimSpace(str[0]))
		IdList = append(IdList, strings.TrimSpace(str[1]))
	}

    // 쿠폰 리스트을 읽기 위해 파일 오픈 
	f2, err := os.Open(CouponIni)
	if err != nil {
		fmt.Printf("os.Open() Fail... err=%v\n", err)
		return
	}
	defer f2.Close()

	reader2 := bufio.NewReader(f2)

	for {
		line, isPrefix, err := reader2.ReadLine()
		if isPrefix || err != nil {
			break
		}

		str := strings.Split(string(line), "-")
		if len(str) != 2 {
			fmt.Printf("Coupon INI Read Fail... line=%s\n", line)
			continue
		}
		CouponList = append(CouponList, strings.TrimSpace(str[0])) // Coupon
		CouponDesc = append(CouponDesc, strings.TrimSpace(str[1])) // Description
	}

	for i := 0; i < len(IdList); i++ {
		FailCnt = 0
		SuccCnt = 0
		AlreadyCnt = 0
        // 각각 쿠폰 결과 저장을 위한 파일 생성
		f, err := os.OpenFile(IdName[i]+".txt", os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(0644))
		if err != nil {
			fmt.Printf("OpenFile Fail... err=%v\n", err)
			continue
		}

		fmt.Printf("#### %s Coupon #####\n", IdName[i])
		fmt.Fprintf(f, "#### %s Coupon #####\n", IdName[i])
		for j := 0; j < len(CouponList); j++ {
            // Body 데이터 생성
			Body := fmt.Sprintf("pid=%s&channelCode=%s&couponCode=%s&worldId=&nickname=", IdList[i], ChannelCode, CouponList[j])
			// string 데이터를 IoReader로 변환 
            r := strings.NewReader(Body)
            
            // HTTP POST요청 결과는 RESP 객체에 저장 됨
			resp, err := http.Post(Ip+Url, "application/x-www-form-urlencoded; charset=UTF-8", r)
			if err != nil {
				fmt.Printf("http.Post Fail.. err=%v\n", err)
				FailCnt++
				continue
			}

			// HTTP 요청 실패 
			if resp.StatusCode != http.StatusOK {
				fmt.Fprintf(f, "HTTP Request Fail\n")
				FailCnt++
				continue
			}
            
            // IoReader를 byte로 변환 
			resBody := ReadBody(resp)
			if len(resBody) == 0 {
				fmt.Fprintf(f, "ResBody Fail...\n")
			}
            
            // Response 구조체 변수 선언
			var data Data
            
            // []byte 형식의 데이터를 구조체에 저장 
			json.Unmarshal(resBody, &data)
			if data.ResultCode == "COUPON_ALREADY_USE" {
				fmt.Fprintf(f, "Already Use Coupon [%s]:[%s]\n", CouponList[j], CouponDesc[j])
				AlreadyCnt++
				continue
			}
			fmt.Fprintf(f, "Succ Coupon.. Description [%s]:[%s]\n", CouponList[j], CouponDesc[j])
			SuccCnt++
		}
		fmt.Fprintf(f, "\n\nTotal Result\n")
		fmt.Fprintf(f, "%10s : %d\n", "Succ", SuccCnt)
		fmt.Fprintf(f, "%10s : %d\n", "Fail", FailCnt)
		fmt.Fprintf(f, "%10s : %d\n", "Already", AlreadyCnt)
		f.Close()
	}
	os.Exit(0)
}

'Language > ..1' 카테고리의 다른 글

URL Tree <Path Tree> - 매칭  (0) 2021.11.05
URL Tree <Path Tree> - 생성  (0) 2021.11.05

관련글 더보기

댓글 영역