Compare commits
5 commits
97943aa595
...
24a2bf55af
Author | SHA1 | Date | |
---|---|---|---|
24a2bf55af | |||
277fbd6311 | |||
1e2b3902f2 | |||
f0cfa46f28 | |||
d50cd17f9d |
4 changed files with 168 additions and 19 deletions
88
buypeeb.py
88
buypeeb.py
|
@ -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(
|
||||
|
|
61
listing.py
61
listing.py
|
@ -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}"
|
||||
|
||||
|
|
18
settings.py
18
settings.py
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue