Commit graph

193 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
a7dab2896b MAX photo upload fix + SDK v1.6.24, native group picker, addon hooks
Some checks failed
Build / build (push) Has been cancelled
- Fix MAX photo upload: bypass SDK (it appends deprecated v=1.2.5 → 404) with a
  custom two-step image upload, mirroring the video/file path.
- Update MAX SDK v1.4.2 → v1.6.24; pin base URL to platform-api.max.ru
  (v1.6.x defaults to platform-api2.max.ru, whose TLS cert prod doesn't trust).
- Native group picker: TG sender supports KeyboardButtonRequestChat + chat_shared;
  new generic addon hook HandleChatShared (core stays semantics-agnostic).
- Route unhandled MAX callbacks to the addon; add Repository.ListBotChats for
  group pickers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-23 21:57:45 +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
1235aa05de Addon extension framework + crosspost UX/reliability improvements
Some checks failed
Build / build (push) Has been cancelled
- Pluggable optional addons via `addon` build tag: Addon interface,
  stub for the public build, generic b.extraCommands injection so the core
  stays agnostic of any extension. README section + working examples/echoaddon.
- publishTgCrosspost: единая точка кросспоста TG→MAX (формат entity→markdown +
  замены), переиспользуется вместо дублирования.
- TG_API_URL local mode: getFile отдаёт абсолютный путь — срезаем префикс до
  токена, чтобы файлы отдавались через статику (nginx).
- Автозамены: понятная инструкция вместо "from | to", человеко-читаемые ошибки.
- Очередь доставки: причина + номер поста при недоставке в MAX, быстрый дроп
  перманентных ошибок (send-message.empty, нет прав) вместо 30 ретраев.
- ForwardOrigin msg_id проброшен в Addon (старт операций с конкретного поста).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 22:34:17 +04:00
Andrey Lugovskoy
402b7d4f86 Inline URL buttons + addon callback routing
Some checks are pending
Build / build (push) Waiting to run
InlineKeyboardButton gains URL (renders a link button instead of a
callback). Used by the optional addon for a "buy" button.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 16:29:17 +04:00
Andrey Lugovskoy
2e9113953f Markdown: trim newlines/tabs (not just spaces) at entity boundaries
tgEntitiesToMarkdown pushed leading/trailing spaces out of an entity
span (fix for caption "** bold **"), but TG often includes trailing
newlines inside a bold/header entity ("Header\n\n" bold). The closing
** then landed after the newline ("**Header\n\n**"), which MAX/CommonMark
won't treat as bold — the markers leaked as literal text.

Generalize the boundary trim to all ASCII whitespace (space, \n, \r,
\t) via isMarkupSpace. Test added for trailing-newline bold.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 15:24:54 +04:00
Andrey Lugovskoy
9f49b14e62 addon: route inline-button callbacks to optional addon
handleTgCallback offers each callback to the addon first (when present);
if the addon owns it (by its data prefix) the bridge stops. Adds
HandleCallback to the Addon interface. No-op in the public build.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 14:29:35 +04:00
Andrey Lugovskoy
9a5f187caf Add generic optional-addon extension point
Some checks are pending
Build / build (push) Waiting to run
Introduces a minimal Addon interface (Start, HandleDMCommand,
HandleDMForward) wired into the bridge:

- bridge.go: Bridge gains an addon field, started as a goroutine in Run
  when non-nil; loadAddon is provided per build configuration.
- addon_stub.go (default build, //go:build !addon): loadAddon returns
  nil — the public binary has no addon and no extra behavior.
- telegram.go: private-chat messages and channel forwards are offered to
  the addon first when present; if it handles them, the bridge skips its
  own processing. No-op in the public build.
- TGSender.ForwardMessage(from, to, msgID, silent) added — a generic Bot
  API wrapper returning the full message; useful for any addon that needs
  to relay existing messages.

The concrete addon implementation is compiled separately via the `addon`
build tag and is not part of this repository.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 01:57:30 +04:00
Andrey Lugovskoy
fa11af83a6 Forward MAX→TG: re-attach media from Link.Message for forwards
Issue #44: forwarding a MAX message that contained a file/photo/video
from group A into group B (bridged to TG) delivered only the text to
TG; the media was missing.

The MAX SDK only converts Link.Message.RawAttachments → Attachments
when body.RawAttachments == nil. MAX sends body.RawAttachments=[] (an
empty array, NOT nil) for forwards, so the else-if branch never fires
and Link.Message.Attachments stays empty. Our merge-into-body code in
forwardMaxToTg saw nothing to add.

Parse Link.Message.RawAttachments ourselves: read each entry's "type",
unmarshal to the concrete PhotoAttachment / VideoAttachment / etc., and
prepend to body.Attachments before the downstream send loop. Unknown
types are skipped silently.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 00:44:30 +04:00
Andrey Lugovskoy
c575e3e10c Fix Markdown delimiter rules at TG entity boundaries
Two reported issues, same root cause: markdown delimiter has to sit
flush against the content — "** bold**" or "**bold **" with spaces
inside the entity body is not closed by CommonMark/MAX parsers, so the
asterisks leak into the rendered text.

- markup.go (issue #48): when TG sends a bold/italic/etc entity that
  also captures leading or trailing spaces (e.g. "Всего 5 минут ..."
  with trailing space), trim those spaces out of the entity span before
  inserting markers. If the entity is all-whitespace, skip it.
  Tests updated to reflect the new (correct) expected output.

- telegram.go (issue #50): crosspost photo branch in forwardTgToMax
  built mdCaption from entities but only set format=markdown for bridge
  posts. Subscribers saw the raw "**bold**" under the picture. Now
  format is set whenever the text is non-empty, regardless of bridge
  vs crosspost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-18 00:36:59 +04:00
Victor Golovanenko
06a1e5ef70 fix: sendTgMediaFromURL: pass parseMode, replyToID for stickers and etc.
Some checks failed
Build / build (push) Has been cancelled
2026-06-14 22:07:38 +04:00
Andrey Lugovskoy
bb7bcfdb33 Drop permanent queue errors fast + log MAX bot add/remove events
Some checks failed
Build / build (push) Has been cancelled
Two queue items in send_queue could pile up indefinitely behind the
30-attempt limit and block per-destination ordering (hasPendingForChat):

  tg2max: "MAX attachment not ready after 10 retries", "must be at most"
  max2tg: "can't parse entities", "caption/message is too long"

These never become deliverable — drop them on the first failure so the
queue can drain and new messages for the same destination skip the
queued path.

Also: subscribe-listed bot_added / bot_removed updates weren't handled,
so when admins kicked the bot from a MAX chat the only signal was
silence in logs. listenMax now logs both events with chat id, actor user
id, username and display name; the DB pair is left intact in case the
bot is re-added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-01 13:15:35 +04:00
Andrey Lugovskoy
9f4c1dc36a Split TG→MAX text on the 4000-char MAX limit
MAX API rejects messages where 'text' field exceeds 4000 characters
("Field 'text' size (N) must be at most 4000"). Long Telegram posts
landed in send_queue and retried forever until the 30-attempt cap,
then dropped — meanwhile the source user got nothing in MAX.

sendMaxDirectFormatted now slices text > 3900 runes into chunks at
newline/space boundaries (last 20% of the chunk) and sends them as a
chain: first chunk keeps the attachment + reply, the rest go as
text-only follow-ups linked to the first chunk via reply. Returns
the mid of the first chunk so the message-mapping stays valid for
edits and deletes.

3900 (not 4000) leaves margin for the byte/rune ambiguity in MAX's
limit and for the attribution prefix that forwardTgToMax prepends.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-31 01:00:53 +04:00
Andrey Lugovskoy
f4f97bd7af MAX webhook: unsubscribe on graceful shutdown
Without DELETE /subscriptions, two problems persist after the bridge
stops or switches to polling:

1. Switching webhook → polling — MAX keeps trying to POST to the old
   webhook URL and the bot's polling getUpdates can return nothing
   relevant until MAX gives up on its own backoff schedule.
2. During downtime — MAX retries the dead webhook with exponential
   backoff; first delivery after restart may be delayed by minutes.

listenMax now defers a Subscriptions.Unsubscribe(whURL) with a fresh
5s context (the parent ctx is already cancelled by shutdown), so
subscriptions are released cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-30 22:22:04 +04:00
Andrey Lugovskoy
a14047303f Queue expire: notify crosspost owner DM, not the channel
Some checks failed
Build / build (push) Has been cancelled
processQueue dropped expired tg2max items with a SendMessage to
item.SrcChatID. For crossposts (TG-channel → MAX) that meant the
"message not delivered after N attempts" notice landed in the channel
itself, visible to all subscribers. v0.3.15 fixed this for live
errors via notifyTgUser but missed the queue-expiration path.

Now: if SrcChatID is a crosspost source, DM the tg_owner_id
(GetCrosspostOwner); otherwise keep the current behavior (post in
the source chat for bridge flows).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:37:27 +04:00
artemws
f7137ff160
Веб хуки. Разделение для TG и Max (#42)
Some checks failed
Build / build (push) Has been cancelled
* Refactor webhook server handling and configuration

Refactor webhook server initialization to support separate ports for MAX and TG webhooks. Update configuration structure to include new webhook URLs and ports.

* Update webhook URL configuration for Max

* Update webhook URL configuration for Telegram

* Add MaxWebhook and TgWebhook configuration options

* Update webhook configuration in README

* Include original attachments in forwarded messages

Add original message attachments when forwarding.
2026-05-12 11:31:08 +04:00
Andrey Lugovskoy
c910f36a64 Edit messages with media in MAX instead of sending new ones (TG→MAX)
Some checks failed
Build / build (push) Has been cancelled
When a TG user edited a caption on a post with video/gif/document/audio/
voice, editTgMediaInMax called MAX EditMessage with text only — the SDK
sends a payload without attachments which strips the existing media on
MAX side. Subscribers ended up with the corrected caption but the media
gone.

editTgMediaInMax already handled Photo (re-upload via UploadPhotoFromUrl
or uploadTgPhotoToMax + AddPhoto). Now does the same for the rest of the
types via uploadTgMediaToMax + AddVideo/AddFile/AddAudio. Sticker and
VideoNote stay skipped — TG doesn't allow caption edits on them anyway.

Reported by user via the bridge: video disappeared from MAX after
fixing a typo on TG side.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:52:11 +04:00
Andrey Lugovskoy
56212e48ec Forward MAX→TG: relay Link.Message content for repost messages
Some checks failed
Build / build (push) Has been cancelled
When a MAX user does "репост" (forward) of a message, MAX puts the
original content in Message.Link with Type=forward and leaves
body.Text empty (or only carries the user's own optional comment).
forwardMaxToTg only read body.Text, so reposts arrived in TG as a
naked "Name:" with no content.

Now: if Link.Type=forward, take Link.Message.Text and Link.Message.Markups
as the relay content; if body.Text also has the user's comment,
concatenate "comment\n\nforwarded text" (markups dropped — offsets
no longer match the combined text).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 12:53:07 +04:00
Andrey Lugovskoy
47e23be3e0 Add DISABLE_PREFIX env + default OFF for chats not in pairs
Some checks failed
Build / build (push) Has been cancelled
Two related fixes around the [TG]/[MAX] prefix:

1. HasPrefix on a chat that isn't in `pairs` (typically a thread-bridged
   MAX chat) used to return true — making /bridge prefix off useless
   for thread-bridges (the command can only update pairs rows). Now
   returns false on no-row, matching the post-v0.3.x default of OFF.

2. New env DISABLE_PREFIX=true|1|yes globally disables [TG]/[MAX]
   prefixes regardless of per-pair state. Implemented as a Bridge
   wrapper b.hasPrefix that short-circuits to false when the flag is
   set; all 10 call sites switched to the wrapper. The DB column
   `pairs.prefix` keeps its meaning per-pair when DISABLE_PREFIX is off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 16:06:24 +04:00
Andrey Lugovskoy
77fa94914f Fix: video-without-caption TG→MAX rejected as send-message.empty
Some checks failed
Build / build (push) Has been cancelled
v0.3.14 started sending format="markdown" for every TG→MAX forward
(including crossposts). For media-only posts (video without caption,
etc.) text ends up empty and MAX API rejects the payload with
  400 proto.payload errors.send-message.empty
because format is set but no text is present.

sendMaxDirectFormatted now drops format when text is "". This also
unsticks queued items that were retrying forever with the same 400.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 17:40:53 +04:00
Andrey Lugovskoy
8b80bb8ff1 Route crosspost error notifications to owner DM, not to the channel
For crosspost TG→MAX (a post in a TG channel), any delivery error
message used to be posted back into the same channel (visible to all
subscribers). The only person who can act on it is the crosspost owner.

New helper notifyTgUser(ctx, srcMsg, maxChatID, text, isCrosspost):
- crosspost: resolves tg_owner_id via GetCrosspostOwner and DMs that
  user; drops with a warn if the crosspost has no owner (legacy).
- bridge: keeps the existing behavior (post into the source chat,
  preserving forum thread).

All upload/size/circuit-breaker notifications in forwardTgToMax and
flushMediaGroup now go through notifyTgUser. Bridge flows are
unchanged; crosspost flows stop spamming channels.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:29:52 +04:00
Andrey Lugovskoy
63b95e4c8c Preserve links in TG→MAX crossposts; deliver text when video fails
Two issues reported by a user via the bridge:

1. Links vanish on TG channel → MAX crosspost. formatTgCrosspostCaption
   returned raw text and forwardTgToMax dropped markdown format for the
   crosspost branch, so text_link entities became plain text. Now
   formatTgCrosspostCaption runs tgEntitiesToMarkdown, and
   forwardTgToMax always uses markdown. Media group flusher also
   skips the second conversion for crosspost items (caption is already
   markdown) and keeps markdown format.

2. When a channel video is too big for Bot API getFile (e.g. HD > 2 GB
   on local server), the whole post was dropped — even the caption
   text was lost. Video upload failure no longer returns from
   forwardTgToMax; the fallback branch appends a "[Видео]" marker so
   subscribers in MAX at least get the text and know a video was
   attached but not relayed. The fallback condition now checks
   presence of any media instead of only msg.Text == "" (it missed
   video-with-caption posts) and also covers Animation and Photo.

Tradeoff: crosspost replacements now operate on markdown text. URL-
and phrase-level replacements keep working; regex rules touching
markdown characters (_, *, [, ]) may need adjustment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:26:24 +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
9b7e3cd165 Simplify upload error hints — user-facing, no config details
Some checks are pending
Build / build (push) Waiting to run
Previous hints mentioned bot config (e.g. "20 MB limit without local
server") which is useless to a regular group member who can't
administer the bot. Drop technical detail:

- "файл слишком большой" (no parenthetical)
- "файл не найден"
- "попробуйте ещё раз" (for transient MAX CDN)
- unknown error → no suffix, just the generic "Не удалось отправить X в MAX."

New helper uploadErrMsg(base, err) builds the final string, appending
": hint." only when uploadErrHint returns a non-empty known hint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 21:22:45 +04:00
Andrey Lugovskoy
825bfee473 Surface TG→MAX upload errors in chat (not only in log)
Requested by user Victor via bridged group.

- New helper uploadErrHint maps common upload failures to a short
  Russian hint ("файл слишком большой", "Telegram не нашёл файл",
  "MAX CDN не успел обработать файл", etc) and falls back to a truncated
  raw error for the unknown case.
- Every TG→MAX upload failure path now tells the user what happened
  (photo, gif, sticker, video, video note, document, voice, audio,
  edit-with-media, media group).
- Edit path previously failed silently and media group dropped the
  error on the floor — both now notify.
- Media group notifies once with the first failure when no photos
  survived upload (avoids N messages per album).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 19:01:18 +04:00
Andrey Lugovskoy
02e2ea4f88 Document /thread_bridge and Part 1 reply routing in README
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:42:34 +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
38e5777798 Default prefix off, clarify admin requirement for TG forum groups
Some checks failed
Build / build (push) Has been cancelled
- Default prefix to 0 for new pairs (user can enable via /bridge prefix on)
- Anonymous admins (SenderChat == Chat) recognized as admins
- Log getChatMember errors; show "make bot admin" hint on CHAT_ADMIN_REQUIRED
- Clarify /bridge key message: send in the other group, not in bot DM
- Add /start hint that TG forum supergroups require bot admin
- Add /bridge command logging with chat/user IDs for support

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 17:54:28 +04:00
Andrey Lugovskoy
adb5727c2a Treat anonymous admins as admins for /bridge, /thread etc.
When a group owner/admin enables "Remain anonymous", their messages
are sent from @GroupAnonymousBot with msg.SenderChat pointing to the
group itself. GetChatMember was then checking the bot's status (not
admin) and rejecting valid owners with "only for admins".

New helper isTgAnonymousAdmin(msg) returns true when SenderChat.ID
equals Chat.ID; in that case we skip the GetChatMember check and
treat the user as admin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 17:31:37 +04:00
Andrey Lugovskoy
111e4a8c0d Apply replacements in crosspost forwardTgToMax
forwardTgToMax built the final text from msg.Text/msg.Caption directly,
discarding the caption parameter that already had replacements applied
in handleTgChannelPost. For single posts (not media groups) in crosspost,
replacements were effectively ignored.

Added isCrosspost flag: when true, use the caption as-is (post-replacements,
no attribution, no markdown format since replacement may have invalidated
entity offsets).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 17:06:57 +04:00
Andrey Lugovskoy
474555fd31 Fix release workflow: pass changelog via file to avoid shell expansion
Some checks failed
Build / build (push) Has been cancelled
Backticks in commit messages caused 'no matches found' errors when
changelog was interpolated into shell string. Use --notes-file instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 21:40:15 +04:00
artemws
bfce1c66d2
Очередь отправки (#34)
* Update max.go

* Update telegram.go

* Update format.go

* Update telegram.go

* Implement message queuing for existing pending messages

Added logic to queue messages if there are pending messages for the chat to maintain delivery order.

* Refactor message sending logic for Telegram

* Implement hasPendingForChat function

Add hasPendingForChat function to check for pending messages in the queue.

* Add HasPendingQueue method to repository interface

* Add HasPendingQueue method to sqliteRepo

* Add HasPendingQueue method to check for pending jobs
2026-04-16 21:14:42 +04:00
BEARlogin
dca74ad2cd
Merge pull request #35 from BEARlogin/add-claude-github-actions-1776351404305
Add Claude Code GitHub Workflow
2026-04-16 18:57:05 +04:00
BEARlogin
56b356d61e "Claude Code Review workflow" 2026-04-16 18:56:48 +04:00
BEARlogin
d38fc35051 "Claude PR Assistant workflow" 2026-04-16 18:56:46 +04:00
Andrey Lugovskoy
fbb843e951 Cache-bust favicon in README
Some checks failed
Build / build (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:53:30 +04:00
Andrey Lugovskoy
0d615febba Revert README to plain links with logo and support link
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:52:11 +04:00
Andrey Lugovskoy
d626d6bd49 Redesign README with centered layout, badges and logo
Some checks are pending
Build / build (push) Waiting to run
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:42:54 +04:00
Andrey Lugovskoy
271c40e5ff Add support/donate link to README
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:23:13 +04:00
Andrey Lugovskoy
f4baf9b211 Fix overlapping entities (bold+italic+strike) duplicating text
Some checks failed
Build / build (push) Has been cancelled
tgEntitiesToMarkdown used a linear walk that output each entity's text
independently — when multiple entities covered the same range (e.g.
bold+italic), the text was duplicated for each entity.

Rewrote to tag-insertion approach (matching maxMarkupsToHTML): collect
open/close markers at UTF-16 positions, sort with proper nesting order,
then walk text inserting markers without duplication.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 22:31:12 +03:00
Andrey Lugovskoy
3bd42a5a5e Fix formatting in both TG→MAX and MAX→TG directions
Some checks are pending
Build / build (push) Waiting to run
TG→MAX: tgEntitiesToMarkdown was called on caption with "Name: " prefix,
but entity offsets are relative to raw text — formatting markers landed
in wrong positions. Now entities are converted on raw text first, then
attribution is applied.

MAX→TG: condition `caption == text` was always false for bridge chats
(caption has "Name: " prefix), so markups were never converted to HTML.
Now markups are always applied, with proper HTML-escaped attribution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-08 18:40:37 +03:00
Andrey Lugovskoy
ec166754c3 Edit messages with media in MAX instead of sending new ones (TG→MAX)
Some checks failed
Build / build (push) Has been cancelled
Previously, editing a TG message with photo/media would send a new
message in MAX. Now it properly calls EditMessage with the updated
attachment when a message mapping exists, falling back to new message
only when no mapping is found.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 14:29:16 +03:00
Andrey Lugovskoy
2fc7e903cf Allow other bots' messages through bridge, filter only self
Previously all bot messages were skipped. Now only our own bot
is filtered (by TG username / MAX user ID), so messages from
other bots in the group are forwarded normally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:45:29 +03: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
01e0c83bdc Disable edit sync for crosspost channels
Same rationale as delete sync — edits between crossposted channels
cause confusion. Now edits only sync for regular bridge pairs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 22:47:26 +03:00
Andrey Lugovskoy
75a41892c7 Disable delete sync for crosspost channels
Delete propagation between crossposted channels causes more harm
than good — posts get accidentally removed. Now deletes only sync
for regular bridge pairs, not crossposts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 21:36:31 +03:00
Andrey Lugovskoy
cc277c07f5 Respect crosspost direction for delete and edit from MAX
When direction is "tg>max" (TG-only to MAX), delete/edit events
from MAX should not propagate back to TG. Previously all deletes
and edits from MAX were forwarded regardless of direction setting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 21:33:23 +03:00
Andrey Lugovskoy
9e0a72ea35 Fix caption formatting lost on photo/album crosspost TG→MAX
Photo sends via SDK (Messages.SendWithResult) didn't convert
TG entities to markdown or set format field. Text-only sends
used sendMaxDirectFormatted which did both — hence the discrepancy.

Now photo and media group captions convert CaptionEntities to
markdown and call SetFormat("markdown") when formatting is present.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 21:31:50 +03:00
Andrey Lugovskoy
0a5ee32b33 Update README with forum topics and /thread command
Some checks are pending
Build / build (push) Waiting to run
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:21:57 +03:00