diff --git a/internal/protocol/protocol.go b/internal/protocol/protocol.go index 093f7dc..535d6f9 100644 --- a/internal/protocol/protocol.go +++ b/internal/protocol/protocol.go @@ -81,6 +81,7 @@ type Metadata struct { // ChannelInfo describes a single feed channel. type ChannelInfo struct { Name string + DisplayName string // human-readable title; empty means fall back to Name Blocks uint16 LastMsgID uint32 ContentHash uint32 // CRC32 of serialized message data; changes on edits @@ -104,12 +105,16 @@ func ContentHashOf(msgs []Message) uint32 { // SerializeMetadata encodes metadata into bytes for channel 0 blocks. // Format: marker(3) + timestamp(4) + nextFetch(4) + flags(1) + channelCount(2) + per-channel data -// Per-channel: nameLen(1) + name + blocks(2) + lastMsgID(4) + contentHash(4) + chatType(1) + flags(1) +// Per-channel: nameLen(1) + name + blocks(2) + lastMsgID(4) + contentHash(4) + chatType(1) + flags(1) + displayNameLen(1) + displayName func SerializeMetadata(m *Metadata) []byte { // 3 marker + 4 timestamp + 4 nextFetch + 1 flags + 2 channel count + per-channel data size := MarkerSize + 4 + 4 + 1 + 2 for _, ch := range m.Channels { - size += 1 + len(ch.Name) + 2 + 4 + 4 + 1 + 1 // +4 for contentHash + dn := ch.DisplayName + if len(dn) > 255 { + dn = dn[:255] + } + size += 1 + len(ch.Name) + 2 + 4 + 4 + 1 + 1 + 1 + len(dn) } buf := make([]byte, size) off := 0 @@ -156,6 +161,14 @@ func SerializeMetadata(m *Metadata) []byte { } buf[off] = chFlags off++ + dnBytes := []byte(ch.DisplayName) + if len(dnBytes) > 255 { + dnBytes = dnBytes[:255] + } + buf[off] = byte(len(dnBytes)) + off++ + copy(buf[off:], dnBytes) + off += len(dnBytes) } return buf @@ -213,8 +226,19 @@ func ParseMetadata(data []byte) (*Metadata, error) { chFlags := data[off] off++ + var displayName string + if off < len(data) { + dnLen := int(data[off]) + off++ + if off+dnLen <= len(data) { + displayName = string(data[off : off+dnLen]) + off += dnLen + } + } + m.Channels = append(m.Channels, ChannelInfo{ Name: name, + DisplayName: displayName, Blocks: blocks, LastMsgID: lastID, ContentHash: contentHash, diff --git a/internal/server/feed.go b/internal/server/feed.go index 614f634..74a8449 100644 --- a/internal/server/feed.go +++ b/internal/server/feed.go @@ -14,6 +14,7 @@ type Feed struct { mu sync.RWMutex marker [protocol.MarkerSize]byte channels []string + displayNames map[int]string blocks map[int][][]byte lastIDs map[int]uint32 contentHashes map[int]uint32 @@ -31,6 +32,7 @@ type Feed struct { func NewFeed(channels []string) *Feed { f := &Feed{ channels: channels, + displayNames: make(map[int]string), blocks: make(map[int][][]byte), lastIDs: make(map[int]uint32), contentHashes: make(map[int]uint32), @@ -135,6 +137,7 @@ func (f *Feed) rebuildMetaBlocks() { } meta.Channels = append(meta.Channels, protocol.ChannelInfo{ Name: name, + DisplayName: f.displayNames[chNum], Blocks: blockCount, LastMsgID: f.lastIDs[chNum], ContentHash: f.contentHashes[chNum], @@ -211,19 +214,20 @@ func (f *Feed) SetChannels(channels []string) { f.rebuildMetaBlocks() } -// SetChannelDisplayName updates the display name for a specific channel number (1-indexed). -// This allows replacing the raw handle (e.g. "networkti") with the channel's -// actual title (e.g. "Sarto") after it has been fetched. +// SetChannelDisplayName stores a human-readable title for a channel (1-indexed). +// It never mutates the handle in f.channels, which remains the stable identifier. func (f *Feed) SetChannelDisplayName(channelNum int, displayName string) { + if displayName == "" { + return + } f.mu.Lock() defer f.mu.Unlock() - idx := channelNum - 1 - if idx < 0 || idx >= len(f.channels) { + if channelNum < 1 || channelNum > len(f.channels) { return } - if displayName == "" || f.channels[idx] == displayName { + if f.displayNames[channelNum] == displayName { return } - f.channels[idx] = displayName + f.displayNames[channelNum] = displayName f.rebuildMetaBlocks() } diff --git a/internal/server/public.go b/internal/server/public.go index 0b79810..413bcc9 100644 --- a/internal/server/public.go +++ b/internal/server/public.go @@ -144,9 +144,7 @@ func (pr *PublicReader) fetchAll(ctx context.Context) { pr.feed.UpdateChannel(chNum, msgs) pr.feed.SetChatInfo(chNum, protocol.ChatTypeChannel, false) - if title != "" { - pr.feed.SetChannelDisplayName(chNum, title) - } + pr.feed.SetChannelDisplayName(chNum, title) fetched++ log.Printf("[public] updated %s (%s): %d messages", username, title, len(msgs)) } @@ -178,12 +176,10 @@ func (pr *PublicReader) fetchChannel(ctx context.Context, username string) ([]pr if err != nil { return nil, "", err } - title := extractChannelTitle(body) - return msgs, title, nil + return msgs, extractChannelTitle(body), nil } // extractChannelTitle parses the channel display name from the Telegram public page. -// It looks for
' + t('loading') + '