notify.go (view raw)
1package main
2
3import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "io"
9 "log"
10 "net/http"
11 "os"
12 "time"
13)
14
15const msgFormat = "*Giorno %02d/%02d*\n\n_%s_\n%s"
16const msgFormatYear = "*Giorno %02d/%02d/%04d*\nš %d anni\n\n_%s_\n%s"
17const baseUrl = "https://api.telegram.org/bot"
18
19type Response struct {
20 Ok bool `json:"ok"`
21 Result Result `json:"result"`
22}
23
24type Result struct {
25 MessageID int `json:"message_id"`
26}
27
28var (
29 NotificationWindow int
30 SoftNotificationWindow int
31 SleepDuration time.Duration
32 telegramToken string
33 chatID string
34 threadID string
35
36 sendMessageUrl string
37 pinMessageUrl string
38)
39
40func sendPostRequest(url string, payload map[string]interface{}) (*http.Response, error) {
41 jsonData, err := json.Marshal(payload)
42 if err != nil {
43 return nil, fmt.Errorf("failed to marshal payload: %v", err)
44 }
45
46 req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
47 if err != nil {
48 return nil, fmt.Errorf("failed to create new request: %v", err)
49 }
50 req.Header.Set("Content-Type", "application/json")
51
52 client := &http.Client{}
53 resp, err := client.Do(req)
54 if err != nil {
55 return nil, fmt.Errorf("failed to send request: %v", err)
56 }
57
58 return resp, nil
59}
60
61func sendTelegramMessage(message string) (messageID int, err error) {
62 // Create the payload
63 payload := map[string]interface{}{
64 "chat_id": chatID,
65 "text": message,
66 "parse_mode": "markdown",
67 "message_thread_id": threadID,
68 "disable_notification": true,
69 }
70
71 // Send the POST request
72 resp, err := sendPostRequest(sendMessageUrl, payload)
73 if err != nil {
74 log.Printf("Failed to send notification: %v", err)
75 return
76 }
77 defer resp.Body.Close()
78
79 bodyBytes, err := io.ReadAll(resp.Body)
80 if err != nil {
81 log.Printf("Failed to read response body: %v", err)
82 return
83 }
84
85 log.Printf("Notification sent: %s, Response: %s", message, string(bodyBytes))
86
87 // Decode the JSON response
88 var r Response
89 err = json.Unmarshal(bodyBytes, &r)
90 if err != nil {
91 log.Printf("Failed to decode response: %v", err)
92 return
93 }
94
95 if !r.Ok {
96 log.Printf("Telegram API returned an error: %v", r)
97 err = fmt.Errorf("telegram API error: %v", r)
98 return
99 }
100
101 return r.Result.MessageID, nil
102}
103
104func pinTelegramMessage(messageID int) (err error) {
105 // Prepare the request to pin the message
106 payload := map[string]interface{}{
107 "chat_id": chatID,
108 "message_id": messageID,
109 "disable_notification": false,
110 }
111
112 // Send the POST request to pin the message
113 resp, err := sendPostRequest(pinMessageUrl, payload)
114 if err != nil {
115 log.Printf("Failed to pin message: %v", err)
116 return err
117 }
118 defer resp.Body.Close()
119
120 bodyBytes, err := io.ReadAll(resp.Body)
121 if err != nil {
122 log.Printf("Failed to read response body: %v", err)
123 return err
124 }
125
126 log.Printf("Message pinned: %d, Response: %s", messageID, string(bodyBytes))
127 return
128}
129
130func notifyTelegram(occurrence Occurrence, soft bool) error {
131 log.Println("Sending notification for occurrence", occurrence.ID)
132 var message string
133 if occurrence.Year != nil {
134 years := time.Now().Year() - int(*occurrence.Year)
135 message = fmt.Sprintf(msgFormatYear, occurrence.Day, occurrence.Month, *occurrence.Year, years, occurrence.Name, occurrence.Description)
136 } else {
137 message = fmt.Sprintf(msgFormat, occurrence.Day, occurrence.Month, occurrence.Name, occurrence.Description)
138 }
139
140 msgId, err := sendTelegramMessage(message)
141 if err != nil {
142 return err
143 }
144
145 if soft {
146 return nil
147 }
148
149 return pinTelegramMessage(msgId)
150}
151
152func resetNotifications(notified_column string) (err error) {
153 if err = db.Model(&Occurrence{}).Where(notified_column+" = ?", true).Update(notified_column, false).Error; err != nil {
154 log.Printf("Failed to reset notifications: %v", err)
155 } else {
156 log.Println("Notifications have been reset for the new year.")
157 }
158 return
159}
160
161func initNotifications() error {
162 telegramToken = os.Getenv("TELEGRAM_BOT_TOKEN")
163 chatID = os.Getenv("TELEGRAM_CHAT_ID")
164 threadID = os.Getenv("TELEGRAM_THREAD_ID")
165
166 if telegramToken == "" || chatID == "" {
167 log.Println("Warning: you should set your Telegram Bot token and chat id in .env, otherwise you won't get notifications!")
168 return errors.New("empty telegramToken or chatId")
169 }
170
171 sendMessageUrl = fmt.Sprintf("%s%s/sendMessage", baseUrl, telegramToken)
172 pinMessageUrl = fmt.Sprintf("%s%s/pinChatMessage", baseUrl, telegramToken)
173
174 return nil
175}
176
177func check(notificationWindowDays int, soft bool) (err error) {
178 now := time.Now()
179 today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
180 endWindow := today.AddDate(0, 0, notificationWindowDays)
181
182 notified_column := "notified"
183 if soft {
184 notified_column += "_soft"
185 }
186
187 var occurrences []Occurrence
188 db.Where(notified_column+" = ? AND ((month = ? AND day >= ?) OR (month = ? AND day <= ?))",
189 false, today.Month(), today.Day(), endWindow.Month(), endWindow.Day()).Find(&occurrences)
190
191 for _, occurrence := range occurrences {
192 occurrenceDate := time.Date(today.Year(), time.Month(occurrence.Month), int(occurrence.Day), 0, 0, 0, 0, time.Local)
193 if occurrenceDate.Before(today) || occurrenceDate.After(endWindow) || !occurrence.Notify {
194 continue
195 }
196
197 if !soft && occurrence.Notified {
198 continue
199 }
200
201 err = notifyTelegram(occurrence, soft)
202 if err != nil {
203 return err
204 }
205
206 err = db.Model(&Occurrence{}).Where("id = ?", occurrence.ID).Update(notified_column, true).Error
207 if err != nil {
208 return err
209 }
210 }
211
212 // Check if New Year's Eve is within the next sleep cycle
213 nextCheck := now.Add(SleepDuration)
214 if (now.Month() == 12 && now.Day() == 31) && (nextCheck.Month() == 1 && nextCheck.Day() == 1) {
215 resetNotifications(notified_column)
216 }
217
218 return
219}
220
221func CheckOccurrences() {
222 err := initNotifications()
223 if err != nil {
224 log.Println(err.Error())
225 return
226 }
227
228 for {
229 log.Println("Checking for new occurrences.")
230
231 check(NotificationWindow, false)
232 check(SoftNotificationWindow, true)
233
234 time.Sleep(SleepDuration)
235 }
236}