diff --git a/tui.py b/tui.py index f6cbbc3..8ec73aa 100644 --- a/tui.py +++ b/tui.py @@ -1,18 +1,21 @@ import requests import curses, curses.textpad -import subprocess, sys, json - +import subprocess +import sys +import json +# API access handler class Session(): def __init__(self): self.client = requests.Session() def api_request(self, url='/', method='get', **kwargs): + # read only, errors should force crash with no damage return json.loads(getattr(self.client, method)('https://api.warframe.market/v1' + url, **kwargs).text) - +# main terminal element renderer class GUI(): def __init__(self, root, h, w, y, x): self.root = root @@ -22,55 +25,60 @@ class GUI(): self.create_search_bar() self.create_item_search_results() self.create_order_search_results() + self.root.refresh() def create_search_bar(self): - self.search_box = self.window.subwin(3, self.window.getmaxyx()[1], 0, 0) + self.search_box = self.window.subwin(3, self.window.getmaxyx()[1], 0, 0) # 2 extra lines for border self.search_box.border() - self.search_bar = self.window.subwin(1, self.window.getmaxyx()[1] - 2, 1, 1) - self.search_in = curses.textpad.Textbox(self.search_bar) + self.search_bar = self.window.subwin(1, self.window.getmaxyx()[1] - 2, 1, 1) # actual search box + self.search_in = curses.textpad.Textbox(self.search_bar) # just cursed nested vars at this point, really only need this one for searching def create_item_search_results(self): - self.results_box = self.window.subwin(self.window.getmaxyx()[0] - 3, self.window.getmaxyx()[1] // 3, 3, 0) + self.results_box = self.window.subwin(self.window.getmaxyx()[0] - 3, self.window.getmaxyx()[1] // 3, 3, 0) # skip y of search box, occupy left 1/3 of screen self.results_box.border() def create_order_search_results(self): - self.orders_box = self.window.subwin(self.window.getmaxyx()[0] - 3, self.window.getmaxyx()[1] * 2 // 3, 3, self.window.getmaxyx()[1] // 3) + self.orders_box = self.window.subwin(self.window.getmaxyx()[0] - 3, self.window.getmaxyx()[1] * 2 // 3, 3, self.window.getmaxyx()[1] // 3) # see item search self.orders_box.border() - +# central class handling states and user interactions class App(): def __init__(self, root): self.root = root self.gui = GUI(self.root, *self.root.getmaxyx(), 0, 0) self.client = Session() - self.item_list = self.client.api_request(url = '/items').get('payload').get('items') + self.item_list = self.client.api_request(url = '/items').get('payload').get('items') # fetch all items as cache on start def show_gui(self): self.root.clear() self.gui.create_gui() def update_item_search_results(self): - search_str = self.gui.search_in.gather() + search_str = self.gui.search_in.gather() # read current search box text, match item list to show, truncate at gui height, no scrolling implemented items = [item for item in self.item_list if search_str.strip().lower() in item.get('item_name').lower()][:self.gui.results_box.getmaxyx()[0] - 2] self.gui.results_box.clear() self.gui.results_box.border() for i in range(len(items)): try: - self.gui.results_box.addstr(i + 1, 1, items[i]['item_name'][:self.gui.results_box.getmaxyx()[1] - 2]) + self.gui.results_box.addstr(i + 1, 1, items[i]['item_name'][:self.gui.results_box.getmaxyx()[1] - 2]) # display name, pos+border adjust, truncate at gui width except: break self.gui.results_box.refresh() - self.items = items + self.items = items # for selection def update_orders_results(self): + # query API for orders with item, sort by plat orders = sorted(self.client.api_request(url = f'/items/{self.selected_item["url_name"]}/orders').get('payload').get('orders'), key=lambda x: x['platinum']) + # restrict to buy and online in-game, truncate at height self.buy_orders = [order for order in orders if order['order_type'] == 'buy' and order['user']['status'] == 'ingame'][:self.gui.orders_box.getmaxyx()[0] - 2] - self.buy_orders.reverse() + self.buy_orders.reverse() # reverse sort for sells + # restrict to sells and online in-game, truncate at height self.sell_orders = [order for order in orders if order['order_type'] == 'sell' and order['user']['status'] == 'ingame'][:self.gui.orders_box.getmaxyx()[0] - 2] self.gui.orders_box.clear() self.gui.orders_box.border() + # display item listing str for each order for i in range(len(self.sell_orders)): try: self.gui.orders_box.addstr(i + 1, 1, '{:20.20} {: >3}p x{: <3} {: <10}'.format(self.sell_orders[i]['user']['ingame_name'], self.sell_orders[i]['platinum'], self.sell_orders[i]['quantity'], self.sell_orders[i].get('mod_rank', ''))) @@ -84,6 +92,7 @@ class App(): self.gui.orders_box.refresh() def decide_state(self): + # decide next action and input state if self.state == 0: return self.search elif self.state == 1: @@ -92,6 +101,7 @@ class App(): return self.select_order def run(self): + # initialize at search state self.show_gui() self.state = 0 while True: @@ -99,14 +109,19 @@ class App(): def search(self): + # when search box selected, allow edit, update item listing when user done self.gui.search_in.edit() self.update_item_search_results() + + # move to item selection state self.state = 1 def select_item(self): + # move cursor pos to first listing in search results, wait for intpu self.gui.results_box.move(1, 1) self.gui.results_box.refresh() x = self.root.getch() + # implement item scrolling if enter or s while x != 10 and x != 115: if x == curses.KEY_UP: self.gui.results_box.move((self.gui.results_box.getyx()[0] - 1 - 1) % len(self.items) + 1, 1) @@ -115,19 +130,25 @@ class App(): self.gui.results_box.refresh() x = self.root.getch() + # on return if x == 10: + # copy to clipboard, requires xclip obv, replace however, change for other, no idea why I wrote this part? only copy should be in order sel subprocess.run(f'echo \'{self.gui.results_box.getyx()}\' | xclip -sel c', shell = True) - self.selected_item = self.items[self.gui.results_box.getyx()[0] - 1] - self.update_orders_results() - self.state = 2 + self.selected_item = self.items[self.gui.results_box.getyx()[0] - 1] # get cursor position, select corresponding item + self.update_orders_results() # update orders listing from selection + self.state = 2 # move to order selection else: - self.state = 0 + self.state = 0 # otherwise s returns to search state def select_order(self): + # move cursor to order listing self.gui.orders_box.move(1, 1) self.gui.orders_box.refresh() x = self.root.getch() - col = 0 + col = 0 # for switching between buy and sell cols + + # if not s, i or enter, implement selection + # same as items, but also left and right moves between buy and sell while x != 10 and x != 115 and x != 105: if x == curses.KEY_UP: self.gui.orders_box.move((self.gui.orders_box.getyx()[0] - 1 - 1) % len(self.sell_orders if col == 0 else self.buy_orders) + 1, 1 if col == 0 else self.gui.orders_box.getmaxyx()[1] // 2) @@ -139,15 +160,20 @@ class App(): self.gui.orders_box.refresh() x = self.root.getch() + # if enter, get index of order and copy whisper text if x == 10: self.selected_order = (self.buy_orders if col else self.sell_orders)[self.gui.orders_box.getyx()[0] - 1] self.copy_whisper() + # if s, go back to search elif x == 115: self.state = 0 + # elif i, go to item select else: self.state = 1 def copy_whisper(self): + # copy whisper to clipboard, again xclip, replace as needed, structure: + # WTB/WTS item_name listed for plat_val on warframe.market. whisper = f'\\w {self.selected_order["user"]["ingame_name"]} {"WTS" if self.selected_order["order_type"] == "buy" else "WTB"} {self.selected_item["item_name"]} listed for {self.selected_order["platinum"]}p on warframe.market' subprocess.run(f'echo \'{whisper}\' | xclip -sel c', shell = True) @@ -158,7 +184,7 @@ def main(w): app.run() try: - curses.wrapper(main) + curses.wrapper(main) # runs main, curses handles exit wierdness except KeyboardInterrupt: print('Exiting') except requests.exceptions.ConnectionError: