all repos — cameraman @ main

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}