PoE(패스오브엑자일) PoE Session id 를 이용해 인벤토리를 정리해 보자



0. Python 환경 설정 !!

CMD 상에서 코드를 돌리기 위한 환경을 세팅해 보겠습니다. 

  1. 아래 링크 instruction 대로 Python을 설치합니다. (64bit 이신 분들은 64bit로 진행하시는게 좋습니다. 64bit의 경우 설치, Path 세팅, 환경변수 세팅시 Python36-32 -> Python36 으로 하시면 됩니다.)
  1. 적당한 위치에 폴더를 만듭니다. (예시로 저는 C 드라이브 및에 mycode라는 폴더(C:\mycode)를 만들겠습니다.)
  2. 링크에서 받은 Zip 파일의 압축을 풀어서 나온 모든 파일을 C:\mycode 로 옮깁니다. 
  3. CMD를 관리자 권한으로 실행한 뒤 아래와 같이 입력하여 가상환경을 만듭니다. 가상환경이 정상적으로 만들어 지게 되면 mycode 폴더에 dsvenv 폴더가 생길거에요 !
    • C:\WINDOWS\system32>cd C:\mycode
      C:\mycode>c:\Python36\python -m venv dsvenv
      
      C:\mycode>(가상환경 성공시 프롬프트만 나옴)

       

  4. 아래 명령어로 가상환경을 Enable 합니다. 
    • C:\mycode>dsvenv\Scripts\activate
      (dsvenv) C:\mycode>

       

  5. 코드에 필요한 라이브러리를 설치합니다.
    • (dsvenv) C:\mycode>pip install -r requirements.txt
      
      ...............
      Successfully installed Pillow-5.4.1 PyAutoGUI-0.9.38 PyInstaller-3.4 PyMsgBox-1.0.6 PyScreeze-0.1.20 PyTweening-1.0.3 PyYAML-5.1.1 altgraph-0.16.1 beautifulsoup4-4.7.1 certifi-2019.6.16 chardet-3.0.4 comtypes-1.1.7 configparser-3.7.4 entrypoints-0.3 future-0.17.1 idna-2.8 iso8601-0.1.12 keyboard-0.13.3 keyring-19.0.2 lxml-4.3.4 macholib-1.11 mouse-0.7.0 mss-4.0.3 numpy-1.16.2 opencv-python-4.0.0.21 pbkdf2-1.3 pefile-2019.4.18 pyaes-1.6.1 pyserial-3.4 pywin32-224 pywin32-ctypes-0.2.0 pywinauto-0.6.6 requests-2.22.0 selenium-3.141.0 six-1.12.0 soupsieve-1.9.2 urllib3-1.25.3 wxPython-4.0.6
      You are using pip version 9.0.1, however version 19.2.1 is available.
      You should consider upgrading via the 'python -m pip install --upgrade pip' command.

       

  6. 영상을 참조한 뒤 아래 명령어로 코드를 실행 시킨다. 
    • (dsvenv) C:\mycode>python invenSorter.py

       

 


저번시간에 Currency를 키한방에 Stash 로 옮기는 코드를 만들어 봤는데요 

오늘은 POE API 를 이용해서 Pathofexile 서버에서 캐릭터 인벤토리의 아이템 정보를 한번에 받고

키를 누를 때마다 저번 영상에서 했던 동작을 한번씩 하도록 만들어 봤습니다.

우선 코드는 이 링크에서 받으실 수 있습니다.  전부 다운받아 주시면 됩니다. 

 

해당 매크로는 GGG 정책인 1 Command, 1 Action 정책에 위배 되는 것 같습니다. ! 교육적인 목적으로 봐주시고요 ! 사용은 자유이시나 책임은 본인에게 있으십니다.

1. 설치가 필요한 라이브러리

신규 라이브러리로 requests 라이브러리가 필요합니다. 

