Compare commits
6 Commits
0a73bca056
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11ea7b8398 | ||
|
|
7d3c083cea | ||
|
|
dbfbd6865a | ||
|
|
27ce4d3c78 | ||
|
|
693996a262 | ||
|
|
7da7f38e13 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/tg-holy-bot
|
||||||
|
/data/config.json
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
## tg-holy-bot
|
## tg-holy-bot
|
||||||
|
|
||||||
Телеграммный бот для канала с праздниками. Раз в сутки забирает список праздников с к сайта celebratoday.com и
|
Телеграммный бот для канала с праздниками. Раз в сутки забирает список праздников с к сайта celebratoday.com и
|
||||||
показывает его на канальчике в телеграме.
|
показывает его на канальчике в телеграме. Создан исключительно в обучающих целях, как пример приложения на языке go.
|
||||||
|
|
||||||
## Как это собрать?
|
## Как это собрать?
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ func main() {
|
|||||||
|
|
||||||
log.Infof("Stop cron")
|
log.Infof("Stop cron")
|
||||||
|
|
||||||
// Jobs usually runs in a fraction of second, don't bother waiting for their completion.
|
// Кроновые задачки выполняются за доли сеекунды, не будем заморачиваться ожиданием их завершения.
|
||||||
_ = c.Stop()
|
_ = c.Stop()
|
||||||
|
|
||||||
log.Infof("Shutdown bot")
|
log.Infof("Shutdown bot")
|
||||||
|
|||||||
@@ -24,12 +24,14 @@ func APIClient() (string, error) {
|
|||||||
|
|
||||||
// Формальная попытка притвориться браузером.
|
// Формальная попытка притвориться браузером.
|
||||||
_, _, day := time.Now().Date()
|
_, _, day := time.Now().Date()
|
||||||
userAgent := Config.UserAgents[day]
|
userAgent := Config.UserAgents[day-1]
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
if err := requests.URL(url).UserAgent(userAgent).
|
if err := requests.URL(url).UserAgent(userAgent).
|
||||||
Header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8").
|
Header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8").
|
||||||
Header("Accept-Language", "en-US,en;q=0.5").
|
Header("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7").
|
||||||
Header("User-Agent", "ru-RU").
|
|
||||||
Header("Accept-Charset", "utf-8").ToString(&respBody).Fetch(ctx); err != nil {
|
Header("Accept-Charset", "utf-8").ToString(&respBody).Fetch(ctx); err != nil {
|
||||||
return "", fmt.Errorf("Unable to GET %s: %w", url, err) //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
return "", fmt.Errorf("Unable to GET %s: %w", url, err) //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
}
|
}
|
||||||
@@ -70,6 +72,8 @@ func parser(data string) ([]string, error) {
|
|||||||
// А теперь пробежимся по полученной структуре и вытянем из неё все строки с праздниками.
|
// А теперь пробежимся по полученной структуре и вытянем из неё все строки с праздниками.
|
||||||
for _, ass := range s {
|
for _, ass := range s {
|
||||||
for _, entity := range ass.MainEntity {
|
for _, entity := range ass.MainEntity {
|
||||||
|
holidays = append(holidays, entity.AcceptedAnswer.Text)
|
||||||
|
|
||||||
for _, sugestedAnswer := range entity.SuggestedAnswer {
|
for _, sugestedAnswer := range entity.SuggestedAnswer {
|
||||||
holidays = append(holidays, sugestedAnswer.Text)
|
holidays = append(holidays, sugestedAnswer.Text)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,56 @@ package holy
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"tg-holy-bot/internal/log"
|
"tg-holy-bot/internal/log"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CronTask sends generated hiliday list to the channel.
|
const maxRetryAttempts = 15
|
||||||
|
|
||||||
|
// CronTask sends generated holiday list to the channel.
|
||||||
func CronTask() {
|
func CronTask() {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
errcnt int
|
||||||
|
phrase string
|
||||||
|
timeout time.Duration
|
||||||
|
)
|
||||||
|
|
||||||
log.Info("Cron Task triggered")
|
log.Info("Cron Task triggered")
|
||||||
|
|
||||||
phrase, err := APIClient()
|
for {
|
||||||
|
phrase, err = APIClient()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Unable to get holidays: %s", err)
|
errcnt++
|
||||||
|
|
||||||
return
|
log.Errorf("Unable to get holidays (try %d/%d): %s", errcnt, maxRetryAttempts, err)
|
||||||
|
|
||||||
|
switch errcnt {
|
||||||
|
case 1, 2:
|
||||||
|
timeout = 1 * time.Second
|
||||||
|
|
||||||
|
case 3, 4, 5:
|
||||||
|
timeout = 5 * time.Second
|
||||||
|
|
||||||
|
case 6, 7, 8:
|
||||||
|
timeout = 1 * time.Minute
|
||||||
|
|
||||||
|
case 9, 10, 11:
|
||||||
|
timeout = 5 * time.Minute
|
||||||
|
|
||||||
|
case 12, 13, 14:
|
||||||
|
timeout = 1 * time.Hour
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Error("Giveup trying to get holidays")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = Bot.SendMessage(phrase, Config.ChannelID, nil); err != nil {
|
if _, err = Bot.SendMessage(phrase, Config.ChannelID, nil); err != nil {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package holy
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"tg-holy-bot/internal/log"
|
"tg-holy-bot/internal/log"
|
||||||
@@ -45,7 +46,7 @@ func ReadConfig() error {
|
|||||||
|
|
||||||
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но он и не часто требуется.
|
// сериализуем в json, а потом json преврщааем в стркутурку. Не очень эффективно, но оно и не часто требуется.
|
||||||
var (
|
var (
|
||||||
sampleConfig *MyConfig
|
sampleConfig *MyConfig
|
||||||
tmp map[string]any
|
tmp map[string]any
|
||||||
@@ -103,6 +104,13 @@ func ReadConfig() error {
|
|||||||
return errors.New("Amount of user_agent strings in config must be exactly 31! Refusing to start") //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
return errors.New("Amount of user_agent strings in config must be exactly 31! Refusing to start") //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate that all user agent strings are non-empty
|
||||||
|
for i, userAgent := range sampleConfig.UserAgents {
|
||||||
|
if userAgent == "" {
|
||||||
|
return fmt.Errorf("User agent string at index %d is empty! Refusing to start", i) //nolint: staticcheck // Looks like golangci-lint ignoring settings about fucking ST1005 check. We want readable errors despite what wants author of linter or assholes from go dev team.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.Infof("Using %s as config file", location)
|
log.Infof("Using %s as config file", location)
|
||||||
|
|
||||||
Config = sampleConfig
|
Config = sampleConfig
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ func SigHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Shutdown = true
|
Shutdown = true
|
||||||
|
|
||||||
close(Run)
|
close(Run)
|
||||||
|
|
||||||
|
// Ensure we exit cleanly after handling the signal
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package holy
|
package holy
|
||||||
|
|
||||||
type (
|
type (
|
||||||
// Holy структура, описывающая искомый json-чик.
|
// Holy структура, описывающая искомый json-чик с праздниками.
|
||||||
Holy struct {
|
Holy struct {
|
||||||
AtContext string `json:"@context"`
|
AtContext string `json:"@context"`
|
||||||
AtType string `json:"@type"`
|
AtType string `json:"@type"`
|
||||||
|
|||||||
Reference in New Issue
Block a user