feat(bot): add modality map, config loader, and backend client
Implements core bot infrastructure: - modality_map: channel name to modality string mapping (personal_text, voice, fb_messenger, nd_messenger) - config: Config dataclass with env-driven configuration loading for Discord, backend, and email IMAP credentials - backend_client: async HTTP client for posting items to backend /items endpoint All modality_map tests passing (3/3)
This commit is contained in:
parent
a90c3fc172
commit
669bb3456a
7 changed files with 105 additions and 0 deletions
0
bot/chorus_bot/__init__.py
Normal file
0
bot/chorus_bot/__init__.py
Normal file
28
bot/chorus_bot/backend_client.py
Normal file
28
bot/chorus_bot/backend_client.py
Normal file
|
|
@ -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()
|
||||
45
bot/chorus_bot/config.py
Normal file
45
bot/chorus_bot/config.py
Normal file
|
|
@ -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"),
|
||||
)
|
||||
10
bot/chorus_bot/modality_map.py
Normal file
10
bot/chorus_bot/modality_map.py
Normal file
|
|
@ -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)
|
||||
5
bot/requirements.txt
Normal file
5
bot/requirements.txt
Normal file
|
|
@ -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
|
||||
0
bot/tests/__init__.py
Normal file
0
bot/tests/__init__.py
Normal file
17
bot/tests/test_modality_map.py
Normal file
17
bot/tests/test_modality_map.py
Normal file
|
|
@ -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
|
||||
Loading…
Reference in a new issue