2022-11-07 15:35:07 +05:00
import requests
2022-11-07 14:35:58 +05:00
import curses , curses . textpad
2023-08-02 16:00:06 +05:00
import subprocess
import sys
import json
2022-12-15 14:10:41 +05:00
2023-08-02 16:00:06 +05:00
# API access handler
2022-11-07 15:35:07 +05:00
class Session ( ) :
def __init__ ( self ) :
self . client = requests . Session ( )
def api_request ( self , url = ' / ' , method = ' get ' , * * kwargs ) :
2023-08-02 16:00:06 +05:00
# read only, errors should force crash with no damage
2022-11-07 15:35:07 +05:00
return json . loads ( getattr ( self . client , method ) ( ' https://api.warframe.market/v1 ' + url , * * kwargs ) . text )
2022-11-04 13:16:10 +05:00
2022-11-07 03:14:37 +05:00
2023-08-02 16:00:06 +05:00
# main terminal element renderer
2022-11-04 15:50:51 +05:00
class GUI ( ) :
def __init__ ( self , root , h , w , y , x ) :
self . root = root
self . window = root . subwin ( h , w , y , x )
def create_gui ( self ) :
self . create_search_bar ( )
2022-11-07 03:14:37 +05:00
self . create_item_search_results ( )
2022-11-07 14:35:58 +05:00
self . create_order_search_results ( )
2023-08-02 16:00:06 +05:00
2022-11-07 14:35:58 +05:00
self . root . refresh ( )
2022-11-04 15:50:51 +05:00
def create_search_bar ( self ) :
2023-08-02 16:00:06 +05:00
self . search_box = self . window . subwin ( 3 , self . window . getmaxyx ( ) [ 1 ] , 0 , 0 ) # 2 extra lines for border
2022-11-04 15:50:51 +05:00
self . search_box . border ( )
2023-08-02 16:00:06 +05:00
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
2022-11-04 15:50:51 +05:00
2022-11-07 03:14:37 +05:00
def create_item_search_results ( self ) :
2023-08-02 16:00:06 +05:00
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
2022-11-04 15:50:51 +05:00
self . results_box . border ( )
2022-11-07 14:35:58 +05:00
def create_order_search_results ( self ) :
2023-08-02 16:00:06 +05:00
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
2022-11-07 14:35:58 +05:00
self . orders_box . border ( )
2023-08-02 16:00:06 +05:00
# central class handling states and user interactions
2022-11-04 15:50:51 +05:00
class App ( ) :
def __init__ ( self , root ) :
self . root = root
self . gui = GUI ( self . root , * self . root . getmaxyx ( ) , 0 , 0 )
2022-11-07 03:14:37 +05:00
self . client = Session ( )
2023-08-02 16:00:06 +05:00
self . item_list = self . client . api_request ( url = ' /items ' ) . get ( ' payload ' ) . get ( ' items ' ) # fetch all items as cache on start
2022-11-04 15:50:51 +05:00
def show_gui ( self ) :
self . root . clear ( )
self . gui . create_gui ( )
2022-11-07 03:14:37 +05:00
def update_item_search_results ( self ) :
2023-08-02 16:00:06 +05:00
search_str = self . gui . search_in . gather ( ) # read current search box text, match item list to show, truncate at gui height, no scrolling implemented
2022-11-07 14:35:58 +05:00
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 ]
2022-11-07 03:14:37 +05:00
self . gui . results_box . clear ( )
self . gui . results_box . border ( )
for i in range ( len ( items ) ) :
try :
2023-08-02 16:00:06 +05:00
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
2022-11-07 03:14:37 +05:00
except :
2022-11-07 14:35:58 +05:00
break
2022-11-07 03:14:37 +05:00
self . gui . results_box . refresh ( )
2023-08-02 16:00:06 +05:00
self . items = items # for selection
2022-11-07 14:35:58 +05:00
def update_orders_results ( self ) :
2023-08-02 16:00:06 +05:00
# query API for orders with item, sort by plat
2022-11-07 14:35:58 +05:00
orders = sorted ( self . client . api_request ( url = f ' /items/ { self . selected_item [ " url_name " ] } /orders ' ) . get ( ' payload ' ) . get ( ' orders ' ) , key = lambda x : x [ ' platinum ' ] )
2023-08-02 16:00:06 +05:00
# restrict to buy and online in-game, truncate at height
2022-11-07 14:35:58 +05:00
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 ]
2023-08-02 16:00:06 +05:00
self . buy_orders . reverse ( ) # reverse sort for sells
# restrict to sells and online in-game, truncate at height
2022-11-07 14:35:58 +05:00
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 ( )
2023-08-02 16:00:06 +05:00
# display item listing str for each order
2022-11-07 14:35:58 +05:00
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 ' , ' ' ) ) )
except :
break
for i in range ( len ( self . buy_orders ) ) :
try :
self . gui . orders_box . addstr ( i + 1 , self . gui . orders_box . getmaxyx ( ) [ 1 ] / / 2 , ' {:20.20} {: >3} p x {: <3} {: <10} ' . format ( self . buy_orders [ i ] [ ' user ' ] [ ' ingame_name ' ] , self . buy_orders [ i ] [ ' platinum ' ] , self . buy_orders [ i ] [ ' quantity ' ] , self . buy_orders [ i ] . get ( ' mod_rank ' , ' ' ) ) )
except :
break
self . gui . orders_box . refresh ( )
def decide_state ( self ) :
2023-08-02 16:00:06 +05:00
# decide next action and input state
2022-11-07 15:30:28 +05:00
if self . state == 0 :
return self . search
elif self . state == 1 :
return self . select_item
elif self . state == 2 :
return self . select_order
2022-11-07 14:35:58 +05:00
def run ( self ) :
2023-08-02 16:00:06 +05:00
# initialize at search state
2022-11-07 14:35:58 +05:00
self . show_gui ( )
self . state = 0
while True :
self . decide_state ( ) ( )
def search ( self ) :
2023-08-02 16:00:06 +05:00
# when search box selected, allow edit, update item listing when user done
2022-11-07 14:35:58 +05:00
self . gui . search_in . edit ( )
self . update_item_search_results ( )
2023-08-02 16:00:06 +05:00
# move to item selection state
2022-11-07 14:35:58 +05:00
self . state = 1
def select_item ( self ) :
2023-08-02 16:00:06 +05:00
# move cursor pos to first listing in search results, wait for intpu
2022-11-07 14:35:58 +05:00
self . gui . results_box . move ( 1 , 1 )
self . gui . results_box . refresh ( )
x = self . root . getch ( )
2023-08-02 16:00:06 +05:00
# implement item scrolling if enter or s
2022-11-07 14:35:58 +05:00
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 )
elif x == curses . KEY_DOWN :
self . gui . results_box . move ( ( self . gui . results_box . getyx ( ) [ 0 ] - 1 + 1 ) % len ( self . items ) + 1 , 1 )
self . gui . results_box . refresh ( )
x = self . root . getch ( )
2023-08-02 16:00:06 +05:00
# on return
2022-11-07 14:35:58 +05:00
if x == 10 :
2023-08-02 16:00:06 +05:00
# 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
2022-12-15 14:10:41 +05:00
subprocess . run ( f ' echo \' { self . gui . results_box . getyx ( ) } \' | xclip -sel c ' , shell = True )
2023-08-02 16:00:06 +05:00
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
2022-11-07 14:35:58 +05:00
else :
2023-08-02 16:00:06 +05:00
self . state = 0 # otherwise s returns to search state
2022-11-07 14:35:58 +05:00
def select_order ( self ) :
2023-08-02 16:00:06 +05:00
# move cursor to order listing
2022-11-07 14:35:58 +05:00
self . gui . orders_box . move ( 1 , 1 )
self . gui . orders_box . refresh ( )
x = self . root . getch ( )
2023-08-02 16:00:06 +05:00
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
2022-11-07 14:35:58 +05:00
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 )
elif x == curses . KEY_DOWN :
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 )
elif x == curses . KEY_LEFT or x == curses . KEY_RIGHT :
col = int ( not col )
self . gui . orders_box . move ( min ( self . gui . orders_box . getyx ( ) [ 0 ] , len ( self . buy_orders if col else self . sell_orders ) ) , 1 if col == 0 else self . gui . orders_box . getmaxyx ( ) [ 1 ] / / 2 )
self . gui . orders_box . refresh ( )
x = self . root . getch ( )
2023-08-02 16:00:06 +05:00
# if enter, get index of order and copy whisper text
2022-11-07 14:35:58 +05:00
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 ( )
2023-08-02 16:00:06 +05:00
# if s, go back to search
2022-11-07 14:35:58 +05:00
elif x == 115 :
self . state = 0
2023-08-02 16:00:06 +05:00
# elif i, go to item select
2022-11-07 14:35:58 +05:00
else :
self . state = 1
def copy_whisper ( self ) :
2023-08-02 16:00:06 +05:00
# copy whisper to clipboard, again xclip, replace as needed, structure:
# WTB/WTS item_name listed for plat_val on warframe.market.
2022-11-07 14:35:58 +05:00
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 )
2022-11-07 03:14:37 +05:00
2022-11-04 15:50:51 +05:00
def main ( w ) :
app = App ( w )
2022-11-07 14:35:58 +05:00
app . run ( )
2022-11-04 15:50:51 +05:00
2022-11-07 14:35:58 +05:00
try :
2023-08-02 16:00:06 +05:00
curses . wrapper ( main ) # runs main, curses handles exit wierdness
2022-11-07 14:35:58 +05:00
except KeyboardInterrupt :
print ( ' Exiting ' )
2022-11-07 15:35:07 +05:00
except requests . exceptions . ConnectionError :
2022-11-07 14:35:58 +05:00
print ( " No Internet " )
sys . exit ( 1 )