trmnl-bring-plugin/main.py

138 lines
4.7 KiB
Python
Raw Normal View History

2025-02-15 23:01:03 -05:00
"""Bring! TRMNL plugin"""
2025-02-12 00:25:55 -05:00
import asyncio
2025-02-15 23:01:03 -05:00
import copy
2025-02-12 00:25:55 -05:00
import datetime
import os
import time
2025-02-15 23:01:03 -05:00
from dataclasses import dataclass, field
from typing import List
2025-02-12 00:25:55 -05:00
2025-02-15 23:01:03 -05:00
import aiohttp
from aiohttp.web_exceptions import HTTPException
2025-02-12 00:25:55 -05:00
from bring_api import Bring
from bring_api.exceptions import BringException
2025-02-15 23:01:03 -05:00
from dotenv import load_dotenv
2025-02-12 00:25:55 -05:00
load_dotenv()
EXCEPTION_SLEEP_INTERVAL = 60
2025-02-15 23:01:03 -05:00
PLUGIN_SLEEP_INTERVAL = 60 * 15 # 15 minutes
@dataclass
class BringList:
"""Represents a Bring list"""
name: str = ""
uuid: str = ""
items: List[str] = field(default_factory=list)
def __eq__(self, other):
if isinstance(other, BringList):
return (
self.name == other.name
and self.uuid == other.uuid
and set(self.items) == set(other.items)
)
return False
2025-02-12 00:25:55 -05:00
class BringPlugin:
2025-02-15 23:01:03 -05:00
"""
Class syncing Bring shopping list with TRMNL.
The plugin currently supports only one shopping list.
"""
2025-02-12 00:25:55 -05:00
def __init__(self):
2025-02-15 23:01:03 -05:00
self.email = os.getenv("EMAIL")
self.password = os.getenv("PASSWORD")
2025-07-21 21:15:28 +00:00
self.list_uuid = os.getenv("LIST_UUID")
2025-02-15 23:01:03 -05:00
self.webhook_url = os.getenv("WEBHOOK_URL")
self.bring = None
self.existing_list = None
async def grab_items(self, bring_list):
"""Grabs the items of the list using the list's uuid"""
item_objs = (await self.bring.get_list(bring_list.uuid)).items.purchase
bring_list.items = [self.transform_item(item) for item in item_objs]
2025-02-13 21:25:44 -05:00
print(f"Successfully fetched items at {datetime.datetime.now().isoformat()}")
2025-02-15 23:01:03 -05:00
print(f"Items = {bring_list.items}")
def transform_item(self, api_item):
if api_item.specification:
return f"{api_item.itemId} ({api_item.specification})"
else:
return api_item.itemId
2025-02-15 23:01:03 -05:00
async def send_list_to_trmnl(self, session, bring_list):
"""Sends the list to TRMNL if it has changed"""
if self.existing_list == bring_list:
print("The items list hasn't changed since the last fetch.")
print("Skipping sending updates to TRMNL.")
return
2025-02-15 23:01:03 -05:00
self.existing_list = bring_list
2025-02-12 00:25:55 -05:00
try:
await session.post(
self.webhook_url,
2025-02-15 23:01:03 -05:00
json={
"merge_variables": {
"items": self.existing_list.items,
"list_name": self.existing_list.name,
}
},
headers={"Content-Type": "application/json"},
raise_for_status=True,
)
2025-02-12 00:25:55 -05:00
current_timestamp = datetime.datetime.now().isoformat()
print(f"Items sent successfully to TRMNL at {current_timestamp}")
2025-02-15 23:01:03 -05:00
except HTTPException as e:
2025-02-12 00:25:55 -05:00
print(f"Exception occurred during sending items to TRMNL: {e}")
async def run(self):
2025-02-15 23:01:03 -05:00
"""Start the plugin"""
2025-02-12 00:25:55 -05:00
async with aiohttp.ClientSession() as session:
self.bring = Bring(session, self.email, self.password)
await self.bring.login()
while True:
2025-02-15 23:01:03 -05:00
new_list = None
if self.existing_list:
new_list = copy.deepcopy(self.existing_list)
else:
2025-07-21 21:15:28 +00:00
all_api_lists = (await self.bring.load_lists()).lists
if self.list_uuid is None:
if len(all_api_lists) > 1:
print(f"LIST_UUID not specified, will use first list.")
bring_api_list = all_api_lists[0]
else:
bring_api_list = next((l for l in all_api_lists if l.listUuid == self.list_uuid), None)
if bring_api_list is None:
print(f"No list with UUID \"{self.list_uuid}\" found, will use first list instead.")
bring_api_list = all_api_lists[0]
2025-02-15 23:01:03 -05:00
new_list = BringList(name=bring_api_list.name, uuid=bring_api_list.listUuid)
await self.grab_items(new_list)
await self.send_list_to_trmnl(session, new_list)
time.sleep(PLUGIN_SLEEP_INTERVAL)
2025-02-12 00:25:55 -05:00
if __name__ == "__main__":
bring_plugin = BringPlugin()
2025-02-12 01:04:27 -05:00
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
2025-02-15 23:01:03 -05:00
2025-02-12 00:25:55 -05:00
while True:
try:
loop.run_until_complete(bring_plugin.run())
except BringException as e:
print(f"Bring exception occured: {e}")
print(f"Sleeping for {EXCEPTION_SLEEP_INTERVAL} seconds.")
time.sleep(EXCEPTION_SLEEP_INTERVAL)
2025-02-15 23:01:03 -05:00
print("Retrying the service")
2025-02-12 00:25:55 -05:00
except Exception as e:
print(f"Unknown exception occured: {e}")
2025-02-15 23:01:03 -05:00
raise