Commit graph

19 commits

Author SHA1 Message Date
Andrey Lugovskoy
f57105d2b6 crosspost: fix long album caption + strip custom emoji and whitespace padding
Some checks failed
Build / build (push) Has been cancelled
- MAX→TG album: caption over Telegram's 1024 media-group limit is no longer lost —
  the album is sent without an inline caption and the full text follows as a
  separate message (sendMaxAlbumToTg, covers both direct and queued delivery).
- Forwarded text: strip Telegram custom_emoji (MAX can't render them; the fallback
  glyph only littered the copy) in tgEntitiesToHTML, and collapse whitespace padding
  (runs of spaces/nbsp, trailing spaces, 3+ blank lines) in crosspost captions —
  cuts bloated length (MAX ~4000 char limit) and fixes ugly gaps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ajcn7YhgeexPwbfrUNMj8u
2026-07-03 13:00:46 +04:00
Andrey Lugovskoy
9004c007f1 Fix MAX media upload; generic addon hooks + bridge improvements
- MAX media upload: drop deprecated &v=1.2.5 from /uploads and /messages
  (MAX returns 404 with it; photos/videos broke, text was unaffected).
- Run group moderation before discussion-comment ingest.
- Generic addon extension hooks; core stays agnostic to their semantics:
  CrosspostAllowed, CrosspostDeliverable, HandleMaxCommand.
- Customizable /help and /start from a file next to the binary (HELP_FILE).
- /link command; optional web_app menu button (MINIAPP_URL).
- Track bot chats and pair owners; migrations 017-019.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 20:33:17 +04:00
Andrey Lugovskoy
49af1b6432 Add created_at to pairs for pair growth stats
Migration 000016. Existing 825 rows keep created_at=0 (we can't
backfill accurately); new /bridge pairs get a real unix timestamp.

SQLite uses INSERT … ON CONFLICT … DO UPDATE SET prefix=0 (previously
INSERT OR REPLACE, which would have wiped created_at on conflict).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 15:40:28 +04:00
Andrey Lugovskoy
1290a23ad7 Add /thread_bridge: link a single TG forum thread to a separate MAX chat
Issue #38 part 2. Allows using multiple MAX group chats as per-thread
mirrors of a TG forum group, since MAX has no native thread concept.

- New table thread_pairs (tg_chat_id, tg_thread_id, max_chat_id) with
  unique(max_chat_id) — one MAX chat = at most one TG thread.
- pending gains thread_id column for thread-bridge key exchange.
- Repo methods: StartThreadBridge (TG issues key), CompleteThreadBridge
  (MAX consumes key), GetThreadMaxChat, GetThreadTgPair, UnpairThread,
  UnpairThreadByMax.
- Commands:
  * /thread_bridge in a TG forum thread (admin, non-General) -> key
  * /thread_bridge <key> in a MAX chat -> binds
  * /thread_unbridge on either side
  TG's BOT_COMMAND_INVALID rule forbids hyphens, so the commands use
  underscore.
- Routing priority: thread-bridge > regular pair.
  TG->MAX: if msg.MessageThreadID has a thread_pair, route there.
  MAX->TG: if MAX chat is in thread_pairs, route to (tg_chat, thread).
- For thread-paired MAX chats, the Part 1 reply-to-source-thread
  override is disabled: thread-paired chats have a fixed target thread,
  replies stay there.
- Safeguard: a MAX chat cannot be in both pairs and thread_pairs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:39:00 +04:00
Andrey Lugovskoy
46d2f4f748 Route MAX replies to source TG thread + crosspost/UX fixes
- Issue #38 part 1: MAX reply to a bridged TG message now lands in the
  same TG forum thread as the original, not in the pair's default
  thread. messages table gets tg_thread_id column (migration 000014);
  SaveMsg stores the TG thread, LookupTgMsgID returns it, forwardMaxToTg
  applies source thread for reply-routing (both body.ReplyTo and
  Link.Type=reply paths).
- Fix crosspost attribution heuristic: forwardMaxToTg used
  "caption != text" to detect bridge mode, which broke when MaxToTg
  replacements or whitespace made caption differ from raw body.Text —
  MAX→TG crossposts then got [MAX] prefix and bold name. Now explicit
  isCrosspost flag.
- /bridge without key in an already-linked chat no longer generates a
  fresh key; instead shows "already linked" hint with pairing guidance
  (both TG and MAX sides).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 15:26:44 +04:00
