frokostbot/frokostbot.py

236 lines
6.9 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.
translation = Translator().translate(menu, src="da", dest="en").text
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 = {
"Accept": "application/json;odata=verbose",
"Content-Type": "application/json;odata=verbose",
}
request_data = {
"prompt": translation,
"version": "35s5hfwn9n78gb06",
"token": token,
}
sys.stdout.write("Asking Craiyon to generate images for the menu...\n")
try:
response = requests.post(
"https://api.craiyon.com/draw",
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")
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")
# 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"]
sys.stdout.write(f"The menu of today ({today}) is: {menu}\n")
# Determine appropriate emojis for the menu.
emojis = []
for emoji, keywords in {
"flag-in": ["indisk", "indien"],
"flag-gr": ["græsk", "grækenland"],
"fish": ["fisk", "laks", "rødspætte", "sej "],
"shrimp": ["reje"],
"pig2": ["skinke", "gris"],
"cow": ["hakkebøf"],
"cow2": ["kalv", "okse"],
"chicken": ["kylling", "chicken"],
"turkey": ["kalkun"],
"rabbit2": [" hare"],
"rooster": ["coq au vin"],
"falafel": ["falafel"],
"hot_pepper": ["chili", "hot sauce"],
"onion": ["løg"],
"mango": ["mango"],
"mushroom": ["svampe", "kantarel", "champignon"],
"eggplant": ["moussaka", "mousakka"],
"beans": ["bønne"],
"hamburger": ["burger"],
"sandwich": ["sandwich"],
"stuffed_flatbread": ["pita"],
"pie": ["tærte"],
"hotdog": ["hotdog"],
"wine_glass": ["coq au vin"],
"bowl_with_spoon": ["suppe"],
"stew": ["gryde", "gullasch"],
"rice": [" ris", "ris "],
"ramen": ["nudler"],
"cloud": ["sky"],
"potato": ["kartoffel", "kartofler"],
"apple": ["æble"],
"baguette_bread": ["flute"],
"flag-dk": ["tillykke", "fødselsdag"],
"wave": ["farvel"],
"gift_heart": ["valentines"],
}.items():
for keyword in keywords:
if keyword in menu.lower():
emojis.append(emoji)
break
if emojis:
emojis = f":{': :'.join(emojis)}:"
sys.stdout.write(f"Emojis determined: {emojis}\n")
else:
emojis = ""
sys.stdout.write("No emojis determined.\n")
# Pick an introduction for the menu.
introduction = random.choice([
"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:30 har kantinen fremtryllet en lækker omgang",
])
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:
payload["blocks"][0]["accessory"] = {
"type": "image",
"image_url": photo[0],
"alt_text": (
f"En kunstig intelligens' fortolkning af dagens menu på engelsk: {photo[1]}"
),
}
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")