commit 8d49e24ed3edb7c31212816660d9fd8e583039b6 Author: Mikkel Munch Mortensen <3xm@detfalskested.dk> Date: Fri Mar 3 14:44:57 2023 +0100 Start version control for Frokostbot Including a roadmap, which also acts as sort of a TODO. At this point, the MVP is successfully running. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e0f677 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +frokostbot.sh diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..833ef1c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +browser-cookie3==0.17.0 +requests==2.28.2 diff --git a/roadmap.md b/roadmap.md new file mode 100644 index 0000000..2d074e8 --- /dev/null +++ b/roadmap.md @@ -0,0 +1,20 @@ +# Frokostbot Roadmap + +## Release an MVP that post today's menu to Slack. + Requirements: + [x] Extract menu from SharePoint. + [x] Process menu to improve presentation with emojis. + [x] Post menu to Slack. + +## Improve the post by including a photo of the menu. + Requirements: + [ ] Ask Google Translate to translate menu from Danish to English. + [ ] Ask Crayion to generate images of the menu in English. + [ ] Include a random image from the Crayion results as an attachment in + the Slack post. + +## Improve the post with an extended description. + Requirements: + [ ] Ask ChatGPT (or similar service) to provide a ~50 word, mouthwatering + presentation of today's menu. + [ ] Include the presentation in the Slack post. diff --git a/slack.py b/slack.py new file mode 100644 index 0000000..27b4757 --- /dev/null +++ b/slack.py @@ -0,0 +1,171 @@ +""" +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 +import requests + + +# 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") + + +# 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']}" + 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"], + "hamburger": ["burger"], + "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"], + "beans": ["bønne"], + "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") + + # 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, + }, + }, + ], +} + +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")