Andrey Lugovskoy
0eb19f66bb Add sync_edits toggle for crosspost edit/delete sync
Some checks are pending
Build / build (push) Waiting to run
New setting per crosspost pair — disabled by default:
- Toggle via inline button "Синк правок" in TG and MAX
- When enabled: edits and deletes sync between channels (respecting direction)
- When disabled (default): only new posts are crossposted
- Migration 000013 adds sync_edits column to crossposts table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:52:55 +03:00
Andrey Lugovskoy
d535578add Restore migration files after revert (schema already applied on prod)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 12:39:01 +03:00
Andrey Lugovskoy
8b8956af49 Revert "Add forum topic (thread_id) support for TG groups"
This reverts commit 7f5044a7fa.
2026-04-02 12:38:17 +03:00
Andrey Lugovskoy
7f5044a7fa Add forum topic (thread_id) support for TG groups
- Store tg_thread_id in pairs table (from /bridge command location)
- tgSendText/tgSendMediaToThread with message_thread_id support
- Text messages in MAX→TG now sent to correct topic
- Raw HTTP for thread_id since go-telegram-bot-api v5 lacks support

Fixes TOPIC_CLOSED errors in TG forum groups.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 12:34:57 +03:00
Andrey Lugovskoy
9b207ba7d8 Add text replacements for crosspost channels
Per-crosspost configurable find/replace rules applied during forwarding.
Supports string and regex replacements, scoped to full text or links only.
Each direction (TG→MAX, MAX→TG) has its own set of rules.

UI: inline buttons for add/delete/toggle target type in both TG and MAX bots.

Closes #14

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 12:18:53 +03:00
Andrey Lugovskoy
063231bd55 Add retry queue for MAX→TG direction
Failed MAX→TG messages now enqueued with media URL and retried
automatically. Supports photo, video, audio, file, sticker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 10:49:00 +03:00
Andrey Lugovskoy
c345217787 Track users, dual owner IDs for crosspost (TG + MAX)
- Add users table: TouchUser on every DM interaction
- Add tg_owner_id to crossposts: same person has different IDs on TG/MAX
- isCrosspostOwner checks both owner_id and tg_owner_id
- ListCrossposts matches by either owner ID
- TG user ID captured when forwarding channel post

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 22:52:13 +03:00
Andrey Lugovskoy
902339e321 Soft delete for crossposts — track deleted_at and deleted_by
Instead of DELETE, crossposts are marked with deleted_at timestamp
and deleted_by user ID. All queries filter by deleted_at = 0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 21:58:23 +03:00
Andrey Lugovskoy
da18c840e3 Add owner_id to crossposts, restrict management to owner
Only the user who created a crosspost link can change direction
or delete it. Prevents unauthorized users from managing others'
crosspost links via /crosspost list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 21:14:57 +03:00
Andrey Lugovskoy
528d5afce5 Add retry queue for failed message delivery
All checks were successful
Build / build (push) Successful in 2m15s
Messages that fail to send to MAX are saved to DB and retried
automatically every 10s with exponential backoff (10s→30s→1m→2m).
Max 20 attempts or 30 min age, then dropped with notification.

Also: /whoami command, circuit breaker notifications, 15s HTTP timeout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 11:30:09 +03:00
Andrey Lugovskoy
43bffb2dba Remove prefix support from crossposting
Channels should forward content as-is without [TG]/[MAX] prefixes.
Removed prefix column from crossposts table, HasCrosspostPrefix/
SetCrosspostPrefix methods, and /crosspost prefix command.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 16:30:11 +03:00
Andrey Lugovskoy
f7ac73ae23 Add /crosspost command for channel-to-channel crossposting
Support TG channel posts (update.ChannelPost/EditedChannelPost),
add direction control (tg>max, max>tg, both), prefix toggle,
and shared forwarding helpers (forwardTgToMax, forwardMaxToTg).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 16:26:18 +03:00
Andrey Lugovskoy
62a4233027 Security hardening and admin check tests
- Webhook paths now include token-derived secret (prevents spoofed updates)
- HTTP server with Read/Write/Idle timeouts (prevents slowloris)
- Shared HTTP client with 60s timeout for all uploads/downloads
- Removed tokens and sensitive data from debug logs
- Retry loop respects context cancellation instead of blocking sleep
- Pending bridge keys expire after 1 hour (migration 000003)
- Increased bridge key entropy from 32 to 64 bits (16 hex chars)
- Docker container runs as non-root user
- Extracted admin check helpers with unit tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 15:05:41 +03:00
Andrey Lugovskoy
28f671d41c Initial commit: TG↔MAX bridge
Bi-directional message bridge between Telegram and MAX messengers.
Features: 1:1 chat pairing, reply support, message formatting,
prefix toggle, SQLite/PostgreSQL with golang-migrate, Dockerfile,
CI/CD with GitHub Actions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 03:58:24 +03:00