375 lines
12 KiB
Python
375 lines
12 KiB
Python
"""
|
|
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")
|