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