diff --git a/src/trackbert/classes/core.py b/src/trackbert/classes/core.py index dc9a684..df0923c 100644 --- a/src/trackbert/classes/core.py +++ b/src/trackbert/classes/core.py @@ -11,12 +11,14 @@ from os import PathLike from configparser import ConfigParser from .database import Database -from .tracker import BaseTracker +from .provider import BaseProvider class Core: - loop_interval = 60 - loop_timeout = 30 + loop_interval: int = 60 + loop_timeout: int = 30 + + config: Optional[ConfigParser] = None def __init__(self, config: Optional[PathLike] = None): logging.basicConfig( @@ -26,81 +28,112 @@ class Core: ) self._pre_start(config) + self.notifiers = self.find_notifiers() + self.providers = self.find_providers() - self.find_apis() + def find_core_notifiers(self): + logging.debug("Finding core notifiers") - def find_apis(self): - logging.debug("Finding APIs") + notifiers = [] - self.apis = [] - - for api in Path(__file__).parent.parent.glob("trackers/*.py"): - if api.name in ("__init__.py", "base.py"): + for notifier in Path(__file__).parent.parent.glob("notifiers/*.py"): + if notifier.name in ("__init__.py", "base.py"): continue - logging.debug(f"Found API {api.stem}") + logging.debug(f"Considering notifier {notifier.stem}") try: - module = importlib.import_module(f"trackbert.trackers.{api.stem}") + module = importlib.import_module(f"trackbert.notifiers.{notifier.stem}") except: - logging.error(f"Error loading class {api.stem}") + logging.error(f"Error loading class {notifier.stem}") + continue + + if "notifier" in module.__dict__: + notifier_class = module.notifier + logging.debug(f"Found notifier {notifier_class.__name__}") - if "tracker" in module.__dict__: - tracker = module.tracker - logging.debug(f"Found tracker {api.stem}") try: - api = tracker(config=self.config_path) - carriers = api.supported_carriers() + if self.config and notifier_class.__name__ in self.config: + nconfig = self.config[notifier_class.__name__] + else: + nconfig = None + + nobj = notifier_class(config=nconfig) + + if nobj.enabled: + notifiers.append(nobj) + + except Exception as e: + logging.error( + f"Error loading notifier {notifier_class.__name__}: {e}" + ) + + return notifiers + + def find_notifiers(self): + return self.find_core_notifiers() + + def find_core_providers(self): + logging.debug("Finding core tracking providers") + + providers = [] + + for provider in Path(__file__).parent.parent.glob("providers/*.py"): + if provider.name in ("__init__.py", "base.py"): + continue + + logging.debug(f"Considering provider {provider.stem}") + + try: + module = importlib.import_module(f"trackbert.providers.{provider.stem}") + except Exception as e: + logging.error(f"Error loading class {provider.stem}: {e}") + continue + + if "provider" in module.__dict__: + provider_api = module.provider + logging.debug(f"Found provider {provider_api.__name__}") + try: + pobj = provider_api(config=self.config_path) + carriers = pobj.supported_carriers() for carrier in carriers: - self.apis.append((carrier[0], carrier[1], api, (carrier[2] if len(carrier) > 2 else None))) + providers.append( + ( + carrier[0], + carrier[1], + pobj, + (carrier[2] if len(carrier) > 2 else None), + ) + ) except Exception as e: - logging.error(f"Error loading tracker {api.__class__.__name__}: {e}") + logging.error( + f"Error loading provider {provider.__class__.__name__}: {e}" + ) - def query_api(self, tracking_number: str, carrier: str) -> list: - logging.debug(f"Querying API for {tracking_number} with carrier {carrier}") + return providers - for api_entry in sorted(self.apis, key=lambda x: x[1], reverse=True): + def find_providers(self): + return self.find_core_providers() + + def query_provider(self, tracking_number: str, carrier: str) -> list: + logging.debug(f"Querying provider for {tracking_number} with carrier {carrier}") + + for api_entry in sorted(self.providers, key=lambda x: x[1], reverse=True): api_carrier = api_entry[0] priority = api_entry[1] - api = api_entry[2] + provider = api_entry[2] name = api_entry[3] if len(api_entry) > 3 else None if api_carrier == "*" or api_carrier == carrier: logging.debug( - f"Using API {api.__class__.__name__} for {tracking_number} with carrier {carrier}" + f"Using provider {provider.__class__.__name__} for {tracking_number} with carrier {carrier}" ) - return list(api.get_status(tracking_number, carrier)) + return list(provider.get_status(tracking_number, carrier)) - def notify( - self, - title: str, - message: str, - urgency: str = "normal", - timeout: Optional[int] = 5000, - ) -> None: - logging.debug(f"Sending notification: {title} - {message}") - - command = [ - "notify-send", - "-a", - "trackbert", - "-u", - urgency, - "-i", - str(Path(__file__).parent.parent / "assets" / "parcel-delivery-icon.webp"), - ] - - if timeout: - command += ["-t", str(timeout)] - - command = command + [title, message] - - try: - subprocess.run(command) - - except FileNotFoundError: - logging.warning("notify-send not found, not sending notification") + def notify(self, title, message, urgent=False) -> None: + for notifier in self.notifiers: + notifier.notify(title, message, urgent) def notify_event(self, shipment, event, critical=False) -> None: logging.info( @@ -126,9 +159,11 @@ class Core: latest_known_event = self.db.get_latest_event(shipment.id) try: - events = self.query_api(shipment.tracking_number, shipment.carrier) + events = self.query_provider(shipment.tracking_number, shipment.carrier) except Exception as e: - logging.exception(f"Error querying API for {shipment.tracking_number}") + logging.exception( + f"Error querying provider for {shipment.tracking_number}: {e}" + ) return events = sorted(events, key=lambda x: x.event_time) @@ -213,21 +248,21 @@ class Core: def _pre_start(self, config: Optional[PathLike] = None): self.config_path = config - parser = ConfigParser() - parser.read(config or []) + self.config = ConfigParser() + self.config.read(config or []) - self.debug = parser.getboolean("Trackbert", "debug", fallback=False) + self.debug = self.config.getboolean("Trackbert", "debug", fallback=False) if self.debug: logger = logging.getLogger() logger.setLevel(logging.DEBUG) - self.database_uri = parser.get( + self.database_uri = self.config.get( "Trackbert", "database", fallback="sqlite:///trackbert.db" ) self.db = Database(self.database_uri) - self.loop_interval = parser.getint("Trackbert", "interval", fallback=60) + self.loop_interval = self.config.getint("Trackbert", "interval", fallback=60) def start(self, config: Optional[PathLike] = None): self.notify("Trackbert", "Starting up") diff --git a/src/trackbert/classes/notifier.py b/src/trackbert/classes/notifier.py new file mode 100644 index 0000000..dc26eef --- /dev/null +++ b/src/trackbert/classes/notifier.py @@ -0,0 +1,10 @@ +class BaseNotifier: + def __init__(self, *args, **kwargs): + pass + + def notify(self, title: str, message: str, urgent: bool = False) -> None: + raise NotImplementedError + + @property + def enabled(self) -> bool: + raise NotImplementedError \ No newline at end of file diff --git a/src/trackbert/classes/tracker.py b/src/trackbert/classes/provider.py similarity index 97% rename from src/trackbert/classes/tracker.py rename to src/trackbert/classes/provider.py index 33ee8ec..cbcdfb5 100644 --- a/src/trackbert/classes/tracker.py +++ b/src/trackbert/classes/provider.py @@ -2,7 +2,7 @@ from typing import Optional, Tuple, List, Generator from ..classes.database import Event -class BaseTracker: +class BaseProvider: def __init__(self, *args, **kwargs): pass diff --git a/src/trackbert/trackers/__init__.py b/src/trackbert/notifiers/__init__.py similarity index 100% rename from src/trackbert/trackers/__init__.py rename to src/trackbert/notifiers/__init__.py diff --git a/src/trackbert/notifiers/notify_send.py b/src/trackbert/notifiers/notify_send.py new file mode 100644 index 0000000..3048e79 --- /dev/null +++ b/src/trackbert/notifiers/notify_send.py @@ -0,0 +1,48 @@ +from ..classes.notifier import BaseNotifier + +import subprocess +import logging + +class NotifySend(BaseNotifier): + def __init__(self, *args, **kwargs): + pass + + def notify( + self, + title: str, + message: str, + urgent: bool = False, + timeout: Optional[int] = 5000, + ) -> None: + logging.debug(f"Sending notification: {title} - {message}") + + command = [ + "notify-send", + "-a", + "trackbert", + "-u", + "normal" if not urgent else "critical", + "-i", + str(Path(__file__).parent.parent / "assets" / "parcel-delivery-icon.webp"), + ] + + if timeout and not urgent: + command += ["-t", str(timeout)] + + command = command + [title, message] + + try: + subprocess.run(command) + + except FileNotFoundError: + logging.warning("notify-send not found, not sending notification") + + @property + def enabled(self) -> bool: + try: + subprocess.run(["notify-send", "--help"], stdout=subprocess.DEVNULL) + return True + except FileNotFoundError: + return False + +notifier = NotifySend \ No newline at end of file diff --git a/src/trackbert/providers/__init__.py b/src/trackbert/providers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/trackbert/trackers/dhl.py b/src/trackbert/providers/dhl.py similarity index 95% rename from src/trackbert/trackers/dhl.py rename to src/trackbert/providers/dhl.py index b552c54..85af5ec 100644 --- a/src/trackbert/trackers/dhl.py +++ b/src/trackbert/providers/dhl.py @@ -1,4 +1,4 @@ -from ..classes.tracker import BaseTracker +from ..classes.provider import BaseProvider from ..classes.database import Event from dhltrack import DHL as DHLAPI @@ -11,7 +11,7 @@ from datetime import datetime from configparser import ConfigParser -class DHL(BaseTracker): +class DHL(BaseProvider): def __init__(self, *args, **kwargs): self.api = DHLAPI.from_config(str(kwargs.get("config"))) @@ -65,4 +65,4 @@ class DHL(BaseTracker): ] -tracker = DHL +provider = DHL diff --git a/src/trackbert/trackers/dpd.py b/src/trackbert/providers/dpd.py similarity index 92% rename from src/trackbert/trackers/dpd.py rename to src/trackbert/providers/dpd.py index 7645383..9c43ad3 100644 --- a/src/trackbert/trackers/dpd.py +++ b/src/trackbert/providers/dpd.py @@ -1,4 +1,4 @@ -from ..classes.tracker import BaseTracker +from ..classes.provider import BaseProvider from ..classes.database import Event import json @@ -8,7 +8,7 @@ from datetime import datetime from dpdtrack.classes.api import DPD as DPDAPI -class DPD(BaseTracker): +class DPD(BaseProvider): def __init__(self, *args, **kwargs): pass @@ -40,4 +40,4 @@ class DPD(BaseTracker): ] -tracker = DPD +provider = DPD diff --git a/src/trackbert/trackers/fedex.py b/src/trackbert/providers/fedex.py similarity index 94% rename from src/trackbert/trackers/fedex.py rename to src/trackbert/providers/fedex.py index 5752291..5366369 100644 --- a/src/trackbert/trackers/fedex.py +++ b/src/trackbert/providers/fedex.py @@ -1,4 +1,4 @@ -from ..classes.tracker import BaseTracker +from ..classes.provider import BaseProvider from ..classes.database import Event from fedextrack import FedEx as FedExAPI @@ -8,7 +8,7 @@ import json import logging -class FedEx(BaseTracker): +class FedEx(BaseProvider): def __init__(self, *args, **kwargs): self.api = FedExAPI.from_config(str(kwargs.get("config"))) @@ -51,4 +51,4 @@ class FedEx(BaseTracker): ] -tracker = FedEx +provider = FedEx diff --git a/src/trackbert/trackers/gls.py b/src/trackbert/providers/gls.py similarity index 89% rename from src/trackbert/trackers/gls.py rename to src/trackbert/providers/gls.py index 75c07f0..8a5a85a 100644 --- a/src/trackbert/trackers/gls.py +++ b/src/trackbert/providers/gls.py @@ -1,4 +1,4 @@ -from ..classes.tracker import BaseTracker +from ..classes.provider import BaseProvider from ..classes.database import Event import json @@ -7,7 +7,7 @@ from dateutil.parser import parse from glsapi.classes.api import GLSAPI -class GLS(BaseTracker): +class GLS(BaseProvider): def __init__(self, *args, **kwargs): pass @@ -31,4 +31,4 @@ class GLS(BaseTracker): ] -tracker = GLS +provider = GLS diff --git a/src/trackbert/trackers/keydelivery.py b/src/trackbert/providers/keydelivery.py similarity index 93% rename from src/trackbert/trackers/keydelivery.py rename to src/trackbert/providers/keydelivery.py index 96ec65d..e79774c 100644 --- a/src/trackbert/trackers/keydelivery.py +++ b/src/trackbert/providers/keydelivery.py @@ -1,4 +1,4 @@ -from ..classes.tracker import BaseTracker +from ..classes.provider import BaseProvider from ..classes.database import Event from ..classes.http import HTTPRequest @@ -8,7 +8,7 @@ import json import logging -class KeyDelivery(BaseTracker): +class KeyDelivery(BaseProvider): def __init__(self, *args, **kwargs): self.api = KeyDeliveryAPI.from_config(str(kwargs.get("config"))) @@ -50,4 +50,4 @@ class KeyDelivery(BaseTracker): ] -tracker = KeyDelivery +provider = KeyDelivery diff --git a/src/trackbert/trackers/postat.py b/src/trackbert/providers/postat.py similarity index 91% rename from src/trackbert/trackers/postat.py rename to src/trackbert/providers/postat.py index 4054f06..7507f99 100644 --- a/src/trackbert/trackers/postat.py +++ b/src/trackbert/providers/postat.py @@ -1,4 +1,4 @@ -from ..classes.tracker import BaseTracker +from ..classes.provider import BaseProvider from ..classes.database import Event import json @@ -7,7 +7,7 @@ from dateutil.parser import parse from postat.classes.api import PostAPI -class PostAT(BaseTracker): +class PostAT(BaseProvider): def __init__(self, *args, **kwargs): pass @@ -34,4 +34,4 @@ class PostAT(BaseTracker): ] -tracker = PostAT +provider = PostAT