feat(tg): db schema (user_id,thread_id) PK + converter context_key
This commit is contained in:
parent
5def360f8d
commit
82dc840544
5 changed files with 283 additions and 0 deletions
51
adapter/telegram/converter.py
Normal file
51
adapter/telegram/converter.py
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from aiogram.types import Message
|
||||
|
||||
from core.protocol import Attachment, IncomingMessage, OutgoingEvent, OutgoingMessage, OutgoingUI
|
||||
|
||||
|
||||
def from_message(message: Message) -> IncomingMessage | None:
|
||||
"""Convert aiogram Message to IncomingMessage. Returns None for General topic."""
|
||||
thread_id = message.message_thread_id
|
||||
if thread_id is None:
|
||||
return None
|
||||
return IncomingMessage(
|
||||
user_id=str(message.from_user.id),
|
||||
chat_id=str(thread_id),
|
||||
text=message.text or message.caption or "",
|
||||
attachments=_extract_attachments(message),
|
||||
platform="telegram",
|
||||
)
|
||||
|
||||
|
||||
def _extract_attachments(message: Message) -> list[Attachment]:
|
||||
attachments: list[Attachment] = []
|
||||
if message.photo:
|
||||
file = message.photo[-1]
|
||||
attachments.append(Attachment(
|
||||
type="image",
|
||||
url=f"tg://file/{file.file_id}",
|
||||
mime_type="image/jpeg",
|
||||
))
|
||||
if message.document:
|
||||
attachments.append(Attachment(
|
||||
type="document",
|
||||
url=f"tg://file/{message.document.file_id}",
|
||||
mime_type=message.document.mime_type or "application/octet-stream",
|
||||
filename=message.document.file_name,
|
||||
))
|
||||
if message.voice:
|
||||
attachments.append(Attachment(
|
||||
type="audio",
|
||||
url=f"tg://file/{message.voice.file_id}",
|
||||
mime_type="audio/ogg",
|
||||
))
|
||||
return attachments
|
||||
|
||||
|
||||
def format_outgoing(event: OutgoingEvent) -> str:
|
||||
"""Extract text from an outgoing event for sending to Telegram."""
|
||||
if isinstance(event, (OutgoingMessage, OutgoingUI)):
|
||||
return event.text
|
||||
return str(event)
|
||||
102
adapter/telegram/db.py
Normal file
102
adapter/telegram/db.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sqlite3
|
||||
from contextlib import contextmanager
|
||||
|
||||
DB_PATH = os.environ.get("DB_PATH", "lambda_bot.db")
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _conn():
|
||||
con = sqlite3.connect(DB_PATH)
|
||||
con.row_factory = sqlite3.Row
|
||||
try:
|
||||
yield con
|
||||
con.commit()
|
||||
finally:
|
||||
con.close()
|
||||
|
||||
|
||||
def init_db() -> None:
|
||||
with _conn() as con:
|
||||
con.executescript("""
|
||||
CREATE TABLE IF NOT EXISTS chats (
|
||||
user_id INTEGER NOT NULL,
|
||||
thread_id INTEGER NOT NULL,
|
||||
chat_name TEXT NOT NULL DEFAULT 'Чат #1',
|
||||
archived_at DATETIME,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (user_id, thread_id)
|
||||
);
|
||||
""")
|
||||
|
||||
|
||||
def create_chat(user_id: int, thread_id: int, chat_name: str) -> None:
|
||||
with _conn() as con:
|
||||
con.execute(
|
||||
"INSERT OR IGNORE INTO chats (user_id, thread_id, chat_name) VALUES (?, ?, ?)",
|
||||
(user_id, thread_id, chat_name),
|
||||
)
|
||||
|
||||
|
||||
def get_chat(user_id: int, thread_id: int) -> dict | None:
|
||||
with _conn() as con:
|
||||
row = con.execute(
|
||||
"SELECT * FROM chats WHERE user_id = ? AND thread_id = ?",
|
||||
(user_id, thread_id),
|
||||
).fetchone()
|
||||
return dict(row) if row else None
|
||||
|
||||
|
||||
def get_active_chats(user_id: int) -> list[dict]:
|
||||
with _conn() as con:
|
||||
rows = con.execute(
|
||||
"SELECT * FROM chats WHERE user_id = ? AND archived_at IS NULL "
|
||||
"ORDER BY created_at ASC",
|
||||
(user_id,),
|
||||
).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
def count_active_chats(user_id: int) -> int:
|
||||
with _conn() as con:
|
||||
row = con.execute(
|
||||
"SELECT COUNT(*) FROM chats WHERE user_id = ? AND archived_at IS NULL",
|
||||
(user_id,),
|
||||
).fetchone()
|
||||
return row[0]
|
||||
|
||||
|
||||
def archive_chat(user_id: int, thread_id: int) -> None:
|
||||
with _conn() as con:
|
||||
con.execute(
|
||||
"UPDATE chats SET archived_at = CURRENT_TIMESTAMP "
|
||||
"WHERE user_id = ? AND thread_id = ?",
|
||||
(user_id, thread_id),
|
||||
)
|
||||
|
||||
|
||||
def rename_chat(user_id: int, thread_id: int, new_name: str) -> None:
|
||||
with _conn() as con:
|
||||
con.execute(
|
||||
"UPDATE chats SET chat_name = ? WHERE user_id = ? AND thread_id = ?",
|
||||
(new_name, user_id, thread_id),
|
||||
)
|
||||
|
||||
|
||||
def get_display_number(user_id: int, thread_id: int) -> int:
|
||||
"""Return 1-based display number for a chat (by creation order)."""
|
||||
with _conn() as con:
|
||||
row = con.execute(
|
||||
"""
|
||||
SELECT rn FROM (
|
||||
SELECT thread_id,
|
||||
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at) AS rn
|
||||
FROM chats
|
||||
WHERE user_id = ?
|
||||
) WHERE thread_id = ?
|
||||
""",
|
||||
(user_id, thread_id),
|
||||
).fetchone()
|
||||
return row[0] if row else 1
|
||||
Loading…
Add table
Add a link
Reference in a new issue