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 if soft {
141 return nil
142 }
143
144 msgId, err := sendTelegramMessage(message)
145 if err != nil {
146 return err
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 || occurrence.Notified {
194 continue
195 }
196
197 err = notifyTelegram(occurrence, soft)
198 if err != nil {
199 return err
200 }
201
202 err = db.Model(&Occurrence{}).Where("id = ?", occurrence.ID).Update(notified_column, true).Error
203 if err != nil {
204 return err
205 }
206 }
207
208 // Check if New Year's Eve is within the next sleep cycle
209 nextCheck := now.Add(SleepDuration)
210 if (now.Month() == 12 && now.Day() == 31) && (nextCheck.Month() == 1 && nextCheck.Day() == 1) {
211 resetNotifications(notified_column)
212 }
213
214 return
215}
216
217func CheckOccurrences() {
218 err := initNotifications()
219 if err != nil {
220 log.Println(err.Error())
221 return
222 }
223
224 for {
225 log.Println("Checking for new occurrences.")
226
227 check(NotificationWindow, false)
228 check(SoftNotificationWindow, true)
229
230 time.Sleep(SleepDuration)
231 }
232}