all repos

rss-tools @ 97e6bf5d8882b12205658af7ee1de0dd8d42298a

get rss feed from sources that(i need and) dont provide one

rss-tools/sources/telegram/telegram.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
init, 1 month ago
1
package telegram
2
3
import (
4
	"bytes"
5
	"context"
6
	"encoding/binary"
7
	"encoding/gob"
8
	"fmt"
9
	"log/slog"
10
	"net/http"
11
	"time"
12
13
	"olexsmir.xyz/rss-tools/app"
14
)
15
16
type telegram struct {
17
	db        *app.Bucket
18
	messages  *app.Bucket
19
	client    *http.Client
20
	tg        *TelegramSDK
21
	allowedID int64
22
}
23
24
func Register(a *app.App) error {
25
	db, err := a.Bucket("telegram")
26
	if err != nil {
27
		return err
28
	}
29
30
	messages, err := a.Bucket("telegram:messages")
31
	if err != nil {
32
		return err
33
	}
34
35
	t := &telegram{
36
		db:        db,
37
		messages:  messages,
38
		client:    a.Client,
39
		tg:        NewSDK(a.Client, a.Config.TGToken),
40
		allowedID: a.Config.TGUserID,
41
	}
42
43
	a.AddWorker(t.worker)
44
	a.Route("GET /telegram", t.handler)
45
	return nil
46
}
47
48
func (t *telegram) handler(w http.ResponseWriter, r *http.Request) {
49
	// todo: cache feed contruction
50
	// todo: dont include messages older than N days
51
52
	messages, err := t.loadMessages()
53
	if err != nil {
54
		http.Error(w, "failed to load messages", http.StatusInternalServerError)
55
		return
56
	}
57
58
	feed := app.NewFeed("Telegram feed", "telegram-feed")
59
	for _, m := range messages {
60
		title := m.Text
61
		if len(title) > 64 {
62
			title = title[:64] + "..."
63
		}
64
		feed.AddEntry(app.FeedEntry{
65
			Title:   title,
66
			ID:      fmt.Sprintf("telegram-%d", m.MessageID),
67
			Content: m.Text,
68
			Updated: time.Unix(m.Date, 0),
69
		})
70
	}
71
72
	w.WriteHeader(http.StatusOK)
73
	feed.Render(w)
74
}
75
76
func (t *telegram) worker(ctx context.Context) error {
77
	offset, err := t.loadOffset()
78
	if err != nil {
79
		return err
80
	}
81
82
	for {
83
		updates, err := t.tg.GetUpdates(ctx, offset)
84
		if err != nil {
85
			slog.ErrorContext(ctx, "getUpdates failed", "err", err)
86
			select {
87
			case <-ctx.Done():
88
				return nil
89
			case <-time.After(5 * time.Second):
90
				continue
91
			}
92
		}
93
94
		for _, u := range updates {
95
			if u.Message != nil && u.Message.From != nil {
96
				slog.InfoContext(ctx, "message from", "user_id", u.Message.From.ID, "username", u.Message.From.Username, "msg", u.Message.Text)
97
			}
98
99
			if u.Message == nil && u.Message.From == nil || u.Message.From.ID != t.allowedID {
100
				offset = u.UpdateID + 1
101
				continue
102
			}
103
104
			if err := t.saveMessage(u.Message); err != nil {
105
				slog.ErrorContext(ctx, "failed to save message", "err", err)
106
			}
107
108
			if err := t.tg.SetReaction(ctx, u.Message.From.ID, u.Message.MessageID, "👍"); err != nil {
109
				slog.ErrorContext(ctx, "failed to set reaction", "err", err)
110
			}
111
112
			offset = u.UpdateID + 1
113
		}
114
115
		if err := t.saveOffset(offset); err != nil {
116
			slog.ErrorContext(ctx, "failed to save offset", "err", err)
117
		}
118
119
		select {
120
		case <-ctx.Done():
121
			return nil
122
		case <-time.After(time.Second):
123
		}
124
	}
125
}
126
127
func (t *telegram) saveOffset(offset int64) error {
128
	return t.db.Set([]byte("offset"), binary.BigEndian.AppendUint64(nil, uint64(offset)))
129
}
130
131
func (t *telegram) loadOffset() (int64, error) {
132
	val, err := t.db.Get([]byte("offset"))
133
	if err != nil || val == nil {
134
		return 0, err
135
	}
136
	return int64(binary.BigEndian.Uint64(val)), nil
137
}
138
139
func (t *telegram) saveMessage(m *Message) error {
140
	var buf bytes.Buffer
141
	if err := gob.NewEncoder(&buf).Encode(m); err != nil {
142
		return err
143
	}
144
	key := binary.BigEndian.AppendUint64(nil, uint64(m.MessageID))
145
	return t.messages.Set(key, buf.Bytes())
146
}
147
148
func (t *telegram) loadMessages() ([]*Message, error) {
149
	var messages []*Message
150
	err := t.messages.ForEach(func(k, v []byte) error {
151
		var m Message
152
		if err := gob.NewDecoder(bytes.NewReader(v)).Decode(&m); err != nil {
153
			return err
154
		}
155
		messages = append(messages, &m)
156
		return nil
157
	})
158
	return messages, err
159
}