Compare commits

..

5 commits

4 changed files with 168 additions and 19 deletions

View file

@ -1,6 +1,6 @@
#!/usr/bin/env python
# <one line to give the program's name and a brief idea of what it does.>
# buypeeb - a program to track yahoo jp auctions of peebus.
# Copyright (C) 2020 lynnesbian
# This program is free software: you can redistribute it and/or modify
@ -16,16 +16,20 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#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
import requests, gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, Gdk
import functions
from listing import YahooAuctionsItem
from settings import BuypeebSettings
from gi.repository import Gtk
import json, sys
from os import path
from threading import Thread
from datetime import datetime
import functions
from listing import YahooAuctionsItem, JST
from settings import BuypeebSettings
isWindows = sys.platform.startswith('win')
@ -34,7 +38,29 @@ if isWindows:
else:
settingsLocation = path.expanduser("~/.config/Lynnear Software/buypeeb/") # dotfiles in ~ need to die. begone dot
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):
# TODO: make this happen in parallel
if self.update_all:
for id, item in self.app.settings.watchlist.items():
if not (item.ready or item.updating):
item.update()
else:
for item in self.app.settings.outdated_items():
if not(item.ready or item.updating):
item.update()
self.app.renderList()
class BuypeebApp:
# SETUP
def __init__(self):
self.app = Gtk.Application.new('com.lynnearsoftware.buypeeb', 0)
self.app.connect('startup', self.startup)
@ -64,17 +90,20 @@ class BuypeebApp:
def activate(self, app):
self.window.show_all()
self.updateItems()
def shutdown(self, app):
self.settings.save()
self.app.quit()
def msgBox(self, title, text, form = Gtk.MessageType.WARNING):
# CONVENIENCE FUNCTIONS
def msgBox(self, title, text, form = Gtk.MessageType.WARNING, buttons = Gtk.ButtonsType.OK):
msgbox = Gtk.MessageDialog(
parent = self.window,
flags = 0,
message_type = form,
buttons = Gtk.ButtonsType.OK,
buttons = buttons,
text = title
)
@ -116,23 +145,60 @@ class BuypeebApp:
def setExchangeRate(self):
self.rate = functions.get_exchange_rate()
def updateItems(self, update_all: bool = False):
updater = WatchlistUpdater(self, update_all)
updater.start()
def renderList(self):
self.items.clear()
print(self.settings.watchlist)
for id, item in self.settings.watchlist.items():
print("heeh hoo")
self.items.append([item.name, item.price_aud(self.rate), "heenlo", id])
if item.ready:
self.items.append([item.name, item.price_aud(self.rate), "...", id])
else:
self.items.append([item.name, "...", "...", id])
self.updateListTimes()
def updateListTimes(self):
now = datetime.now()
ndate = now.strftime("%d %b")
for listing in self.items:
id = listing[3]
item = self.settings.watchlist[id]
if item.end_date != None:
idate, itime = item.end_date.strftime("%d %b"), item.end_date.strftime("%H:%M")
if idate == ndate:
listing[2] = itime
else:
listing[2] = f"{idate} {itime}"
# BUTTON CLICKS
def btnAddClicked(self, widget):
url = self.entryBox("Add URL", "Enter the URL of the item you want to add.")
if url:
# vry simpl url validation for simpol creachers
if functions.rmatch("https?.+yahoo.+", url):
if functions.rmatch(r"https?.+yahoo.+", url):
self.settings.watch(url)
else:
self.msgBox("Invalid URL", "The provided URL was invalid.", Gtk.MessageType.ERROR)
self.renderList()
self.updateItems()
def btnClearEndedClicked(self, widget):
if self.msgBox("Clear ended?", "Are you sure you want to clear all ended items from the watchlist?", buttons = Gtk.ButtonsType.OK_CANCEL):
for id, item in self.settings.watchlist.items():
if not item.available:
del self.settings.watchlist[key]
self.renderList()
def btnClearAllClicked(self, widget):
if self.msgBox("Clear all?", "Are you sure you want to clear all watched items?", buttons = Gtk.ButtonsType.OK_CANCEL):
self.settings.watchlist = {}
self.items.clear()
def btnQuitClicked(self, widget):
prompt = Gtk.MessageDialog(

View file

@ -8,9 +8,59 @@ import functions
JST = timezone(timedelta(hours = 9))
class YahooAuctionsItem:
"""
A class for handling items on Yahoo! Auctions Japan
Attributes
----------
name : str
The name given to the auction item by the user.
original_name : str
The name as it appears on Y!A,
favourite : bool
Whether or not the user has "favourited" this item.
url : bool
The URL this item was added from. If not provided, defaults to https://buyee.jp/item/yahoo/auction/{id}.
id : str
The ID of the item, found at the end of the URL.
last_checked : datetime
The time that the item's status was last checked by buypeeb.
available : bool
Whether or not the item is still available (if the auction is still available)
ready : bool
Whether or not the object is ready. If not, some of the data (price, available, etc.) may be out of date or unset.
updating : bool
If true, the object is currently being updated. Should never be true if ready is true.
price : float
The price of the item in yen.
bids : int
The number of bids that have been placed on the item.
start_date : datetime
The start date and time of the item in JST.
end_date : datetime
The end date and time of the item in JST.
"""
def __init__(self, url: str, id: str, name: str = None, from_json: dict = None):
# note - incoming url is not validated in any way!
self.name = name
self.price = 0
self.original_name = None
self.favourite = None
self.end_date = None
if url == None and from_json != None and id != None:
self.id = id
self.name = from_json['name']
@ -27,15 +77,17 @@ class YahooAuctionsItem:
self.last_checked = datetime.fromisoformat('1970-01-01')
self.available = True
self.update()
self.ready = False
self.updating = False
def update(self):
self.updating = True
try:
# the good news is, yahoo japan returns all the data we need in handy json format
# the bad news is that the only way to get that json format is to download the whole auction page and grep it
# r = requests.get(f"https://page.auctions.yahoo.co.jp/jp/auction/{self.id}").text
r = open("yahoo.html").read()
r = requests.get(f"https://page.auctions.yahoo.co.jp/jp/auction/{self.id}").text
# r = open("yahoo.html").read()
j = json.loads(re.match(r'.*var pageData ?= ?(\{.*?\});', r, re.DOTALL).group(1))
except:
raise
@ -50,6 +102,9 @@ class YahooAuctionsItem:
if self.name == None:
self.name = j['productName']
self.ready = True
self.updating = False
def price_jpy(self):
return f"¥{self.price:.2f}"

View file

@ -1,8 +1,9 @@
import json, os, pickle
from os import path
from datetime import datetime
import functions
from listing import YahooAuctionsItem
from listing import YahooAuctionsItem, JST
class BuypeebSettings:
def __init__(self, location: str):
@ -50,3 +51,18 @@ class BuypeebSettings:
self.watchlist[id] = YahooAuctionsItem(url, id, name)
for item in self.watchlist.values():
print(item.name, item.price)
def outdated_items(self):
out = []
now = datetime.now(tz = JST).timestamp()
for item in self.watchlist.values():
time_difference = now - item.last_checked.timestamp()
ending_soon = False # TODO: this
if (item.favourite and time_difference >= self.favouriteUpdateInterval) or \
(item.favourite and ending_soon and time_difference >= self.favouriteUpdateIntervalCritical) or \
(time_difference >= self.updateInterval) or \
(ending_soon and time_difference >= self.updateIntervalCritical):
out.append(item)
return out

View file

@ -48,6 +48,7 @@
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Add new</property>
<property name="label" translatable="yes">Add new</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-add</property>
@ -63,6 +64,7 @@
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Update all</property>
<property name="label" translatable="yes">Update all</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-refresh</property>
@ -87,6 +89,7 @@
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Undo</property>
<property name="label" translatable="yes">Undo</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-undo</property>
@ -101,6 +104,7 @@
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Redo</property>
<property name="label" translatable="yes">Redo</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-redo</property>
@ -122,12 +126,14 @@
</packing>
</child>
<child>
<object class="GtkToolButton">
<object class="GtkToolButton" id="btnClearEnded">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Clear ended</property>
<property name="label" translatable="yes">Clear ended</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-clear</property>
<signal name="clicked" handler="btnClearEndedClicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@ -135,12 +141,14 @@
</packing>
</child>
<child>
<object class="GtkToolButton">
<object class="GtkToolButton" id="btnClearAll">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Clear all</property>
<property name="label" translatable="yes">Clear all</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-delete</property>
<signal name="clicked" handler="btnClearAllClicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@ -161,6 +169,7 @@
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Open</property>
<property name="label" translatable="yes">Open</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-open</property>
@ -175,6 +184,7 @@
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Export as...</property>
<property name="label" translatable="yes">Export as...</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-save-as</property>
@ -199,6 +209,7 @@
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Help</property>
<property name="label" translatable="yes">Help</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-help</property>
@ -213,6 +224,7 @@
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Settings</property>
<property name="label" translatable="yes">Settings</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-preferences</property>
@ -227,6 +239,7 @@
<object class="GtkToolButton">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">Quit</property>
<property name="label" translatable="yes">Quit</property>
<property name="use_underline">True</property>
<property name="stock_id">gtk-quit</property>
@ -272,7 +285,6 @@
<property name="title" translatable="yes">Name</property>
<property name="expand">True</property>
<property name="clickable">True</property>
<property name="sort_indicator">True</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>
@ -296,7 +308,7 @@
<child>
<object class="GtkTreeViewColumn">
<property name="resizable">True</property>
<property name="title" translatable="yes">Ending in</property>
<property name="title" translatable="yes">Ending at</property>
<child>
<object class="GtkCellRendererText"/>
<attributes>