diff --git a/bot/chorus_bot/__init__.py b/bot/chorus_bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/chorus_bot/backend_client.py b/bot/chorus_bot/backend_client.py new file mode 100644 index 0000000..96bc6d8 --- /dev/null +++ b/bot/chorus_bot/backend_client.py @@ -0,0 +1,28 @@ +from datetime import datetime +import httpx + + +class BackendClient: + def __init__(self, base_url: str): + self._base_url = base_url.rstrip("/") + + async def create_item( + self, + *, + modality: str, + raw_content: str, + captured_at: datetime, + discord_message_id: str | None, + sender_id: str | None = None, + ) -> dict: + payload = { + "modality": modality, + "raw_content": raw_content, + "captured_at": captured_at.isoformat(), + "discord_message_id": discord_message_id, + "sender_id": sender_id, + } + async with httpx.AsyncClient() as client: + resp = await client.post(f"{self._base_url}/items", json=payload, timeout=10) + resp.raise_for_status() + return resp.json() diff --git a/bot/chorus_bot/config.py b/bot/chorus_bot/config.py new file mode 100644 index 0000000..cfaaf76 --- /dev/null +++ b/bot/chorus_bot/config.py @@ -0,0 +1,45 @@ +import os +from dataclasses import dataclass + + +@dataclass +class Config: + discord_token: str + guild_id: int + inbox_channel_id: int + triage_role_id: int + backend_base_url: str + google_voice_imap_host: str + google_voice_imap_user: str + google_voice_imap_password: str + bh_email_imap_host: str + bh_email_imap_user: str + bh_email_imap_password: str + personal_email_imap_host: str + personal_email_imap_user: str + personal_email_imap_password: str + + +def load_config() -> Config: + def env(name: str) -> str: + value = os.environ.get(name) + if not value: + raise RuntimeError(f"Missing required env var: {name}") + return value + + return Config( + discord_token=env("CHORUS_DISCORD_TOKEN"), + guild_id=int(env("CHORUS_GUILD_ID")), + inbox_channel_id=int(env("CHORUS_INBOX_CHANNEL_ID")), + triage_role_id=int(env("CHORUS_TRIAGE_ROLE_ID")), + backend_base_url=env("CHORUS_BACKEND_BASE_URL"), + google_voice_imap_host=env("CHORUS_GVOICE_IMAP_HOST"), + google_voice_imap_user=env("CHORUS_GVOICE_IMAP_USER"), + google_voice_imap_password=env("CHORUS_GVOICE_IMAP_PASSWORD"), + bh_email_imap_host=env("CHORUS_BH_EMAIL_IMAP_HOST"), + bh_email_imap_user=env("CHORUS_BH_EMAIL_IMAP_USER"), + bh_email_imap_password=env("CHORUS_BH_EMAIL_IMAP_PASSWORD"), + personal_email_imap_host=env("CHORUS_PERSONAL_EMAIL_IMAP_HOST"), + personal_email_imap_user=env("CHORUS_PERSONAL_EMAIL_IMAP_USER"), + personal_email_imap_password=env("CHORUS_PERSONAL_EMAIL_IMAP_PASSWORD"), + ) diff --git a/bot/chorus_bot/modality_map.py b/bot/chorus_bot/modality_map.py new file mode 100644 index 0000000..a4421ee --- /dev/null +++ b/bot/chorus_bot/modality_map.py @@ -0,0 +1,10 @@ +CHANNEL_TO_MODALITY = { + "personal-text": "personal_text", + "voice": "voice", + "fb-messenger": "fb_messenger", + "nd-messenger": "nd_messenger", +} + + +def modality_for_channel(channel_name: str) -> str | None: + return CHANNEL_TO_MODALITY.get(channel_name) diff --git a/bot/requirements.txt b/bot/requirements.txt new file mode 100644 index 0000000..f114ca1 --- /dev/null +++ b/bot/requirements.txt @@ -0,0 +1,5 @@ +discord.py==2.4.0 +httpx==0.27.2 +aioimaplib==1.1.0 +pytest==8.3.3 +pytest-asyncio==0.24.0 diff --git a/bot/tests/__init__.py b/bot/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bot/tests/test_modality_map.py b/bot/tests/test_modality_map.py new file mode 100644 index 0000000..9ae9e06 --- /dev/null +++ b/bot/tests/test_modality_map.py @@ -0,0 +1,17 @@ +from chorus_bot import modality_map + + +def test_known_channels_map_to_modalities(): + assert modality_map.modality_for_channel("personal-text") == "personal_text" + assert modality_map.modality_for_channel("voice") == "voice" + assert modality_map.modality_for_channel("fb-messenger") == "fb_messenger" + assert modality_map.modality_for_channel("nd-messenger") == "nd_messenger" + + +def test_unknown_channel_returns_none(): + assert modality_map.modality_for_channel("random-chat") is None + + +def test_inbox_and_general_channels_are_not_mapped(): + assert modality_map.modality_for_channel("inbox") is None + assert modality_map.modality_for_channel("general") is None