refacto
This commit is contained in:
parent
ab1a169d45
commit
618213572f
7 changed files with 260 additions and 96 deletions
13
config.py
Normal file
13
config.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
MENU_CRECHE_PDF_URL = os.environ.get("MENU_CRECHE_PDF_URL")
|
||||||
|
MENU_TYPE = os.environ.get("MENU_KIND")
|
||||||
|
SIGNAL_SENDER = os.environ.get("SIGNAL_SENDER")
|
||||||
|
SIGNAL_RECIPIENTS = os.environ.get("SIGNAL_RECIPIENTS", default=SIGNAL_SENDER).split(
|
||||||
|
","
|
||||||
|
)
|
||||||
|
SIGNAL_API_URL = os.environ.get("SIGNAL_API_URL")
|
141
main.py
141
main.py
|
@ -1,128 +1,79 @@
|
||||||
import base64
|
import base64
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
|
||||||
import requests
|
import requests
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from pdfminer.high_level import extract_text
|
from pdfminer.high_level import extract_text
|
||||||
|
|
||||||
|
from config import (
|
||||||
|
MENU_CRECHE_PDF_URL,
|
||||||
|
MENU_TYPE,
|
||||||
|
SIGNAL_SENDER,
|
||||||
|
SIGNAL_RECIPIENTS,
|
||||||
|
SIGNAL_API_URL,
|
||||||
|
)
|
||||||
|
from menus import Menus, MenuMessageFormatter
|
||||||
|
from message_sender import SignalMessager
|
||||||
|
from pdf_downloader import download
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
def main():
|
def send_signal_message(
|
||||||
menu_link = os.environ.get("MENU_CRECHE_PDF_URL")
|
days: list[str], menu: dict[str, dict[str, str]], menu_pdf_file: Path
|
||||||
menu_pdf = requests.get(menu_link).content
|
) -> None:
|
||||||
|
|
||||||
tmp_folder = Path(tempfile.mkdtemp())
|
|
||||||
menu_file = tmp_folder / "menu.pdf"
|
|
||||||
menu_file.write_bytes(menu_pdf)
|
|
||||||
|
|
||||||
text = extract_text(menu_file, page_numbers=[0])
|
|
||||||
|
|
||||||
previous_was_title = False
|
|
||||||
curated_text = ""
|
|
||||||
for line in text.splitlines():
|
|
||||||
if len(line) == 1:
|
|
||||||
previous_was_title = True
|
|
||||||
elif len(line) > 1 and line[1] == " ":
|
|
||||||
curated_text += f"{line[2:]}\n"
|
|
||||||
previous_was_title = True
|
|
||||||
elif len(line) > 0 and line[0].islower():
|
|
||||||
curated_text = curated_text.removesuffix("\n")
|
|
||||||
curated_text += f"{line}\n"
|
|
||||||
previous_was_title = False
|
|
||||||
elif line == '' and previous_was_title:
|
|
||||||
previous_was_title = False
|
|
||||||
else:
|
|
||||||
curated_text += f"{line}\n"
|
|
||||||
previous_was_title = False
|
|
||||||
|
|
||||||
cells = curated_text.split("\n\n")
|
|
||||||
|
|
||||||
# remove month
|
|
||||||
month = cells.pop(0)
|
|
||||||
|
|
||||||
# remove meals titles
|
|
||||||
for i in range(5):
|
|
||||||
cells.pop(0)
|
|
||||||
|
|
||||||
days = []
|
|
||||||
for i in range(5):
|
|
||||||
days.append(cells.pop(0))
|
|
||||||
|
|
||||||
menus = {
|
|
||||||
"introduction": {
|
|
||||||
days[0]: {"midi": cells[0], "gouter": cells[5]},
|
|
||||||
days[1]: {"midi": cells[1], "gouter": cells[6]},
|
|
||||||
days[2]: {"midi": cells[2], "gouter": cells[7]},
|
|
||||||
days[3]: {"midi": cells[3], "gouter": cells[8]},
|
|
||||||
days[4]: {"midi": cells[4], "gouter": cells[9]},
|
|
||||||
},
|
|
||||||
"diversification": {
|
|
||||||
days[0]: {"midi": cells[10], "gouter": cells[15]},
|
|
||||||
days[1]: {"midi": cells[11], "gouter": cells[16]},
|
|
||||||
days[2]: {"midi": cells[12], "gouter": cells[17]},
|
|
||||||
days[3]: {"midi": cells[13], "gouter": cells[18]},
|
|
||||||
days[4]: {"midi": cells[14], "gouter": cells[19]},
|
|
||||||
},
|
|
||||||
"petit musclé": {
|
|
||||||
days[0]: {"midi": cells[20], "gouter": cells[24]},
|
|
||||||
days[1]: {"midi": cells[21], "gouter": cells[25]},
|
|
||||||
days[2]: {"midi": cells[22], "gouter": cells[26]},
|
|
||||||
days[3]: {"midi": cells[23], "gouter": cells[27]},
|
|
||||||
days[4]: {"midi": cells[32], "gouter": cells[39]},
|
|
||||||
},
|
|
||||||
"petit lion": {
|
|
||||||
days[0]: {"midi": cells[28], "gouter": cells[35]},
|
|
||||||
days[1]: {"midi": cells[29], "gouter": cells[36]},
|
|
||||||
days[2]: {"midi": cells[30], "gouter": cells[37]},
|
|
||||||
days[3]: {"midi": cells[31], "gouter": cells[38]},
|
|
||||||
days[4]: {"midi": cells[34], "gouter": cells[33]},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
menu = os.environ.get("MENU_KIND")
|
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
message = f"""
|
message = f"""
|
||||||
**Menu de la crèche** (message automatique)
|
**Menu de la crèche** (message automatique)
|
||||||
|
|
||||||
Menu {menu}:
|
Menu {MENU_TYPE}:
|
||||||
|
|
||||||
{days[0].upper()}:
|
{days[0].upper()}:
|
||||||
*Midi*: {menus[menu][days[0]]["midi"]}
|
*Midi*: {menu[days[0]]["midi"]}
|
||||||
*Goûter*: {menus[menu][days[0]]["gouter"]}
|
*Goûter*: {menu[days[0]]["gouter"]}
|
||||||
|
|
||||||
{days[1].upper()}:
|
{days[1].upper()}:
|
||||||
*Midi*: {menus[menu][days[1]]["midi"]}
|
*Midi*: {menu[days[1]]["midi"]}
|
||||||
*Goûter*: {menus[menu][days[1]]["gouter"]}
|
*Goûter*: {menu[days[1]]["gouter"]}
|
||||||
|
|
||||||
{days[2].upper()}:
|
{days[2].upper()}:
|
||||||
*Midi*: {menus[menu][days[2]]["midi"]}
|
*Midi*: {menu[days[2]]["midi"]}
|
||||||
*Goûter*: {menus[menu][days[2]]["gouter"]}
|
*Goûter*: {menu[days[2]]["gouter"]}
|
||||||
|
|
||||||
{days[3].upper()}:
|
{days[3].upper()}:
|
||||||
*Midi*: {menus[menu][days[3]]["midi"]}
|
*Midi*: {menu[days[3]]["midi"]}
|
||||||
*Goûter*: {menus[menu][days[3]]["gouter"]}
|
*Goûter*: {menu[days[3]]["gouter"]}
|
||||||
|
|
||||||
{days[4].upper()}:
|
{days[4].upper()}:
|
||||||
*Midi*: {menus[menu][days[4]]["midi"]}
|
*Midi*: {menu[days[4]]["midi"]}
|
||||||
*Goûter*: {menus[menu][days[4]]["gouter"]}
|
*Goûter*: {menu[days[4]]["gouter"]}
|
||||||
"""
|
"""
|
||||||
text_mode = "styled"
|
text_mode = "styled"
|
||||||
sender = os.environ.get("SIGNAL_SENDER")
|
attachment = base64.b64encode(menu_pdf_file.read_bytes())
|
||||||
recipient = os.environ.get("SIGNAL_RECIPIENT")
|
|
||||||
attachment = base64.b64encode(menu_pdf)
|
|
||||||
body = {
|
body = {
|
||||||
"message": message,
|
"message": message,
|
||||||
"text_mode": text_mode,
|
"text_mode": text_mode,
|
||||||
"number": sender,
|
"number": SIGNAL_SENDER,
|
||||||
"recipients": [recipient],
|
"recipients": SIGNAL_RECIPIENTS,
|
||||||
"base64_attachments": [f"data:application/pdf;filename=menu.pdf;base64,{attachment.decode()}"],
|
"base64_attachments": [
|
||||||
|
f"data:application/pdf;filename=menu.pdf;base64,{attachment.decode()}"
|
||||||
|
],
|
||||||
}
|
}
|
||||||
host = os.environ.get("SIGNAL_API_URL")
|
|
||||||
|
|
||||||
requests.post(host, headers=headers, json=body)
|
requests.post(SIGNAL_API_URL, headers=headers, json=body)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
def main():
|
||||||
|
menu_pdf_file = download(MENU_CRECHE_PDF_URL, file_name="menu.pdf")
|
||||||
|
|
||||||
|
menus = Menus(menu_pdf_file)
|
||||||
|
|
||||||
|
message_formatter = MenuMessageFormatter()
|
||||||
|
message = message_formatter.create_message(menus, MENU_TYPE)
|
||||||
|
|
||||||
|
signal_messager = SignalMessager(SIGNAL_API_URL, SIGNAL_SENDER)
|
||||||
|
signal_messager.send_message(message, menu_pdf_file, SIGNAL_RECIPIENTS)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
144
menus.py
Normal file
144
menus.py
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import base64
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from pdfminer.high_level import extract_text
|
||||||
|
|
||||||
|
|
||||||
|
class Menus:
|
||||||
|
def __init__(self, menu_pdf_file: Path):
|
||||||
|
self.menu_pdf_file = menu_pdf_file
|
||||||
|
|
||||||
|
self._cells = self.extract_cells()
|
||||||
|
self.month = self.extract_month()
|
||||||
|
self.days = self.extract_days()
|
||||||
|
|
||||||
|
self.introduction = self.extract_introduction_menu()
|
||||||
|
self.diversification = self.extract_diversification_menu()
|
||||||
|
self.petit_muscle = self.extract_petit_muscle_menu()
|
||||||
|
self.petit_lion = self.extract_petit_lion_menu()
|
||||||
|
|
||||||
|
def extract_cells(self) -> list[str]:
|
||||||
|
text = extract_text(self.menu_pdf_file, page_numbers=[0])
|
||||||
|
|
||||||
|
previous_was_title = False
|
||||||
|
curated_text = ""
|
||||||
|
for element in text.splitlines():
|
||||||
|
line = Line(element)
|
||||||
|
if line.is_glitch():
|
||||||
|
previous_was_title = True
|
||||||
|
elif line.starts_with_glitch():
|
||||||
|
curated_text += f"{line.text[2:]}\n"
|
||||||
|
previous_was_title = True
|
||||||
|
elif line.is_part_of_previous_line():
|
||||||
|
curated_text = curated_text.removesuffix("\n")
|
||||||
|
curated_text += f"{line.text}\n"
|
||||||
|
previous_was_title = False
|
||||||
|
elif line.text == "" and previous_was_title:
|
||||||
|
previous_was_title = False
|
||||||
|
else:
|
||||||
|
curated_text += f"{line.text}\n"
|
||||||
|
previous_was_title = False
|
||||||
|
|
||||||
|
return curated_text.split("\n\n")
|
||||||
|
|
||||||
|
def extract_month(self) -> str:
|
||||||
|
return self._cells[0]
|
||||||
|
|
||||||
|
def extract_days(self) -> list[str]:
|
||||||
|
return self._cells[6:11]
|
||||||
|
|
||||||
|
def extract_introduction_menu(self) -> dict[str, dict[str, str]]:
|
||||||
|
return {
|
||||||
|
self.days[0]: self.build_menu_of_day(11, 16),
|
||||||
|
self.days[1]: self.build_menu_of_day(12, 17),
|
||||||
|
self.days[2]: self.build_menu_of_day(13, 18),
|
||||||
|
self.days[3]: self.build_menu_of_day(14, 19),
|
||||||
|
self.days[4]: self.build_menu_of_day(15, 20),
|
||||||
|
}
|
||||||
|
|
||||||
|
def extract_diversification_menu(self) -> dict[str, dict[str, str]]:
|
||||||
|
return {
|
||||||
|
self.days[0]: self.build_menu_of_day(21, 26),
|
||||||
|
self.days[1]: self.build_menu_of_day(22, 27),
|
||||||
|
self.days[2]: self.build_menu_of_day(23, 28),
|
||||||
|
self.days[3]: self.build_menu_of_day(24, 29),
|
||||||
|
self.days[4]: self.build_menu_of_day(25, 30),
|
||||||
|
}
|
||||||
|
|
||||||
|
def extract_petit_muscle_menu(self) -> dict[str, dict[str, str]]:
|
||||||
|
return {
|
||||||
|
self.days[0]: self.build_menu_of_day(31, 35),
|
||||||
|
self.days[1]: self.build_menu_of_day(32, 36),
|
||||||
|
self.days[2]: self.build_menu_of_day(33, 37),
|
||||||
|
self.days[3]: self.build_menu_of_day(34, 38),
|
||||||
|
self.days[4]: self.build_menu_of_day(43, 50),
|
||||||
|
}
|
||||||
|
|
||||||
|
def extract_petit_lion_menu(self):
|
||||||
|
return {
|
||||||
|
self.days[0]: self.build_menu_of_day(39, 46),
|
||||||
|
self.days[1]: self.build_menu_of_day(40, 47),
|
||||||
|
self.days[2]: self.build_menu_of_day(41, 48),
|
||||||
|
self.days[3]: self.build_menu_of_day(42, 49),
|
||||||
|
self.days[4]: self.build_menu_of_day(45, 44),
|
||||||
|
}
|
||||||
|
|
||||||
|
def build_menu_of_day(self, midi: int, gouter: int) -> dict[str, str]:
|
||||||
|
return {"midi": self._cells[midi], "gouter": self._cells[gouter]}
|
||||||
|
|
||||||
|
|
||||||
|
class Line:
|
||||||
|
def __init__(self, text: str):
|
||||||
|
self.text = text
|
||||||
|
|
||||||
|
def is_glitch(self) -> bool:
|
||||||
|
return len(self.text) == 1
|
||||||
|
|
||||||
|
def starts_with_glitch(self) -> bool:
|
||||||
|
return len(self.text) > 1 and self.text[1] == " "
|
||||||
|
|
||||||
|
def is_part_of_previous_line(self) -> bool:
|
||||||
|
return len(self.text) > 0 and self.text[0].islower()
|
||||||
|
|
||||||
|
|
||||||
|
class MenuMessageFormatter:
|
||||||
|
@staticmethod
|
||||||
|
def create_message(menus: Menus, menu_type: str) -> str:
|
||||||
|
days = menus.days
|
||||||
|
if menu_type.lower() == "introduction":
|
||||||
|
menu = menus.introduction
|
||||||
|
elif menu_type.lower() == "diversification":
|
||||||
|
menu = menus.diversification
|
||||||
|
elif menu_type.lower() == "petit muscle":
|
||||||
|
menu = menus.petit_muscle
|
||||||
|
elif menu_type.lower() == "petit lion":
|
||||||
|
menu = menus.petit_lion
|
||||||
|
else:
|
||||||
|
raise Exception(f"Unknown menu type: {menu_type}")
|
||||||
|
|
||||||
|
return f"""
|
||||||
|
**Menu de la crèche** (message automatique)
|
||||||
|
|
||||||
|
Menu {menu_type}:
|
||||||
|
|
||||||
|
{days[0].upper()}:
|
||||||
|
*Midi*: {menu[days[0]]["midi"]}
|
||||||
|
*Goûter*: {menu[days[0]]["gouter"]}
|
||||||
|
|
||||||
|
{days[1].upper()}:
|
||||||
|
*Midi*: {menu[days[1]]["midi"]}
|
||||||
|
*Goûter*: {menu[days[1]]["gouter"]}
|
||||||
|
|
||||||
|
{days[2].upper()}:
|
||||||
|
*Midi*: {menu[days[2]]["midi"]}
|
||||||
|
*Goûter*: {menu[days[2]]["gouter"]}
|
||||||
|
|
||||||
|
{days[3].upper()}:
|
||||||
|
*Midi*: {menu[days[3]]["midi"]}
|
||||||
|
*Goûter*: {menu[days[3]]["gouter"]}
|
||||||
|
|
||||||
|
{days[4].upper()}:
|
||||||
|
*Midi*: {menu[days[4]]["midi"]}
|
||||||
|
*Goûter*: {menu[days[4]]["gouter"]}
|
||||||
|
"""
|
||||||
|
|
36
message_sender.py
Normal file
36
message_sender.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import base64
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from menus import MenuMessageFormatter, Menus
|
||||||
|
|
||||||
|
|
||||||
|
class SignalMessager:
|
||||||
|
def __init__(self, api_url: str, user_number: str):
|
||||||
|
self.api_url = api_url
|
||||||
|
self.user_number = user_number
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_headers() -> dict[str, str]:
|
||||||
|
return {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_attachment(pdf: Path):
|
||||||
|
return base64.b64encode(pdf.read_bytes())
|
||||||
|
|
||||||
|
def send_message(self, message: str, pdf_file: Path, recipients: list[str]):
|
||||||
|
headers = self.create_headers()
|
||||||
|
attachment = self.create_attachment(pdf_file)
|
||||||
|
|
||||||
|
body = {
|
||||||
|
"message": message,
|
||||||
|
"text_mode": "styled",
|
||||||
|
"number": self.user_number,
|
||||||
|
"recipients": recipients,
|
||||||
|
"base64_attachments": [
|
||||||
|
f"data:application/pdf;filename=menu.pdf;base64,{attachment.decode()}"
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
requests.post(self.api_url, headers=headers, json=body)
|
18
pdf_downloader.py
Normal file
18
pdf_downloader.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
def download(
|
||||||
|
url: str,
|
||||||
|
destination_folder: Path = Path(tempfile.mkdtemp()),
|
||||||
|
file_name: str = "file.pdf",
|
||||||
|
) -> Path:
|
||||||
|
response = requests.get(url)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
output_file = destination_folder / file_name
|
||||||
|
output_file.write_bytes(response.content)
|
||||||
|
|
||||||
|
return output_file
|
2
requirements-dev.txt
Normal file
2
requirements-dev.txt
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
-r requirements.txt
|
||||||
|
ruff
|
|
@ -1,3 +1,3 @@
|
||||||
requests~=2.32.3
|
requests~=2.32.3
|
||||||
pdfminer.six~=20250506
|
pdfminer.six==20250506
|
||||||
python-dotenv~=1.1.0
|
python-dotenv~=1.1.0
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue