парсер

This commit is contained in:
Artem Tsyrulnikov
2026-01-06 21:04:42 +03:00
parent b77e4fd0b6
commit 2c0ae2dfc3
45 changed files with 2202 additions and 51 deletions

View File

@@ -0,0 +1,37 @@
package telegram
import (
"context"
"fmt"
"github.com/TelegramExchange/tgex-parser/internal/domain"
"github.com/gotd/td/tg"
)
func (t *Telegram) UpdatePostsViews(ctx context.Context, channel domain.Channel, posts []domain.Post) error {
ids := make([]int, len(posts))
for i, p := range posts {
ids[i] = p.MessageID
}
req := &tg.MessagesGetMessagesViewsRequest{
Peer: &tg.InputPeerChannel{
ChannelID: channel.ChannelID(),
AccessHash: channel.AccessHash,
},
ID: ids,
Increment: false,
}
resp, err := t.API().MessagesGetMessagesViews(ctx, req)
if err != nil {
return fmt.Errorf("get messages views: %w", err)
}
// Telegram гарантирует, что resp.Views соответствует порядку ids
for i, v := range resp.Views {
posts[i].Views = v.Views
}
return nil
}

View File

@@ -0,0 +1,119 @@
package telegram
import (
"context"
"fmt"
"github.com/TelegramExchange/tgex-parser/internal/domain"
"github.com/gotd/td/tg"
)
func (t *Telegram) GetChannelDiff(ctx context.Context, channel domain.Channel, limit int) (domain.ChannelDiff, error) {
req := &tg.UpdatesGetChannelDifferenceRequest{
Channel: &tg.InputChannel{
ChannelID: channel.ChannelID(),
AccessHash: channel.AccessHash,
},
Filter: &tg.ChannelMessagesFilterEmpty{},
Pts: channel.Pts,
Limit: limit,
}
rawDiff, err := t.API().UpdatesGetChannelDifference(ctx, req)
if err != nil {
return domain.ChannelDiff{}, fmt.Errorf("get difference: %w", err)
}
result := domain.ChannelDiff{}
switch d := rawDiff.(type) {
case *tg.UpdatesChannelDifferenceEmpty:
result.NewPts = d.Pts
case *tg.UpdatesChannelDifferenceTooLong:
if dialog, ok := d.Dialog.(*tg.Dialog); ok {
result.NewPts = dialog.Pts
}
result.NewPosts = extractPosts(channel, d.Messages)
result.UpdatedChannel = extractChannelMeta(channel, d.Chats)
case *tg.UpdatesChannelDifference:
result.NewPts = d.Pts
result.NewPosts = extractPosts(channel, d.NewMessages)
result.DeletedPosts = extractDeletedPosts(channel, d.OtherUpdates)
result.UpdatedChannel = extractChannelMeta(channel, d.Chats)
default:
return domain.ChannelDiff{}, fmt.Errorf("unexpected rawDiff type: %T", rawDiff)
}
return result, nil
}
func extractPosts(channel domain.Channel, msgs []tg.MessageClass) []domain.Post {
posts := make([]domain.Post, 0, len(msgs))
for _, raw := range msgs {
m, ok := raw.(*tg.Message)
if !ok {
continue
}
p := domain.NewPost(channel, m.ID, m.Message, m.Views)
posts = append(posts, p)
}
return posts
}
func extractDeletedPosts(channel domain.Channel, updates []tg.UpdateClass) []domain.Post {
var deleted []domain.Post
for _, upd := range updates {
if u, ok := upd.(*tg.UpdateDeleteChannelMessages); ok {
for _, msgID := range u.Messages {
p := domain.NewPost(channel, msgID, "", 0)
deleted = append(deleted, p)
}
}
}
return deleted
}
func extractChannelMeta(currentChannel domain.Channel, chats []tg.ChatClass) *domain.Channel {
for _, chat := range chats {
ch, ok := chat.(*tg.Channel)
if !ok {
continue
}
if ch.ID != currentChannel.ChannelID() {
continue
}
username := ""
if usernameVal, ok := ch.GetUsername(); ok {
username = usernameVal
}
// Keep current access hash if new one is not provided or is zero
accessHash := currentChannel.AccessHash
if accessHashVal, ok := ch.GetAccessHash(); ok && accessHashVal != 0 {
accessHash = accessHashVal
}
updated := domain.Channel{
ID: currentChannel.ID,
TelegramID: domain.ChatIDFromChannelID(ch.ID),
Username: username,
Title: ch.Title,
AccessHash: accessHash,
Pts: currentChannel.Pts,
}
return &updated
}
return nil
}

