I have been working on where I create a payload of dicts with different values as store, name etc etc which you will see very soon. The idea is that with the payload I send to this script, it should see if the values is in the payload (dict) and if it is then add it into discord embed. As simple as it sounds.
I have done something like this:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import time
from threading import Thread
from typing import Dict
import pendulum
from discord_webhook import DiscordEmbed, DiscordWebhook
from loguru import logger
mixed_filtered = {
'test1': 'https://discordapp.com/api/webhooks/529345345345345/6h4_yshmNDKktdT-0VevOhqdXG9rDhRWclIfDD4jY8IbdCQ5-kllob-k1251252151',
'test2': 'https://discordapp.com/api/webhooks/529674575474577/6h4_yshmNDKktdT-0VevOhqdXG9rDhRWclIfDD4jY8IbdCQ5-kllob-fhgdfdghfhdh'
}
mixed_unfiltered = {
'test1': 'https://discordapp.com/api/webhooks/12412421412412/6h4_yshmNDKktdT-0VevOhqdXG9rDhR12412412414Q5-kllob-kI2jAxCZ5PdIn',
'test2': 'https://discordapp.com/api/webhooks/529617352682110997/6h4_yshmNDKktdT-0VevOhqdXG912412412412IbdCQ5-kllob-kI2jAxCZ5PdIn'
}
def create_embed(payload: dict) -> None:
# -------------------------------------------------------------------------
# Name of the product, URL of the product & New! or Restock! url
# -------------------------------------------------------------------------
embed = DiscordEmbed(
url=payload["link"],
description=payload["status"],
color=8149447
)
# -------------------------------------------------------------------------
# Image product
# -------------------------------------------------------------------------
embed.set_thumbnail(
url=payload["image"]
)
# -------------------------------------------------------------------------
# Store Name
# -------------------------------------------------------------------------
embed.add_embed_field(
name="Site",
value=f'{payload["store"]}'
)
# -------------------------------------------------------------------------
# The price of the product
# -------------------------------------------------------------------------
if payload.get("price"):
embed.add_embed_field(
name="Price",
value=payload["price"]
)
# -------------------------------------------------------------------------
# If store is Nike
# -------------------------------------------------------------------------
if "Nike" in payload["store"]:
if payload.get("nikeStatus"):
embed.add_embed_field(
name="\u200b",
value="\u200b"
)
embed.add_embed_field(
name="Status",
value=payload["nikeStatus"]
)
# -------------------------------------------------------------------------
# Nike Sales Channel
# -------------------------------------------------------------------------
if payload.get("salesChannel"):
embed.add_embed_field(
name="Sales Channel",
value="\n".join(payload["salesChannel"])
)
# -------------------------------------------------------------------------
# Sizes available
# Add extra spaces for sizes to make it cleaner for discord embed
# -------------------------------------------------------------------------
if payload.get("sizes"):
payload["stock"] = sum(v for v in payload["sizes"].values() if v)
payload["sizes"] = [f"{k} - ({v})" if v else k for k, v in payload["sizes"].items()]
# If we have stock in values then sum it up
embed.add_embed_field(
name="\u200b",
value="\u200b"
)
characterCount, i = 0, 0
for j, item in enumerate(payload["sizes"]):
# There is a limitation for Discord where if we reach over 1020 characters for one embed column.
# IT will throw a error. Now I check if the characters count is less than 900 then we create a new embed.
if len(item) + characterCount > 900:
embed.add_embed_field(
name="Sizes",
value="\n".join(payload["sizes"][i:j])
)
characterCount, i = len(item), j
else:
characterCount += len(item)
if characterCount:
embed.add_embed_field(
name="Sizes",
value="\n".join(payload["sizes"][i:])
)
embed.add_embed_field(
name="\u200b",
value="\u200b"
)
embed.add_embed_field(
name="\u200b",
value="\u200b"
)
# -------------------------------------------------------------------------
# If store is footlocker
# -------------------------------------------------------------------------
if "Footlocker" in payload["store"]:
if payload.get("stockLoaded"):
embed.add_embed_field(
name="Stock Loaded",
value=payload["stockLoaded"].upper()
)
if payload.get("styleCode"):
embed.add_embed_field(
name="\u200b",
value="\u200b"
)
embed.add_embed_field(
name="Style Code",
value=payload["styleCode"]
)
# -------------------------------------------------------------------------
# Release date for the product
# -------------------------------------------------------------------------
if payload.get("releaseDate"):
embed.add_embed_field(
name="Release Date",
value=payload["releaseDate"].to_datetime_string()
)
# -------------------------------------------------------------------------
# Stock keeping unit etc. 508214-660
# -------------------------------------------------------------------------
if payload.get("sku"):
embed.add_embed_field(
name="SKU",
value=payload["sku"]
)
# -------------------------------------------------------------------------
# Total stock of the product
# -------------------------------------------------------------------------
if payload.get("stock"):
embed.add_embed_field(
name="Total Stock",
value=payload["stock"]
)
# -------------------------------------------------------------------------
# Login/Cart/Checkout shortcut links
# -------------------------------------------------------------------------
embed.add_embed_field(
name="Shortcuts Links",
value=f'{" | ".join(shortcuts for shortcuts in payload["shortcut"])}'
)
# -------------------------------------------------------------------------
# Quick task for bots
# -------------------------------------------------------------------------
if payload.get("quicktask"):
embed.add_embed_field(
name="Quick Tasks",
value=f'{" | ".join(shortcuts for shortcuts in payload["quicktask"])}'
)
# -------------------------------------------------------------------------
# Footer timestamp
# -------------------------------------------------------------------------
embed.set_footer(
text=f'AutoSnkr | {pendulum.now("Europe/Stockholm").format("YYYY-MM-DD [[]HH:mm:ss.SSSS[]]")}'
)
# -------------------------------------------------------------------------
# Set title on the embed
# -------------------------------------------------------------------------
if payload.get('stock') and payload.get('name'):
embed.title = f'({payload["stock"]}) {payload["name"]}'
elif payload.get('name'):
embed.title = payload["name"]
else:
embed.title = payload.get('link')
# -------------------------------------------------------------------------
# Send payload/embed to Discord Notification function
# -------------------------------------------------------------------------
collection = mixed_filtered if payload["keyword"] else mixed_unfiltered
for region, discord_collection in collection.items():
webhook = DiscordWebhook(
url=discord_collection,
username="AutoSnkr Monitor",
)
webhook.add_embed(embed)
# Adding thread so each URL can post as fast as possible without needing to wait for each other
Thread(
target=post_embed,
args=(
payload,
region,
webhook
)
).start()
def post_embed(payload: Dict, region: str, webhook: DiscordWebhook) -> None:
success: bool = False
while not success:
try:
response = webhook.execute()
success = response.ok
# If we get a 429, retry after a short delay
if response.status_code == 429:
sleep_time = int(response.headers["retry-after"]) / 1000
logger.debug(f"Rate limit -> Retrying in {sleep_time} seconds")
time.sleep(sleep_time)
continue
# any response other than a 429 or a 200 OK is an error.
if not response.ok:
# FIXME Add discord notficiation and raise exception
pass
logger.info(f"Succesfully sent to Discord Reporter -> {region}")
except Exception as err:
# FIXME Add discord notficiation and raise exception
pass
if __name__ == '__main__':
create_embed(
{
"store": "Basket4ballers",
"link": "https://www.basket4ballers.com/en/pg/26471-nike-pg5-bred-cw3143-101.html",
"name": "Nike PG5 Bred",
"price": "EUR 119.9",
"image": "https://cdn1.basket4ballers.com/114821-large_default/nike-pg5-bred-cw3143-101.jpg",
"sizes": {
"[EU 38.5](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205049)": 1,
"[EU 39](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205052)": 1,
"[EU 40](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205055)": 3,
"[EU 40.5](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205058)": 4,
"[EU 41](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205061)": 9,
"[EU 42](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205064)": 11,
"[EU 42.5](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205067)": 11,
"[EU 43](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205070)": 16,
"[EU 44](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205073)": 21,
"[EU 44.5](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205076)": 15,
"[EU 45](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205079)": 20,
"[EU 45.5](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205082)": 7,
"[EU 46](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205085)": 17,
"[EU 47](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205088)": 7,
"[EU 47.5](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205091)": 5,
"[EU 48](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205094)": 3,
"[EU 48.5](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205097)": 2,
"[EU 49.5](https://www.basket4ballers.com/?controller=cart&add=1&as=true&qty=1&id_product=26471&token=e4d64f25476dcee4b08744d382dc405b&ipa=205100)": 1},
"shortcut": ["[Login](https://www.basket4ballers.com/en/authentification?back=my-account)",
"[Cart](https://www.basket4ballers.com/en/commande)",
"[Checkout Delivery](https://www.basket4ballers.com/en/commande?step=1)",
"[Checkout Shipping Service](https://www.basket4ballers.com/en/commande)",
"[Checkout PAyment](https://www.basket4ballers.com/en/commande)"],
"webhook": "mixed",
"status": "Restock!",
"keyword": True
}
)
The mock data is at the very bottom but in the future I will instead send the payload to the function.
I wonder what can I do to try to have less code but that still does the job. I feel like there should be a way more cleaner way to do this than what I did but looking forward to see what can be improved :)
Let me know if there is any missing information. The script should be runnable by copy pasting it but make sure to create your own discord webhooks to test the embed. I will unfortunately need to modify them so no one can spam me :)
1 Answer 1
If I were a smart man I'd give up on recommending that you stop using a payload dictionary for internal data representation and instead use classes, but I'm not a smart man. Please. I implore you. We're not in JavaScript - objects don't have to be dictionaries. This could be well-represented by a class for product, and a class for product size.
Otherwise:
if payload.get("price")
should be replaced by if 'price' in payload
if it were only that statement; but since you actually use it,
if payload.get("price"):
embed.add_embed_field(
name="Price",
value=payload["price"]
)
should become
price = payload.get('price')
if price is not None:
embed.add_embed_field(name='Price', value=price)
More broadly: your create_embed
is a presentation function but mixes in logic concerns such as stock summation, and store-specific logic (i.e. Footlocker). That should be separated.
Your
# FIXME Add discord notficiation and raise exception
first of all has a typo - notficiation
-> notification
- and second of all, while this is waiting to be fixed it's of crucial importance that you not swallow exceptions. Part of development and debugging is seeing errors, and your code breaks that. So replace your pass
with a raise
in the meantime.
-
\$\begingroup\$ Hi again! :) Regarding the payload being dictionary it is something that I still have issue with and it is something we have worked on together as you remember :) My problem is that I do not know how I can work with only classes where I have previously asked for a code review regarding the part where I actually send the payload to the create_embed which can be found from here codereview.stackexchange.com/q/259867/223687 - If I could get more description over how I can actually do a comparision with just classes and throw away the dicts. That would be very cool to know how \$\endgroup\$PythonNewbie– PythonNewbie2021年04月29日 16:29:53 +00:00Commented Apr 29, 2021 at 16:29
-
\$\begingroup\$ Currently I do not know how to do it as for today but am willing to learn :) Regarding the seperation is something I will work on. I did realize that I have done something more complicated and harder to read, thanks for that :) Will work on that part but now im more concern if im going to change with classes then I most likely wont need the dicts at all. Lets see what you have to say :D Excited to see! \$\endgroup\$PythonNewbie– PythonNewbie2021年04月29日 16:31:47 +00:00Commented Apr 29, 2021 at 16:31
-
\$\begingroup\$ Let us continue this in chat: chat.stackexchange.com/rooms/123085/… \$\endgroup\$PythonNewbie– PythonNewbie2021年04月29日 18:37:53 +00:00Commented Apr 29, 2021 at 18:37
-
\$\begingroup\$ Hey again! Unfortunately I need to go back to sleep as I was waiting for a reply. :) I hope and wish that I could get an answer back, would mean alot for me! \$\endgroup\$PythonNewbie– PythonNewbie2021年04月29日 23:48:38 +00:00Commented Apr 29, 2021 at 23:48