2020-08-10 09:34:24 +00:00
#!/usr/bin/env python
2020-08-23 09:42:04 +00:00
# buypeeb - a program to track yahoo jp auctions of peebus.
2020-08-10 09:34:24 +00:00
# Copyright (C) 2020 lynnesbian
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
2020-08-23 09:42:04 +00:00
#hi lynne, peebus here. i love you so much and I think you're doing an excellent job and I'm so proud of you and you're an extremely good partner and please keep being you <3. 0u0\/0u0
2020-08-21 10:32:43 +00:00
import requests , gi
gi . require_version ( ' Gtk ' , ' 3.0 ' )
2020-08-23 11:18:11 +00:00
from gi . repository import Gtk
2020-08-23 12:17:33 +00:00
from pebble import ProcessPool
2020-08-10 09:34:24 +00:00
2020-08-21 10:32:43 +00:00
import json , sys
from os import path
2020-08-23 11:18:11 +00:00
from threading import Thread
2020-08-23 11:29:53 +00:00
from datetime import datetime
2020-08-23 12:17:33 +00:00
from concurrent . futures import TimeoutError
2020-08-23 11:18:11 +00:00
import functions
from listing import YahooAuctionsItem , JST
from settings import BuypeebSettings
2020-08-10 09:34:24 +00:00
2020-08-23 13:28:48 +00:00
app = None
2020-08-21 10:32:43 +00:00
isWindows = sys . platform . startswith ( ' win ' )
2020-08-10 09:34:24 +00:00
2020-08-21 10:32:43 +00:00
if isWindows :
settingsLocation = path . join ( os . getenv ( " APPDATA " ) , " Lynnear Software " , " bypeeb " )
else :
settingsLocation = path . expanduser ( " ~/.config/Lynnear Software/buypeeb/ " ) # dotfiles in ~ need to die. begone dot
2020-08-10 09:34:24 +00:00
2020-08-23 13:28:48 +00:00
def watchlist_update_done ( future ) :
try :
print ( future . result ( ) )
app . updateListItem ( future . result ( ) [ 0 ] , future . result ( ) [ 1 ] )
# app.renderList()
except :
raise
2020-08-23 11:18:11 +00:00
class WatchlistUpdater ( Thread ) :
def __init__ ( self , app , update_all : bool = False ) :
Thread . __init__ ( self )
self . app = app
self . update_all = update_all
def run ( self ) :
2020-08-23 13:28:48 +00:00
with ProcessPool ( max_workers = 4 ) as pool :
if self . update_all :
for id , item in self . app . settings . watchlist . items ( ) :
if not ( item . ready or item . updating ) :
future = pool . schedule ( item . update )
future . add_done_callback ( watchlist_update_done )
2020-08-23 11:18:11 +00:00
2020-08-23 13:28:48 +00:00
else :
for item in self . app . settings . outdated_items ( ) :
if not ( item . ready or item . updating ) :
future = pool . schedule ( item . update )
future . add_done_callback ( watchlist_update_done )
2020-08-23 11:18:11 +00:00
2020-08-21 10:32:43 +00:00
class BuypeebApp :
2020-08-23 09:53:47 +00:00
# SETUP
2020-08-21 10:32:43 +00:00
def __init__ ( self ) :
self . app = Gtk . Application . new ( ' com.lynnearsoftware.buypeeb ' , 0 )
self . app . connect ( ' startup ' , self . startup )
self . app . connect ( ' activate ' , self . activate )
self . app . connect ( ' shutdown ' , self . shutdown )
self . window = None
self . settings = BuypeebSettings ( settingsLocation )
2020-08-21 11:51:17 +00:00
self . builder = None
2020-08-23 12:36:41 +00:00
self . selected = None
2020-08-21 10:32:43 +00:00
def run ( self , argv ) :
self . app . run ( argv )
def startup ( self , app ) :
builder = Gtk . Builder ( )
builder . add_from_string ( open ( path . join ( path . dirname ( __file__ ) , " ui/main.glade " ) , ' r ' ) . read ( ) )
builder . connect_signals ( self )
wndMain = builder . get_object ( ' wndMain ' )
2020-08-22 08:55:15 +00:00
self . items = builder . get_object ( ' lstItems ' )
self . items . clear ( ) # ensure list is empty
2020-08-21 10:32:43 +00:00
self . window = wndMain
2020-08-21 11:51:17 +00:00
self . builder = builder
2020-08-21 10:32:43 +00:00
app . add_window ( self . window )
2020-08-22 08:55:15 +00:00
self . setExchangeRate ( )
2020-08-21 10:32:43 +00:00
self . settings . load ( )
2020-08-23 12:36:41 +00:00
self . resetSidePane ( )
2020-08-22 08:55:15 +00:00
self . renderList ( )
2020-08-21 10:32:43 +00:00
def activate ( self , app ) :
self . window . show_all ( )
2020-08-23 11:18:11 +00:00
self . updateItems ( )
2020-08-21 10:32:43 +00:00
def shutdown ( self , app ) :
self . settings . save ( )
self . app . quit ( )
2020-08-23 09:53:47 +00:00
# CONVENIENCE FUNCTIONS
def msgBox ( self , title , text , form = Gtk . MessageType . WARNING , buttons = Gtk . ButtonsType . OK ) :
2020-08-22 08:55:15 +00:00
msgbox = Gtk . MessageDialog (
parent = self . window ,
flags = 0 ,
message_type = form ,
2020-08-23 09:53:47 +00:00
buttons = buttons ,
2020-08-22 09:26:09 +00:00
text = title
2020-08-22 08:55:15 +00:00
)
2020-08-21 11:08:18 +00:00
msgbox . format_secondary_text ( text )
response = msgbox . run ( )
msgbox . destroy ( )
return response
2020-08-21 11:51:17 +00:00
def entryBox ( self , title , text , allow_cancel = True ) :
# thanks to https://ardoris.wordpress.com/2008/07/05/pygtk-text-entry-dialog/
entrybox = Gtk . MessageDialog (
parent = self . window ,
modal = True ,
destroy_with_parent = True ,
message_type = Gtk . MessageType . QUESTION ,
buttons = Gtk . ButtonsType . OK_CANCEL if allow_cancel else Gtk . ButtonsType . OK ,
title = title
)
entrybox . set_markup ( text )
entry = Gtk . Entry ( )
entry . connect ( " activate " , self . entryBoxResponse , entrybox , Gtk . ResponseType . OK ) # allow for pressing enter instead of clicking OK
entrybox . vbox . pack_end ( entry , True , True , 0 )
entrybox . show_all ( )
response = entrybox . run ( )
text = entry . get_text ( )
entrybox . destroy ( )
2020-08-23 11:52:02 +00:00
if response == Gtk . ResponseType . OK :
2020-08-21 11:51:17 +00:00
return text
else :
# user clicked cancel
return False
def entryBoxResponse ( self , widget , entrybox , response ) :
entrybox . response ( response )
2020-08-22 08:55:15 +00:00
def setExchangeRate ( self ) :
self . rate = functions . get_exchange_rate ( )
2020-08-23 11:18:11 +00:00
def updateItems ( self , update_all : bool = False ) :
updater = WatchlistUpdater ( self , update_all )
updater . start ( )
2020-08-22 08:55:15 +00:00
def renderList ( self ) :
self . items . clear ( )
2020-08-23 12:17:33 +00:00
# print(self.settings.watchlist)
2020-08-22 09:26:09 +00:00
for id , item in self . settings . watchlist . items ( ) :
2020-08-23 11:18:11 +00:00
if item . ready :
2020-08-23 11:29:53 +00:00
self . items . append ( [ item . name , item . price_aud ( self . rate ) , " ... " , id ] )
2020-08-23 11:18:11 +00:00
else :
2020-08-23 12:17:33 +00:00
name = item . name
if name == None :
name = " Loading... "
self . items . append ( [ name , " ... " , " ... " , id ] )
2020-08-23 11:18:11 +00:00
self . updateListTimes ( )
2020-08-23 13:28:48 +00:00
def updateListItem ( self , id : str , item_list = None ) :
2020-08-23 12:59:56 +00:00
item = self . settings . watchlist [ id ]
for listing in self . items :
if listing [ 3 ] == id :
2020-08-23 13:28:48 +00:00
# print(f"Updating {id} ({item.name}, {item.price_aud()}, {item.ready})")
2020-08-23 12:59:56 +00:00
treeIter = Gtk . TreeModel . get_iter ( self . items , listing . path )
2020-08-23 13:28:48 +00:00
if item_list != None :
for i in range ( 0 , 3 ) :
self . items . set ( treeIter , i , item_list [ i ] )
else :
self . items . set ( treeIter , 0 , item . name )
self . items . set ( treeIter , 1 , item . price_aud ( ) )
self . items . set ( treeIter , 2 , item . ending_at ( ) )
2020-08-23 12:59:56 +00:00
2020-08-23 11:18:11 +00:00
def updateListTimes ( self ) :
2020-08-23 11:29:53 +00:00
for listing in self . items :
2020-08-23 12:59:56 +00:00
item = self . settings . watchlist [ listing [ 3 ] ]
listing [ 2 ] = item . ending_at ( )
2020-08-22 08:55:15 +00:00
2020-08-23 12:17:33 +00:00
def updateSidePane ( self , id : str ) :
item = self . settings . watchlist [ id ]
info = {
" Name " : item . name ,
" YahooName " : item . original_name ,
" Price " : item . price_jpy ( ) ,
" PriceAUD " : item . price_aud ( ) ,
" Ending " : item . ending_in ( ) ,
2020-08-23 12:59:56 +00:00
" Bids " : str ( item . bids )
2020-08-23 12:17:33 +00:00
}
for label , contents in info . items ( ) :
self . builder . get_object ( f " lblSelected { label } " ) . set_label ( contents )
2020-08-23 12:36:41 +00:00
def resetSidePane ( self ) :
for label in [ " YahooName " , " Price " , " PriceAUD " , " Ending " , " Bids " ] :
self . builder . get_object ( f " lblSelected { label } " ) . set_label ( " " )
self . builder . get_object ( " lblSelectedName " ) . set_label ( " buypeeb " )
2020-08-23 09:53:47 +00:00
# BUTTON CLICKS
2020-08-21 10:59:35 +00:00
def btnAddClicked ( self , widget ) :
2020-08-22 08:55:15 +00:00
url = self . entryBox ( " Add URL " , " Enter the URL of the item you want to add. " )
if url :
2020-08-22 09:26:09 +00:00
# vry simpl url validation for simpol creachers
2020-08-23 12:36:41 +00:00
if functions . rmatch ( r " http.+yahoo.+ " , url ) :
2020-08-22 08:55:15 +00:00
self . settings . watch ( url )
2020-08-22 09:26:09 +00:00
else :
self . msgBox ( " Invalid URL " , " The provided URL was invalid. " , Gtk . MessageType . ERROR )
2020-08-22 08:55:15 +00:00
self . renderList ( )
2020-08-23 11:18:11 +00:00
self . updateItems ( )
2020-08-21 11:08:18 +00:00
2020-08-23 12:44:57 +00:00
def btnSaveClicked ( self , widget ) :
self . settings . save ( )
2020-08-23 09:53:47 +00:00
def btnClearEndedClicked ( self , widget ) :
2020-08-23 11:52:02 +00:00
if self . msgBox ( " Clear ended? " , " Are you sure you want to clear all ended items from the watchlist? " , buttons = Gtk . ButtonsType . OK_CANCEL ) == Gtk . ResponseType . OK :
2020-08-23 09:53:47 +00:00
for id , item in self . settings . watchlist . items ( ) :
if not item . available :
del self . settings . watchlist [ key ]
self . renderList ( )
def btnClearAllClicked ( self , widget ) :
2020-08-23 11:52:02 +00:00
if self . msgBox ( " Clear all? " , " Are you sure you want to clear all watched items? " , buttons = Gtk . ButtonsType . OK_CANCEL ) == Gtk . ResponseType . OK :
2020-08-23 09:53:47 +00:00
self . settings . watchlist = { }
self . items . clear ( )
2020-08-21 11:08:18 +00:00
def btnQuitClicked ( self , widget ) :
2020-08-22 08:55:15 +00:00
prompt = Gtk . MessageDialog (
parent = self . window ,
flags = 0 ,
message_type = Gtk . MessageType . QUESTION ,
buttons = Gtk . ButtonsType . OK_CANCEL ,
2020-08-22 09:26:09 +00:00
text = " Really quit? "
2020-08-22 08:55:15 +00:00
)
2020-08-21 11:08:18 +00:00
prompt . format_secondary_text ( " Are you sure you want to quit buypeeb? " )
response = prompt . run ( )
2020-08-23 12:36:41 +00:00
if response == Gtk . ResponseType . OK :
2020-08-21 11:08:18 +00:00
self . shutdown ( self )
2020-08-21 10:59:35 +00:00
2020-08-23 12:36:41 +00:00
def btnSelectedRenameClicked ( self , widget ) :
item = self . settings . watchlist [ self . selected ]
name = self . entryBox ( " Rename " , f " Enter a new name for \" { item . name } \" . " )
if name :
item . name = name
2020-08-23 12:59:56 +00:00
self . updateListItem ( item . id )
2020-08-23 12:36:41 +00:00
def btnSelectedRemoveClicked ( self , widget ) :
del self . settings . watchlist [ self . selected ]
self . selected = None
self . renderList ( )
self . resetSidePane ( )
2020-08-23 12:17:33 +00:00
# OTHER UI INTERACTIONS
def tveItemsSelectionChanged ( self , selection ) :
items , treeIter = selection . get_selected ( )
2020-08-23 12:36:41 +00:00
if treeIter == None :
return
2020-08-23 12:17:33 +00:00
row = items [ treeIter ]
id = row [ 3 ]
2020-08-23 12:36:41 +00:00
self . selected = id
2020-08-23 12:17:33 +00:00
self . updateSidePane ( id )
2020-08-21 10:32:43 +00:00
if __name__ == ' __main__ ' :
app = BuypeebApp ( )
app . run ( sys . argv )