all repos — cameraman @ 260b7925d78099d96d6ef24f5a15fba517091961

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}