View File

@@ -0,0 +1,46 @@
package telegram
import (
"context"
"fmt"
"github.com/TelegramExchange/tgex-parser/internal/domain"
"github.com/gotd/td/tg"
"github.com/rs/zerolog/log"
)
func (t *Telegram) GetChannelHistory(ctx context.Context, channel domain.Channel, limit int) ([]domain.Post, error) {
log.Info().Msg(channel.String())
req := &tg.MessagesGetHistoryRequest{
Peer: &tg.InputPeerChannel{
ChannelID: channel.ChannelID(),
AccessHash: channel.AccessHash,
},
Limit: limit,
}
resp, err := t.API().MessagesGetHistory(ctx, req)
if err != nil {
return nil, fmt.Errorf("resp: %w", err)
}
messages, ok := resp.(*tg.MessagesChannelMessages)
if !ok {
return nil, nil
}
placements := make([]domain.Post, 0, len(messages.Messages))
for _, raw := range messages.Messages {
m, ok := raw.(*tg.Message)
if !ok {
continue
}
p := domain.NewPost(channel, m.ID, m.Message, m.Views)
placements = append(placements, p)
}
return placements, nil
}

View File

@@ -0,0 +1,59 @@
package telegram
import (
"context"
"fmt"
"github.com/TelegramExchange/tgex-parser/internal/domain"
"github.com/google/uuid"
"github.com/gotd/td/tg"
)
func (t *Telegram) ParseChannelMeta(ctx context.Context, username string) (domain.Channel, error) {
resolved, err := t.API().ContactsResolveUsername(ctx, &tg.ContactsResolveUsernameRequest{
Username: username,
})
if err != nil {
return domain.Channel{}, fmt.Errorf("resolve username: %w", err)
}
if len(resolved.Chats) == 0 {
return domain.Channel{}, fmt.Errorf("no chats in resolve result")
}
ch, ok := resolved.Chats[0].(*tg.Channel)
if !ok {
return domain.Channel{}, fmt.Errorf("not a channel")
}
req := []tg.InputDialogPeerClass{
&tg.InputDialogPeer{
Peer: &tg.InputPeerChannel{
ChannelID: ch.ID,
AccessHash: ch.AccessHash,
},
},
}
dialogs, err := t.API().MessagesGetPeerDialogs(ctx, req)
if err != nil {
return domain.Channel{}, fmt.Errorf("peer dialogs: %w", err)
}
pts := 0
if len(dialogs.Dialogs) > 0 {
d, ok := dialogs.Dialogs[0].(*tg.Dialog)
if ok {
pts = d.Pts
}
}
return domain.Channel{
ID: uuid.New(),
TelegramID: domain.ChatIDFromChannelID(ch.ID),
Username: username,
Title: ch.Title,
AccessHash: ch.AccessHash,
Pts: pts,
}, nil
}

View File

@@ -0,0 +1,20 @@
package telegram
import (
"github.com/TelegramExchange/tgex-parser/pkg/telegram"
"github.com/gotd/td/tg"
)
type Telegram struct {
client *telegram.Client
}
func New(client *telegram.Client) *Telegram {
return &Telegram{
client: client,
}
}
func (t *Telegram) API() *tg.Client {
return t.client.API()
}