#!/usr/bin/env python # 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 # 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 . #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 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') 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 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) self.app.connect('activate', self.activate) self.app.connect('shutdown', self.shutdown) self.window = None self.settings = BuypeebSettings(settingsLocation) self.builder = None 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') self.items = builder.get_object('lstItems') self.items.clear() # ensure list is empty self.window = wndMain self.builder = builder app.add_window(self.window) self.setExchangeRate() self.settings.load() self.renderList() def activate(self, app): self.window.show_all() self.updateItems() def shutdown(self, app): self.settings.save() self.app.quit() # 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 = buttons, text = title ) msgbox.format_secondary_text(text) response = msgbox.run() msgbox.destroy() return response 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() if response == Gtk.ResponseType.OK: return text else: # user clicked cancel return False def entryBoxResponse(self, widget, entrybox, response): entrybox.response(response) 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(): 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(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) == Gtk.ResponseType.OK: 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) == Gtk.ResponseType.OK: self.settings.watchlist = {} self.items.clear() def btnQuitClicked(self, widget): prompt = Gtk.MessageDialog( parent = self.window, flags = 0, message_type = Gtk.MessageType.QUESTION, buttons = Gtk.ButtonsType.OK_CANCEL, text = "Really quit?" ) prompt.format_secondary_text("Are you sure you want to quit buypeeb?") response = prompt.run() if response: self.shutdown(self) if __name__ == '__main__': app = BuypeebApp() app.run(sys.argv)