- Persist display names in per-channel JSON cache files (Name +
DisplayName fields on cachedChannel). GetAllTitles reads all ch_*.json
files; PutTitle updates a channel file in-place without losing messages.
- Replace blocking FetchTitles calls in fetchMeta and refreshChannel with
an instant disk read (GetAllTitles) applied before the SSE broadcast,
so channels appear with cached titles immediately on every load.
- ensureTitlesFetched runs in a single background goroutine (titlesMu +
titlesLoading guard prevents duplicates). On success it persists titles
and pushes an SSE update. On error or empty response it backs off for
5 minutes so an old server does not cause endless retries.
- Block 0 of TitlesChannel now carries a uint16 total-block-count prefix
(added in rebuildTitlesBlocks). FetchTitles reads the count from block 0
and fetches all remaining blocks in parallel instead of sequentially.
- FetchTitles timeout raised from 10 s to 1 minute.
- Channel list: always show @handle as subtitle below display name for Telegram channels
- Chat header: show @handle as subtitle below channel title
- Mobile (<768px): hide Search/Export buttons; show them in a ⋮ kebab menu (rightmost button)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add dedicated TitlesChannel (0xFFF9) following the VersionChannel pattern
- Server encodes name→title map via EncodeTitlesData/DecodeTitlesData
- Metadata wire format unchanged for backward compatibility with old clients
- All three fetchers (public Telegram, MTProto, X/Nitter) extract and store display names
- Client fetches TitlesChannel with a 10s deadline; falls back to channel handles gracefully on old servers
- Old clients are unaffected — they never query 0xFFF9
- Add DisplayName field to ChannelInfo in the wire protocol
- Add displayNames map to Feed; SetChannelDisplayName no longer mutates
f.channels, keeping handles stable for cache keys and management
- Public fetcher: extract title via extractChannelTitle, pass to feed
- Telegram fetcher: capture ch.Title / user.FirstName from MTProto API
- Frontend: render DisplayName as sidebar label, keep Name as identifier
fetchChannel now returns the channel's display title extracted from the
tgme_channel_info_header_title element. SetChannelDisplayName replaces
the raw @handle in the feed metadata with the human-readable title after
the first successful fetch.
Previously linkify received pre-escaped text, so & in URLs became &
causing the regex to truncate URLs at query-string boundaries. Now
linkify escapes HTML internally so URLs are matched against raw text.
Also adds [label](url) markdown link support.
Add Docker deployment option for the server with multi-stage build.
Changes:
- Dockerfile: multi-stage build (golang:1.26-alpine → alpine:3.21, ~23MB)
- docker-compose.yml: server service on port 5300/udp with iptables redirect
- .env.example: documented environment variables template
- .dockerignore: optimized build context
- README.md & README-FA.md: Docker deployment guide, port 53 safety checks,
and troubleshooting instructions (both EN and FA)
- configs: added more Telegram channels and X accounts
The container listens on port 5300 to avoid conflict with systemd-resolved.
External DNS traffic (port 53) is redirected via iptables PREROUTING.
No changes to existing Go source code.