기존 라이브러리 설치가 필요하다면 가상환경을 Enable 하고 cmd 상에서 코드를 다운받은 폴더로 이동 후 아래 명령어를 입력하시면 됩니다. 

pip install -r requirements.txt

 

2. config.cfg

[inventory]

poeSessionID = 'POESESSID'
accntName = 계정이름
character = 캐릭터이름
except_currency = ['Scroll of Wisdom', 'Portal Scroll']
confirm_item = Scroll of Wisdom
sell_item_category = ['weapons', 'flasks', 'jewels', 'gems', 'armour', 'accessories']

[inventory_key]
key_get_inven_data = ctrl+]
key_currency_to_stash = F2
key_item_confirm = F3
key_item_sell = F4

[inventory_to_stash]
maps = ctrl+[
cards = ctrl+'

poeSessionID: POESESSID 를 입력해주면 됩니다. 해당 값은 poe web cookie에 들어가 있는 값으로 얻는 방법은 

크롬기준으로 Poe 사이트에 로그인 한뒤 f12를 누르면 개발자 모드가 수행되는데 이때 아래 그림과 같이 하시면 됩니다. 

주의하실점은 hex 값을 ‘fe……’ 이런식으로 따옴표 안에 넣으셔야 합니다. 

accntName, character 아래 위치에서 찾을 수 있습니다. 

다른 파라미터 들의 용도는 영상을 참조해 주세요 ㅎㅎ 

3. 코드 

코드는 첨부 파일에 있듯이 네개의 Python 코드가 필요하며 실행은 python invenSorter.py로 실행 가능하십니다. 

각 코드가 하는 역할은 다음과 같습니다. 

  • cvFunc.py : 최초 코드를 실행 시킬때 inventory Size를 측정할수 있는 기능 제공
  • invenFunc.py: invenSorter.py 의 InventoryToolApi 클래스의 부모역할 
  • gui.py: API 요청 결과에 따라 성공 혹은 실패를 띄우는 GUI 모듈
  • invenSorter.py: 메인 모듈 

 

아래 코드는 invenSorter.py의 내용입니다. 주석을 달아 놨으니 참고해 주세요 ㅎㅎ

import requests
import multiprocessing as mp


from POE.inventoryFunc import *
import POE.gui as ui


K_RARENESS = 'category'

V_CURRENCY = 'currency'
K_ITEMBASENAME = 'typeLine'
K_CHECK = 'identified'
CHECK_ITME = ''

def readConfigFile(configFile, section, opt):

    config = configparser.ConfigParser(interpolation=None)
    config.read_file(codecs.open(configFile, 'r', 'UTF-8-sig'))

    try:
        result = config[section][opt].strip()
    except:
        result = None
    return result

def makeCookie(cookiesStr):
    cookDict = {}
    for i in cookiesStr.split(';'):
        key, val = i.strip().split('=')
        cookDict.update({key: val})
    # cookDict['TS'] = int(time.time())

    return cookDict

# www.pathofexile.com 서버로 데이터를 요청하는 함수
def poeItemApiReq(accntName, character, cookieDict, baseURL):

    param = {'accountName': accntName, 'character': character}

    url = baseURL + 'character-window/get-items'
    try:
        response = requests.post(url, data=param, cookies=cookieDict)
        resultDict = response.json()

        if response.status_code > 400:
            print(response.status_code, 'request error')
            return None
        return resultDict
    except:
        return None

# GUI 를 띄우는 Callback 함수
def printItem(itemDict):

    strToPrint = ''
    for key, item in itemDict.items():
        listStr = []
        listStr.append('category : ' + str(item['category']))
        listStr.append('Base Name : ' + str(item['typeLine']))
        strToPrint += ','.join(listStr) + '\n'
    print(strToPrint)

# GUI를 띄우는 Callback 함수
def textGui(textToPrint):
    app = ui.wx.App(False)
    frm = ui.TextFrame(textToPrint)
    ui.setFrmPosition(frm, mo.get_position())
    frm.Show()
    app.MainLoop()

# GUI 기능을 Multiprocess로 등록하는 함수
def runTextGui(textToPrint):


    textP = mp.Process(target=textGui, args=(textToPrint, ))
    textP.start()

# inventoryFunc.py의 InventoryTool 클래스를 상속받아 작성된 함수
class InventoryToolApi(InventoryTool):

    def __init__(self, inventorySize, listexceptCurr, listCategorySell):
        InventoryTool.__init__(self, inventorySize, listexceptCurr)
        self.listCategorySell = listCategorySell

    # poeItemApiReq 의 Response로 온 데이터를 itemInfoInInven 변수에 저장하는 함수
    # itemInfoInInven 변수는 Dictionary 형으로 Key는 아이템좌표 value는 아이템 정보가 된다.
    def checkItemInInventoryWithAPI(self, apiData):
        for item in apiData['items']:
            # 캐릭터 인벤
            if item['inventoryId'] == 'MainInventory':
                unitPoint = (int(item['x']), int(item['y']))
                self.itemInfoInInven[unitPoint] = item
        printItem(self.itemInfoInInven)
        # pprint.pprint(self.itemInfoInInven)

    # itemInfoInInven 변수에서 Currency를 찾고 이 Currency를 Stash로 보내는 함수
    def moveCurrencyToStash(self):
        hotKeyhasCTRL = False
        if not keys.press('ctrl'):
            keys.press('ctrl')
        else:
            hotKeyhasCTRL = True

        for unitPoint, iteminfo in self.itemInfoInInven.items():
            rpoint = self.realPoint(unitPoint[0], unitPoint[1])
            try:
                # print('move', iteminfo[K_ITEMBASENAME])
                if [*iteminfo[K_RARENESS]][0] == V_CURRENCY \
                        and not iteminfo[K_ITEMBASENAME] in self.listexceptCurr:

                    mo.move(rpoint[0], rpoint[1])
                    time.sleep(0.05)
                    mo.click()
                    print('Move Inven To Stash: ', iteminfo[K_ITEMBASENAME])
                    # 인벤토리에서 Currency가 없어졌으니 해당 데이터를 제거한다.
                    self.itemInfoInInven.pop(unitPoint)
                    break
            except:
                pass
        if not hotKeyhasCTRL:
            keys.release('ctrl')

    # 지정한 category 아이템을 Stash로 보내는 함수
    def moveItemToStash(self, category):
        hotKeyhasCTRL = False
        if not keys.press('ctrl'):
            keys.press('ctrl')
        else:
            hotKeyhasCTRL = True

        for unitPoint, iteminfo in self.itemInfoInInven.items():
            rpoint = self.realPoint(unitPoint[0], unitPoint[1])
            try:

                if [*iteminfo[K_RARENESS]][0] == category \
                        and not iteminfo[K_ITEMBASENAME] in self.listexceptCurr:

                    mo.move(rpoint[0], rpoint[1])
                    time.sleep(0.05)
                    mo.click()
                    print('Move Inven To Stash: ', iteminfo[K_ITEMBASENAME])
                    # 인벤토리에서 Currency가 없어졌으니 해당 데이터를 제거한다.
                    self.itemInfoInInven.pop(unitPoint)
                    break
            except:
                pass
        if not hotKeyhasCTRL:
            keys.release('ctrl')

    def findItemOnInventory(self, itemName):
        for key, item in self.itemInfoInInven.items():
            if item[K_ITEMBASENAME] == itemName:
                return key
        return None
    # itemInfoInInven 에서 미확인 아이템을 찾아 확인 하는 함수
    def itemConfirm(self):
        # 확인 아이템의 위치를 찾는다
        checkItemUnitCoord = self.findItemOnInventory(CHECK_ITME)
        if checkItemUnitCoord == None:
            return
        # 실제 모니터상의 좌표로 좌표 전환
        checkItemPoint = self.realPoint(checkItemUnitCoord[0], checkItemUnitCoord[1])

        #print('-----itemConfirm-----')
        for key, item in self.itemInfoInInven.items():
            if item[K_CHECK] == False:
                # -- 감정주문서로 이동 후 우클릭
                mo.move(checkItemPoint[0], checkItemPoint[1])
                time.sleep(0.05)
                mo.click(mo.RIGHT)
                time.sleep(0.05)
                # -- 감정할 아이템이로 가서 클릭
                itemPoint = self.realPoint(key[0], key[1])
                mo.move(itemPoint[0], itemPoint[1])
                time.sleep(0.05)
                mo.click()
                time.sleep(0.05)
                print('Item Confirmed : ', item[K_ITEMBASENAME])
                self.itemInfoInInven[key][K_CHECK] = True
                break
        #print('--------------------')

    # 아이템 판매 함수
    def itemSell(self):

        #print('-----itemSell-----')
        hotKeyhasCTRL = False
        if not keys.press('ctrl'):
            keys.press('ctrl')
        for key, item in self.itemInfoInInven.items():
            if [*item[K_RARENESS]][0] in self.listCategorySell:
                itemPoint = self.realPoint(key[0], key[1])
                mo.move(itemPoint[0], itemPoint[1])
                time.sleep(0.05)
                mo.click()
                time.sleep(0.05)
                print('Item On Sell : ', item[K_ITEMBASENAME])
                self.itemInfoInInven.pop(key)
                break

        if not hotKeyhasCTRL:
            keys.release('ctrl')
        #print('--------------------')

if __name__=="__main__":
    mp.freeze_support()

    import configparser
    import cvFunc as cvf
    from ast import literal_eval

    SECTION = 'inventory'
    SECTION_BASIC_KEY = 'inventory_key'
    SECTION_ADD_KEY = 'inventory_to_stash'
    EDITED = 0
    configFile = os.path.dirname(os.path.realpath(__file__)) + '\\' + 'config.cfg'
    config = configparser.ConfigParser()
    #config.read(configFile)
    config.read_file(codecs.open(configFile, 'r', 'UTF-8-sig'))

    result = SECTION in config.sections()
    if result == False:
        config.add_section(SECTION)
    # ------- 인벤토리 실제 Size를 얻는 루틴
    if config.has_option(SECTION, 'inven_region'):
        inventory_size = literal_eval(config[SECTION]['inven_region'])
    else:
        print('"'"inven_region"'" Option Not Exist Take a Screenshot on Inventory')
        time.sleep(1)
        ip = cvf.ScreenShot()
        inventory_size = ip.partScreenShot()
        config[SECTION]['inven_region'] = str(inventory_size)
        EDITED =1

    if EDITED == 1:
        with open(configFile, 'w', encoding='UTF-8-sig') as config_file:
            config.write(config_file)



    #cookiesStr = ''
    #cookDict = makeCookie(cookiesStr)
    # ------- config.cfg에서 데이터를 가져오는 루틴
    url = 'https://www.pathofexile.com/'
    poeSessionID = literal_eval(readConfigFile(configFile, SECTION, 'poeSessionID'))
    cookDict = {'POESESSID': poeSessionID}
    accntName = readConfigFile(configFile, SECTION, 'accntName')
    character = readConfigFile(configFile, SECTION, 'character')



    #exceptCur = ['감정 주문서', '포탈 주문서']
    exceptCur = literal_eval(readConfigFile(configFile, SECTION, 'except_currency'))
    CHECK_ITME = readConfigFile(configFile, SECTION, 'confirm_item')
    sell = literal_eval(readConfigFile(configFile, SECTION, 'sell_item_category'))


    it = InventoryToolApi(inventory_size, exceptCur, sell)

    key1 = readConfigFile(configFile, SECTION_BASIC_KEY, 'key_get_inven_data')
    key2 = readConfigFile(configFile, SECTION_BASIC_KEY, 'key_currency_to_stash')
    key3 = readConfigFile(configFile, SECTION_BASIC_KEY, 'key_item_confirm')
    key4 = readConfigFile(configFile, SECTION_BASIC_KEY, 'key_item_sell')
    basicKey = [key1, key2, key3, key4]

    addKeyList = {}
    addKeyState = {}
    if config.has_section(SECTION_ADD_KEY):
        for opt in config.options(SECTION_ADD_KEY):
            addKeyList[opt] = config[SECTION_ADD_KEY][opt]
            addKeyState[opt] = 0


    listKeyState = [0] * len(basicKey)

    print('Inven Sorter Start !!!!!!!!!')
    # ------- Main 루틴
    while True:
        time.sleep(0.001)
        # ---- inventory_key Section 기능 수행
        for idx, pressedKey in enumerate(basicKey):
            if pressedKey != '':
                value = keys.is_pressed(pressedKey)

            if value == True:
                if idx == 0 and listKeyState[idx] != value:
                    resultDict = poeItemApiReq(accntName, character, cookDict, url)
                    if resultDict != None:
                        it.checkItemInInventoryWithAPI(resultDict)
                        runTextGui('Success')
                    else:
                        runTextGui('Fail')
                    listKeyState[idx] = value
                elif idx == 1 and listKeyState[idx] != value:
                    it.moveCurrencyToStash()
                    listKeyState[idx] = value
                elif idx == 2 and listKeyState[idx] != value:
                    it.itemConfirm()
                    listKeyState[idx] = value
                elif idx == 3 and listKeyState[idx] != value:
                    it.itemSell()
                    listKeyState[idx] = value


            else:
                if listKeyState[idx] != value:
                    listKeyState[idx] = value
        # ----- inventory_to_stash Section 기능 수행
        for opt, pressedKey in addKeyList.items():
            value = keys.is_pressed(pressedKey)

            if value == True:
                if addKeyState[opt] != value:
                    # print(opt, pressedKey)
                    it.moveItemToStash(opt)
                    addKeyState[opt] = value

            else:
                if addKeyState[opt] != value:
                    addKeyState[opt] = value

 

 

넵 수고하셧습니다. ㅎㅎㅎ 이번 포스팅은 이걸로 마치고요 즐거운 매핑 되세요 유배자님들 ~~~ ㅋㅋ



블로그 구독하기 !!

You may also like...

7 Responses

  1. ㅇㅇ 댓글:

    잘됩니다 잘 사용할게요 ㅎ~!

    • 호그 댓글:

      감솨 합니다. ㅎㅎ 영정 주의하시고요 ~ ㅜ

      • ㅇㅇ 댓글:

        네 ㅎㅎ 모르게 사용해야죠… 근데 인벤토리 데이터 새로불러올 때 창한번 껏다가 다시켜야 바로 데이터가 불러오지더라구요. 그부분도 설명추가하면 좋을거같네요~!

        • 호그 댓글:

          앗 네 알겠습니다. ㅎㅎ 저도 비슷한 현상을 격었는데 저는 크롬을 새로 고침하니까 되더라고요 너무 많이 요청하면 제안 하는 건지 원인은 잘 모르겠더라고요 ㅜㅜ

  2. 조재 댓글:

    안녕하세요.
    질문이 있어서 답글을 남깁니다.
    현재는 감정 스크롤이 캐릭터 인벤에 있는 상태에서 하는데
    저는 보통 커런시 창고에 있는걸 쓰거든요

    그럼 그냥 169 번 줄의
    mo.move(checkItemPoint[0], checkItemPoint[1])
    여기를 절대값으로 창고안의 스크롤 위치로 바꿔서 쓰면 될까요?

  3. 슈베어 댓글:

    오…..? 댓글이 삭제 됬네요……?
    뭐지………..??