""" Post today's menu to Slack. This is done by parsing the JSON data from SharePoint. """ import datetime import json import os import sys import random from urllib import error, parse, request import browser_cookie3 from googletrans import Translator import requests from requests.exceptions import JSONDecodeError, ReadTimeout # Load configuration. try: IS_LIVE = bool(os.environ.get("IS_LIVE")) SLACK_ERROR_HOOK = os.environ["SLACK_ERROR_HOOK"] SLACK_SUCCESS_HOOK = ( os.environ["SLACK_SUCCESS_HOOK"] if IS_LIVE else SLACK_ERROR_HOOK ) SHAREPOINT_URL = os.environ["SHAREPOINT_URL"] MENU_URL = os.environ["MENU_URL"] except KeyError as e: sys.stderr.write(f"Unable to load configuration for {e}.\n") sys.exit(1) sys.stdout.write("Configuration loaded.\n") def get_photo_url(menu: str): """Retrieve a photo of the menu.""" # Translate menu from Danish to English. try: translation = Translator().translate(menu, src="da", dest="en").text except TypeError: sys.stderr.write("Unable to translate menu :(\n") return None sys.stdout.write(f"English translation of the menu is: {translation}\n") # Ask Crayion to be creative. cookies = browser_cookie3.firefox(domain_name="craiyon.com") cookie = None for cookie in cookies: if cookie.name == "supabase-auth-token": cookie = parse.unquote(cookie.value) break if cookie is None: sys.stderr.write("Unable to extract Craiyon cookie.\n") sys.exit(1) token = json.loads(cookie)[0] headers = { "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0", "Accept": "application/json", "Accept-Language": "en-US,en;q=0.5", "Accept-Encoding": "gzip, deflate, br", "Content-Type": "application/json", "Origin": "https://www.craiyon.com", "Connection": "keep-alive", "Sec-Fetch-Dest": "empty", "Sec-Fetch-Mode": "cors", "Sec-Fetch-Site": "same-site", "Sec-GPC": "1", "Pragma": "no-cache", "Cache-Control": "no-cache", "TE": "trailers", } styles = [ "in a school canteen", "from grandma's kitchen", "cooked over a camp fire", "as fast food take away", "on a prison canteen tray", "served in the style of a Michelin restaurant", ] style = random.choice(styles) prompt = f"A meal of {translation} {style}" request_data = { "model": "photo", "negative_prompt": "", "prompt": prompt, "token": token, "version": "35s5hfwn9n78gb06", } sys.stdout.write("Asking Craiyon to generate images for the menu with the prompt:\n") sys.stdout.write(f"{prompt}\n") craiyon_start = datetime.datetime.now() try: response = requests.post( "https://api.craiyon.com/v3", headers=headers, json=request_data, timeout=90, ) except ReadTimeout: sys.stderr.write("Timeout while waiting for Craiyon :(\n") return None try: image = random.choice(response.json()["images"]) except JSONDecodeError: sys.stderr.write("Unable to parse JSON from Craiyon response:\n") sys.stderr.write(f"{response.text}\n") sys.stderr.write(f"Waited for {datetime.datetime.now() - craiyon_start}.") return None return (f"https://img.craiyon.com/{image}", translation) # Set up and execute request to SharePoint. cookies = browser_cookie3.firefox(domain_name="sharepoint.com") headers = { "Accept": "application/json;odata=verbose", "Content-Type": "application/json;odata=verbose", } request_data = { "parameters": { "__metadata": {"type": "SP.RenderListDataParameters"}, "AddRequiredFields": True, "AllowMultipleValueFilterForTaxonomyFields": True, "FilterOutChannelFoldersInDefaultDocLib": True, "RenderOptions": 5707271, } } response = requests.post( SHAREPOINT_URL, headers=headers, cookies=cookies, json=request_data, ) data = response.json() sys.stdout.write("Data retrieved from SharePoint.\n") if response.status_code != 200: hook_url = SLACK_ERROR_HOOK photo = None message = ( plain_message ) = f"SharePoint responded with status {response.status_code} :(" else: # Extract today's menu from the SharePoint data. today = datetime.date.today().strftime("%d-%m-%Y") menu = None if "error" in data: message = plain_message = f"Error: {data['error']['message']['value']}" photo = None hook_url = SLACK_ERROR_HOOK sys.stderr.write(f"SharePoint {message}\n") else: hook_url = SLACK_SUCCESS_HOOK for entry in data["ListData"]["Row"]: if entry.get("DAto") == today: menu = entry break if menu is None: sys.stderr.write("Unable to find the menu of today :(\n") sys.exit(1) menu = menu["Menutekst"].replace("\n", " ") sys.stdout.write(f"The menu of today ({today}) is: {menu}\n") # Determine appropriate emojis for the menu. emojis = [] for emoji, keywords in { "genie": ["ønske"], "art": ["tema"], "birthday": ["tillykke", "fødselsdag"], "flag-in": ["indisk", "indien"], "flag-gr": ["græsk ", "grækenland"], "flag-es": ["spansk", "spanien"], "flag-kr": ["korea"], "flag-us": ["amerikansk"], "flag-th": ["thai"], "flag-fr": ["paris", "fransk ", "frankrig"], "flag-np": ["nepal"], "hushed": ["surprise", "surprice"], "crown": ["kong"], "jack_o_lantern": ["halloween"], "spider_web": ["halloween"], "bat": ["halloween"], "ghost": ["halloween"], "christmas_tree": ["julefrokost"], "santa": ["julefrokost"], "building_construction": ["byg selv", "byg-selv", "bygselv"], "butter": ["smør", "butter"], "fish": [ "fisk", "laks", "rødspætte", "sej ", "kulmule", "brosme", "kuller", "multe", "torsk", ], "shrimp": ["reje"], "pig2": ["skinke", "gris", "nakkefilet", "nakke filet", "flæsk", "pork"], "cow": ["hakkebøf"], "cow2": ["kalv", "okse"], "chicken": ["kylling", "chicken", "høns"], "turkey": ["kalkun"], "rabbit2": [" hare"], "rooster": ["coq au vin"], "egg": [" æg ", " ægge"], "fried_egg": ["spejlæg"], "falafel": ["falafel"], "hot_pepper": ["chili", "hot sauce"], "onion": [" løg"], "carrot": ["gulerod", "gulerød"], "tangerine": ["orange", "appelsin"], "garlic": ["hvidløg"], "avocado": ["avocado", "avokado"], "mango": ["mango"], "lemon": ["citron"], "mushroom": ["svampe", "kantarel", "champignon"], "eggplant": ["moussaka", "mousakka"], "cheese_wedge": [" ost", "parmesan", "mozzarella"], "beans": ["bønne"], "olive": ["oliven"], "bell_pepper": ["peberfrugt"], "spaghetti": ["spaghetti", "bolognese"], "pizza": ["pizza"], "hamburger": ["burger"], "sandwich": ["sandwich"], "stuffed_flatbread": ["pita"], "flatbread": ["naanbrød", " naan", "fladbrød"], "taco": ["taco"], "pie": ["tærte"], "hotdog": ["hotdog", "pølse"], "fries": ["fritter "], "wine_glass": ["coq au vin"], "bowl_with_spoon": ["suppe"], "fire": ["grill", "brændt", "bbq"], "stew": ["gryde", "gullasch", " pot "], "rice": [" ris "], "ramen": ["nudler"], "burrito": ["tortilla"], "cloud": ["sky"], "potato": ["kartoffel", "kartofler"], "tomato": ["tomat"], "apple": ["æble"], "pineapple": ["ananas"], "beer": ["øl ", " øl"], "peanuts": ["peanut"], "baguette_bread": ["flute"], "green_salad": ["mixed salat"], "the_horns": ["diablo"], "wave": ["farvel"], "gift_heart": ["valentines"], "stars::fish::shrimp::lemon": ["stjerneskud"], }.items(): for keyword in keywords: if keyword in f" {menu.lower()} ": emojis.append(emoji) break if emojis: emojis = f":{': :'.join(emojis)}:" sys.stdout.write(f"Emojis determined: {emojis}\n") else: emojis = random.choice([":cook:", ":knife_fork_plate:"]) sys.stdout.write("No emojis determined. Using a fallback emoji.\n") # Pick an introduction for the menu. tokens = { "persons": random.choice([ "Abrahims", "Lewis'", "Martins", "Benjamins", "Karstens", "Christians", "Davids", "Pias", "Minas", "Mariannes", "Alexanders", "Mettes", "Jellings", "Imers", "Djinnies", "Stefans", "Pernilles", ]), "weekday" : [ "mandag", "tirsdag", "onsdag", "torsdag", "fredag", "lørdag", "søndag", ][datetime.datetime.now().weekday()], } # Pick an introduction. Now that the canteen operates with "wishes" # from us, we're only adding the "favourite dish" introduction if the # menu is *not* a wish. introductions = [ "dagens menu er", "i dag forkæler kantinen os med", "du kan godt glæde dig til senere! For vi skal have", "der bliver knoklet i køkkenet for at blive klar til at servere", "klokken 11:00 har kantinen fremtryllet en lækker omgang", ] if "ønske" not in menu.lower(): introductions.append("i dag skal vi have {persons} livret:") introduction = random.choice(introductions) if random.randint(0, 2) == 0: introduction = ( random.choice(["Det er", "Så blev det"]) + " {weekday} og " + introduction ) else: introduction = introduction.capitalize() introduction = introduction.format(**tokens) sys.stdout.write(f"Introduction picked: {introduction}\n") # Retrieve a photo of the menu from Craiyon. photo = get_photo_url(menu) # Compose message for Slack. plain_message = f"{introduction} {menu}" menu_url = MENU_URL message = f"{introduction} {emojis} <{menu_url}|*{menu}*>".strip() payload = { "text": plain_message, "blocks": [ { "type": "section", "text": { "type": "mrkdwn", "text": message, }, }, ], } if photo is not None: photo_text = "Et ukvalificeret bud på hvordan dagens menu kunne se ud" payload["blocks"].insert( 0, { "type": "image", "image_url": photo[0], "alt_text": photo_text, "title": { "type": "plain_text", "text": photo_text, }, }, ) sys.stdout.write("Posting menu to Slack...\n") hook = request.Request( hook_url, data=json.dumps(payload).encode("utf-8"), headers={ "Content-Type": "application/json", }, method="POST", ) try: response = request.urlopen(hook) except error.HTTPError as e: sys.stderr.write(f"{e}\n") sys.exit(1) sys.stdout.write("Done :)\n")