diff --git a/livery_tracker.py b/livery_tracker.py new file mode 100644 index 0000000..0b51f1c --- /dev/null +++ b/livery_tracker.py @@ -0,0 +1,228 @@ +#!/usr/bin/python3 +import json +import logging +import queue +import sys +import threading +import time + +import requests + +# Dump new raw jsons? +DUMP = False + +url = '' # Insert your discord webhook url here + +# setting up logging +logger = logging.getLogger() +logger.setLevel(logging.INFO) +stdout_handler = logging.StreamHandler(sys.stdout) +logger.addHandler(stdout_handler) + +localdb_file = "local_storage.json" +images_base_url = "https://dlc.elitedangerous.com/images/med/" + + +class Messages_sender(threading.Thread): + """Sending message to discord asynchronously""" + + def __init__(self, url, messages_queue=queue.Queue()): + threading.Thread.__init__(self) + self.queue = messages_queue + self.url = url + self.headers = {'Content-Type': 'application/x-www-form-urlencoded'} + + def put(self, message): # put message into queue + self.queue.put(message) + + def exit(self, lock=False): # stop messages_sender thread + self.queue.put('exit') + if lock: + self.join() + + def run(self): + isStopped = False + while True: + if isStopped: + logger.debug("Got exit message") + break + + message_from_queue = self.queue.get() + + if message_from_queue == "exit": + logger.debug("Got exit message") + break + + message = requests.utils.quote(message_from_queue) + + try: + while True: + logger.debug('===BEGIN OF MESSAGE DUMP===\n' + str( + f"content={message}".encode('utf-8')) + '\n===END OF MESSAGE DUMP===') + r = requests.post(self.url, data=f"content={message}".encode('utf-8'), headers=self.headers) + + if r.status_code == 204: # If success + logger.debug("Successful sent, status code: " + str({r.status_code}) + ", text: " + r.text) + time.sleep(0.5) + break + + if r.status_code == 429: # We are rate limited, see https://discord.com/developers/docs/topics/rate-limits + retry_time = time.time() + int(r.headers["retry-after"]) + logger.debug( + f"We are rate limited, text: {r.text}\n Will retry after {int(retry_time - time.time())}") + first_append = True + while time.time() < retry_time: + try: + message_from_queue = self.queue.get(block=False) + if message_from_queue == "exit": + isStopped = True + break + message_from_queue = requests.utils.quote(message_from_queue) + except queue.Empty: + continue + if first_append: + message = "\n" + message + "\n" + message_from_queue + first_append = False + else: + message = message + "\n" + message_from_queue + logger.debug("Add new message for send") + continue + if r.status_code != 204: # Any others cases + logger.error(f"Status code: {r.status_code}! {r.text}") + break + except Exception as e: + logger.warning(f"Got exception in sender thread\n{e}") + + +# setting up messages sender +messages_sender = Messages_sender(url) +messages_sender.start() + + +# def get_onlinestore_data(): +# with open("store_sample.json") as store_sample: +# items = list() +# for item in json.load(store_sample): +# items.append({"name": item["title"], "cur_price": item["current_price"], "orig_price": item["original_price"]}) +# return items + + +def on_exit(): + try: + with open(localdb_file, "w") as dbfile: + dbfile.write(json.dumps(online_items)) + except Exception as e: + logger.error("Got exception on_exit() on file saving:\n" + str(e)) + logger.debug("Waiting for sender thread") + messages_sender.exit(lock=True) + exit() + + +def get_onlinestore_data(): + logger.debug("Getting online store data") + items = list() + for item in json.loads(requests.get("https://api.zaonce.net/3.0/store/product").text): + items.append(dict(name=item["title"], cur_price=item["current_price"], orig_price=item["original_price"], + image=item["image"])) + logger.debug(f"Got {len(items)} items") + return items + + +def get_localstore_data(): + logger.debug("Getting local data") + try: + with open(localdb_file) as local: + data = json.load(local) + logger.debug(f"Found local data file with {len(data)} items") + return data + except FileNotFoundError: + logger.debug("Didn't found local data file") + return None + + +online_items = get_onlinestore_data() +local_items = get_localstore_data() + +if local_items is None: + logger.info("No local data file, creating new one") + on_exit() + +if str(online_items) != str(local_items): + logger.debug("Different data") + if DUMP: + logger.debug("DUMP is True, dumping raw json") + with open(f"{int(time.time())}.json", "w") as file: + file.write(json.dumps(online_items)) +else: + logger.debug("No new data, exiting") + on_exit() + +for online_item in online_items: + if online_item not in local_items: # updated or even new item in store + logger.debug(f"Updated or even new item in store: {online_item}") + + online_item_name = online_item[ + "name"] # lets try to find item with this name in local db but with different price + + for local_item in local_items: + local_item_name = local_item["name"] + + if online_item_name == local_item_name: # updated price for existing item + logger.debug(f"Found this item with different price: {local_item}") + local_item_cur_price = local_item["cur_price"] + local_item_orig_price = local_item["orig_price"] + + online_item_item_cur_price = online_item["cur_price"] + online_item_item_orig_price = online_item["orig_price"] + + image_url = images_base_url + online_item["image"] + + message = f"```Change price for known item: {online_item_name}\n\tcurrent price: {local_item_cur_price} -> {online_item_item_cur_price}\n\torig price: {local_item_orig_price} -> {online_item_item_orig_price}\n```\nlink to item image: {image_url}" + messages_sender.put(message) + local_items.remove(local_item) + find = True + break + else: # new item in store. Works if we didn't break line in for + image_url = images_base_url + online_item["image"] + message = f"```New item: {online_item['name']}\n\tcurrent price: {online_item['cur_price']}\n\torig price: {online_item['orig_price']}\n```\nlink to item image: {image_url}" + messages_sender.put(message) + continue + + if find: + continue + + local_items.remove(online_item) + +for removed_item in local_items: # Detecting removed items from store + message = f"```Item removed: {removed_item['name']}\nLast known prices:\n\tcurrent price: {removed_item['cur_price']}\n\torig_price: {removed_item['orig_price']}```" + messages_sender.put(message) + +on_exit() + +""" +livery_tracker.service +[Unit] +Description=track frontier livery store for ED +After=syslog.target +After=network.target + +[Service] +Type=oneshot +User=user2 +WorkingDirectory=/home/user2/livery_tracker +ExecStart=/home/user2/livery_tracker/livery_tracker.py + +[Install] +WantedBy=multi-user.target + +livery_tracker.timer +[Unit] +Description=run livery_tracker + +[Timer] +OnCalendar=hourly +Persistent=true + +[Install] +WantedBy=multi-user.target +"""