feat: fetch and display channel title from Telegram public page

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.
This commit is contained in:
Sepehr
2026-04-17 10:23:25 -04:00
parent 74e12801da
commit d2922e6afb
2 changed files with 47 additions and 8 deletions
+17
View File
@@ -210,3 +210,20 @@ func (f *Feed) SetChannels(channels []string) {
f.channels = channels
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.
func (f *Feed) SetChannelDisplayName(channelNum int, displayName string) {
f.mu.Lock()
defer f.mu.Unlock()
idx := channelNum - 1
if idx < 0 || idx >= len(f.channels) {
return
}
if displayName == "" || f.channels[idx] == displayName {
return
}
f.channels[idx] = displayName
f.rebuildMetaBlocks()
}
+30 -8
View File
@@ -123,7 +123,7 @@ func (pr *PublicReader) fetchAll(ctx context.Context) {
continue
}
msgs, err := pr.fetchChannel(ctx, username)
msgs, title, err := pr.fetchChannel(ctx, username)
if err != nil {
log.Printf("[public] fetch %s: %v", username, err)
failed++
@@ -144,34 +144,56 @@ 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)
}
fetched++
log.Printf("[public] updated %s: %d messages", username, len(msgs))
log.Printf("[public] updated %s (%s): %d messages", username, title, len(msgs))
}
log.Printf("[public] fetch cycle done in %s: %d fetched, %d failed, %d total", time.Since(start).Round(time.Millisecond), fetched, failed, len(pr.channels))
}
func (pr *PublicReader) fetchChannel(ctx context.Context, username string) ([]protocol.Message, error) {
func (pr *PublicReader) fetchChannel(ctx context.Context, username string) ([]protocol.Message, string, error) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, pr.baseURL+"/"+url.PathEscape(username), nil)
if err != nil {
return nil, err
return nil, "", err
}
req.Header.Set("User-Agent", "Mozilla/5.0 (compatible; thefeed/1.0; +https://github.com/sartoopjj/thefeed)")
resp, err := pr.client.Do(req)
if err != nil {
return nil, err
return nil, "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected HTTP status: %s", resp.Status)
return nil, "", fmt.Errorf("unexpected HTTP status: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
return nil, "", err
}
return parsePublicMessages(body)
msgs, err := parsePublicMessages(body)
if err != nil {
return nil, "", err
}
title := extractChannelTitle(body)
return msgs, title, nil
}
// extractChannelTitle parses the channel display name from the Telegram public page.
// It looks for <div class="tgme_channel_info_header_title"><span>Title</span></div>.
func extractChannelTitle(body []byte) string {
doc, err := html.Parse(strings.NewReader(string(body)))
if err != nil {
return ""
}
titleNode := findFirstByClass(doc, "tgme_channel_info_header_title")
if titleNode == nil {
return ""
}
return strings.TrimSpace(extractInnerText(titleNode))
}
type publicMessage struct {