chore: move telegram projects to top-level
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = ["requests", "markdown-it-py", "sulguk", "tomli; python_version < '3.11'"]
|
||||
# ///
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from markdown_it import MarkdownIt
|
||||
from sulguk import transform_html
|
||||
|
||||
CONFIG_PATH = Path.home() / ".codex" / "telegram.toml"
|
||||
ERR_PATH = Path.home() / ".codex" / "telegram_last_error.txt"
|
||||
|
||||
|
||||
def _load_toml(path: Path) -> dict:
|
||||
if not path.exists():
|
||||
return {}
|
||||
try:
|
||||
import tomllib # type: ignore[attr-defined]
|
||||
except ModuleNotFoundError:
|
||||
import tomli as tomllib # type: ignore[import-not-found]
|
||||
return tomllib.loads(path.read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def _config_get(config: dict, key: str):
|
||||
if key in config:
|
||||
return config[key]
|
||||
nested = config.get("telegram")
|
||||
if isinstance(nested, dict) and key in nested:
|
||||
return nested[key]
|
||||
return None
|
||||
|
||||
|
||||
def main() -> None:
|
||||
config = _load_toml(CONFIG_PATH)
|
||||
bot_token = _config_get(config, "bot_token")
|
||||
chat_id = _config_get(config, "chat_id")
|
||||
if not bot_token or chat_id is None:
|
||||
raise KeyError("telegram.toml must include bot_token and chat_id")
|
||||
bot_token = str(bot_token)
|
||||
chat_id = str(chat_id)
|
||||
|
||||
event = json.loads(sys.argv[1])
|
||||
|
||||
md = event["last-assistant-message"].rstrip()
|
||||
thread_id = event.get("thread-id")
|
||||
if thread_id:
|
||||
md += f"\n\nthread: `{thread_id}`"
|
||||
|
||||
html = MarkdownIt("commonmark", {"html": False}).render(md)
|
||||
rendered = transform_html(html)
|
||||
|
||||
text = re.sub(r"(?m)^(\s*)•", r"\1-", rendered.text)
|
||||
|
||||
# FIX: Telegram requires MessageEntity.language (if present) to be a String.
|
||||
entities = []
|
||||
for e in rendered.entities:
|
||||
d = dict(e)
|
||||
if "language" in d and not isinstance(d["language"], str):
|
||||
d.pop("language", None)
|
||||
entities.append(d)
|
||||
|
||||
r = requests.post(
|
||||
f"https://api.telegram.org/bot{bot_token}/sendMessage",
|
||||
json={
|
||||
"chat_id": chat_id,
|
||||
"text": text,
|
||||
"entities": entities,
|
||||
"disable_web_page_preview": True,
|
||||
},
|
||||
timeout=15,
|
||||
)
|
||||
|
||||
try:
|
||||
data = r.json()
|
||||
except Exception:
|
||||
data = {"ok": False, "description": r.text}
|
||||
|
||||
if not (r.status_code == 200 and data.get("ok") is True):
|
||||
ERR_PATH.write_text(
|
||||
f"{r.status_code}\n{data.get('description','')}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
pass
|
||||
@@ -0,0 +1,34 @@
|
||||
# Notify Telegram (Codex)
|
||||
|
||||
Send Codex completion summaries to Telegram with safe Markdown rendering and stable list bullets.
|
||||
|
||||
## Install
|
||||
|
||||
1. Ensure `uv` is installed.
|
||||
2. Copy the script to `~/.codex/notify_telegram.py`.
|
||||
3. Create your [Telegram creds](https://t.me/botfather) file at `~/.codex/telegram.toml`.
|
||||
|
||||
Example:
|
||||
|
||||
```toml
|
||||
bot_token = "123456:ABCDEF..."
|
||||
chat_id = 462722
|
||||
```
|
||||
|
||||
## Configure
|
||||
|
||||
Add a `notify` entry to `~/.codex/config.toml`:
|
||||
|
||||
```toml
|
||||
notify = ["uv", "run", "-q", "/home/user/.codex/notify_telegram.py"]
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The script reads `last-assistant-message` and treats it as Markdown.
|
||||
- Markdown is rendered to HTML, converted to Telegram text/entities via `sulguk`, then posted with `requests`.
|
||||
- List bullets are normalized from `•` to `-` to keep Telegram output consistent.
|
||||
|
||||
## Files
|
||||
|
||||
- `notify_telegram.py`: the notifier script
|
||||
Reference in New Issue
Block a user