-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhandler.go
228 lines (194 loc) · 6.24 KB
/
handler.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package dolista
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"math"
"net/http"
"net/url"
"os"
"sort"
"strconv"
"strings"
"time"
"cloud.google.com/go/firestore"
firebase "firebase.google.com/go/v4"
"google.golang.org/api/iterator"
"google.golang.org/api/option"
)
// Update is a Telegram object that the handler receives every time an user interacts with the bot.
type Update struct {
UpdateId int `json:"update_id"`
Message Message `json:"message"`
}
// Message is a Telegram object that can be found in an update.
type Message struct {
Text string `json:"text"`
Chat Chat `json:"chat"`
}
// A Telegram Chat indicates the conversation to which the message belongs.
type Chat struct {
Id int `json:"id"`
}
// SummaryItem represents one of the items that can be held by a summary
type SummaryItem struct {
ChatID int `json:"chat_id"`
Message string `json:"message"`
}
type ByCreatedDate []*firestore.DocumentSnapshot
func (d ByCreatedDate) Len() int { return len(d) }
func (d ByCreatedDate) Less(i, j int) bool { return d[i].CreateTime.Before(d[j].CreateTime) }
func (d ByCreatedDate) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
// Config represents the application config params
type Config struct {
TelegramToken string `json:"telegramToken"`
FirebaseConfig struct {
Type string `json:"type"`
ProjectID string `json:"project_id"`
PrivateKeyID string `json:"private_key_id"`
PrivateKey string `json:"private_key"`
ClientEmail string `json:"client_email"`
ClientID string `json:"client_id"`
AuthURI string `json:"auth_uri"`
TokenURI string `json:"token_uri"`
AuthProviderX509CertURL string `json:"auth_provider_x509_cert_url"`
ClientX509CertURL string `json:"client_x509_cert_url"`
} `json:"firebaseConfig"`
}
func HandleMessage(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqBody Update
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
log.Printf("failed to decode request body: %v\n", err)
return
}
appConfig := os.Getenv("APP_CONFIG")
bConfig, err := base64.RawStdEncoding.DecodeString(appConfig)
if err != nil {
log.Printf("failed decode app config: %v\n", err)
return
}
var config Config
err = json.Unmarshal(bConfig, &config)
if err != nil {
log.Printf("failed to parse app config: %v\n", err)
return
}
firebaseConfig, err := json.Marshal(config.FirebaseConfig)
if err != nil {
log.Printf("failed to parse firebase config: %v\n", err)
return
}
opt := option.WithCredentialsJSON(firebaseConfig)
// conf := &firebase.Config{
// ProjectID: "dolista-safado",
// ServiceAccountID: "github-actions@dolista-safado.iam.gserviceaccount.com",
// }
app, err := firebase.NewApp(ctx, nil, opt)
if err != nil {
log.Printf("failed to create firebase app: %v\n", err)
return
}
client, err := app.Firestore(ctx)
if err != nil {
log.Printf("failed to create firestore client: %v\n", err)
return
}
defer client.Close()
if strings.HasPrefix(reqBody.Message.Text, "/hello") {
handleHello(reqBody.Message.Chat.Id, reqBody.Message.Text, config.TelegramToken)
return
}
if strings.HasPrefix(reqBody.Message.Text, "/safada") {
handleSafada(reqBody.Message.Chat.Id, reqBody.Message.Text, config.TelegramToken)
return
}
if strings.HasPrefix(reqBody.Message.Text, "/resumo") {
handleResumo(ctx, reqBody.Message.Chat.Id, config.TelegramToken, client)
return
}
// Make sure `/r` is checked after `/resumo` or the logic will break.
if strings.HasPrefix(reqBody.Message.Text, "/r") {
handleAddResumo(ctx, reqBody.Message.Chat.Id, reqBody.Message.Text, config.TelegramToken, client)
return
}
}
func handleHello(chatID int, message, token string) {
sendMessage(chatID, "hello!", token)
}
func handleSafada(chatID int, message, token string) {
sendMessage(chatID, "é você!", token)
}
func handleResumo(ctx context.Context, chatID int, token string, client *firestore.Client) {
iter := client.Collection("summary").Where("ChatID", "==", chatID).Documents(ctx)
var docs []*firestore.DocumentSnapshot
for {
doc, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
log.Printf("failed to iterate: %v\n", err)
return
}
// Deletes entries 24h or older without adding them to the final string
if doc.ReadTime.Sub(doc.CreateTime).Hours() >= 24 {
doc.Ref.Delete(ctx)
continue
}
docs = append(docs, doc)
}
sort.Sort(ByCreatedDate(docs))
var msgs []string
for i := 0; i < len(docs); i++ {
doc := docs[i]
var item SummaryItem
err := doc.DataTo(&item)
if err != nil {
log.Printf("failed parse item: %v\n", err)
return
}
timeAgo := doc.ReadTime.Sub(doc.CreateTime)
msgs = append(msgs, fmt.Sprintf("[Há %v] %v", makeTimeAgoString(timeAgo), item.Message))
}
resumo := strings.Join(msgs, "\n")
sendMessage(chatID, fmt.Sprintf("Resumo: \n\n%v", resumo), token)
}
func handleAddResumo(ctx context.Context, chatID int, message, token string, client *firestore.Client) {
split := strings.Split(message, " ")
if len(split) == 1 {
sendMessage(chatID, "Safado! Cadê a mensagem pra adicionar no resumo?", token)
return
}
newEntry := strings.Join(split[1:], " ")
_, _, err := client.Collection("summary").Add(ctx, SummaryItem{ChatID: chatID, Message: newEntry})
if err != nil {
log.Printf("Falied to add message: %v\n", err)
sendMessage(chatID, "Ops! O código do Caio não funcionou :)", token)
return
}
successMsg := fmt.Sprintf("Adicionado ao resumo: %v", newEntry)
sendMessage(chatID, successMsg, token)
}
func makeTimeAgoString(timeAgo time.Duration) string {
hoursAgo, minutesAgo := math.Floor(timeAgo.Hours()), math.Floor(math.Mod(timeAgo.Minutes(), 60))
if hoursAgo == 0 {
return fmt.Sprintf("%.0fmin", minutesAgo)
}
return fmt.Sprintf("%.0fh%.0fmin", hoursAgo, minutesAgo)
}
func sendMessage(chatID int, message, token string) {
var telegramApi string = "https://api.telegram.org/bot" + token + "/sendMessage"
response, err := http.PostForm(
telegramApi,
url.Values{
"chat_id": {strconv.Itoa(chatID)},
"text": {message},
})
if err != nil {
return // TODO: return friendly bot message for error cases
}
defer response.Body.Close()
}