Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/tg_cli/cli/tg.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,13 +403,20 @@ async def _run():
@tg_group.command("send")
@click.argument("chat")
@click.argument("message")
@click.option("-r", "--reply-to", type=int, help="Message ID to reply to (topic ID for forum groups)")
@click.option("--no-preview", is_flag=True, help="Disable link preview")
@structured_output_options
def tg_send(chat: str, message: str, as_json: bool, as_yaml: bool):
def tg_send(chat: str, message: str, reply_to: int | None, no_preview: bool, as_json: bool, as_yaml: bool):
"""Send a MESSAGE to CHAT (name, username, or numeric ID)."""

async def _run():
async with connect() as client:
msg = await client.send_message(_parse_chat(chat), message)
msg = await client.send_message(
_parse_chat(chat),
message,
reply_to=reply_to,
link_preview=not no_preview,
)
return msg

msg = asyncio.run(_run())
Expand Down
17 changes: 17 additions & 0 deletions src/tg_cli/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@ async def fetch_history(
if ts and ts.tzinfo is None:
ts = ts.replace(tzinfo=timezone.utc)

# Extract reply_to IDs (reply_to_top_id = topic ID in forum groups)
reply_to_msg_id = None
reply_to_top_id = None
if msg.reply_to:
reply_to_msg_id = getattr(msg.reply_to, "reply_to_msg_id", None)
reply_to_top_id = getattr(msg.reply_to, "reply_to_top_id", None)

batch.append(
dict(
chat_id=chat_id,
Expand All @@ -218,6 +225,8 @@ async def fetch_history(
sender_name=sender_name,
content=content,
timestamp=ts or datetime.now(timezone.utc),
reply_to_msg_id=reply_to_msg_id,
reply_to_top_id=reply_to_top_id,
)
)

Expand Down Expand Up @@ -352,6 +361,12 @@ async def handler(event):
if ts and ts.tzinfo is None:
ts = ts.replace(tzinfo=timezone.utc)

reply_to_msg_id = None
reply_to_top_id = None
if msg.reply_to:
reply_to_msg_id = getattr(msg.reply_to, "reply_to_msg_id", None)
reply_to_top_id = getattr(msg.reply_to, "reply_to_top_id", None)

db.insert_message(
chat_id=chat.id,
chat_name=chat_name,
Expand All @@ -360,6 +375,8 @@ async def handler(event):
sender_name=sender_name,
content=content,
timestamp=ts or datetime.now(timezone.utc),
reply_to_msg_id=reply_to_msg_id,
reply_to_top_id=reply_to_top_id,
)

time_str = ts.strftime("%H:%M:%S") if ts else "??:??:??"
Expand Down
55 changes: 41 additions & 14 deletions src/tg_cli/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,27 @@

_CREATE_TABLE = """
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
platform TEXT NOT NULL DEFAULT 'telegram',
chat_id INTEGER NOT NULL,
chat_name TEXT,
msg_id INTEGER NOT NULL,
sender_id INTEGER,
sender_name TEXT,
content TEXT,
timestamp TEXT NOT NULL,
raw_json TEXT,
id INTEGER PRIMARY KEY AUTOINCREMENT,
platform TEXT NOT NULL DEFAULT 'telegram',
chat_id INTEGER NOT NULL,
chat_name TEXT,
msg_id INTEGER NOT NULL,
sender_id INTEGER,
sender_name TEXT,
content TEXT,
timestamp TEXT NOT NULL,
raw_json TEXT,
reply_to_msg_id INTEGER,
reply_to_top_id INTEGER,
UNIQUE(platform, chat_id, msg_id)
);
"""

_MIGRATIONS = [
"ALTER TABLE messages ADD COLUMN reply_to_msg_id INTEGER",
"ALTER TABLE messages ADD COLUMN reply_to_top_id INTEGER",
]

_CREATE_INDEX = """
CREATE INDEX IF NOT EXISTS idx_messages_chat_ts ON messages(chat_id, timestamp);
CREATE INDEX IF NOT EXISTS idx_messages_content ON messages(content);
Expand Down Expand Up @@ -64,6 +71,16 @@ def __init__(self, db_path: Path | str | None = None):
self.conn.row_factory = sqlite3.Row
self.conn.execute("PRAGMA journal_mode=WAL")
self.conn.executescript(_CREATE_TABLE + _CREATE_INDEX)
self._run_migrations()

def _run_migrations(self):
"""Apply schema migrations that add columns to existing tables."""
for stmt in _MIGRATIONS:
try:
self.conn.execute(stmt)
self.conn.commit()
except sqlite3.OperationalError:
pass # Column already exists

def __enter__(self):
return self
Expand Down Expand Up @@ -118,6 +135,8 @@ def insert_message(
content: str | None,
timestamp: datetime,
raw_json: dict[str, Any] | None = None,
reply_to_msg_id: int | None = None,
reply_to_top_id: int | None = None,
) -> bool:
"""Insert a message, returns True if inserted (not duplicate)."""
try:
Expand All @@ -132,9 +151,11 @@ def insert_message(
sender_name,
content,
timestamp,
raw_json
raw_json,
reply_to_msg_id,
reply_to_top_id
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(
platform,
chat_id,
Expand All @@ -145,6 +166,8 @@ def insert_message(
content,
timestamp.isoformat(),
json.dumps(raw_json, ensure_ascii=False) if raw_json else None,
reply_to_msg_id,
reply_to_top_id,
),
)
self.conn.commit()
Expand Down Expand Up @@ -175,6 +198,8 @@ def insert_batch(self, messages: list[dict], platform: str = "telegram") -> int:
else m["timestamp"]
),
json.dumps(m["raw_json"], ensure_ascii=False) if m.get("raw_json") else None,
m.get("reply_to_msg_id"),
m.get("reply_to_top_id"),
)
for m in messages
]
Expand All @@ -191,9 +216,11 @@ def insert_batch(self, messages: list[dict], platform: str = "telegram") -> int:
sender_name,
content,
timestamp,
raw_json
raw_json,
reply_to_msg_id,
reply_to_top_id
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
rows,
)
self.conn.commit()
Expand Down