all repos — cameraman @ 1e8a4ea7e5c0c5647024075437c220869c521c0e

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}