From 593c6cc0603a01d5b52a0b0ba55ca6dbd22b90e1 Mon Sep 17 00:00:00 2001 From: a3510377 Date: Mon, 30 Sep 2024 15:03:46 +0000 Subject: [PATCH] Refactor code to improve readability and maintainability --- api.go | 36 ++++++++++++++++++++++----------- config.go | 14 +++++++------ main.go | 59 +++++++++++++++++++++++++++++++++++++++++-------------- utils.go | 41 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 32 deletions(-) create mode 100644 utils.go diff --git a/api.go b/api.go index 6bd2734..d31a4ff 100644 --- a/api.go +++ b/api.go @@ -24,8 +24,10 @@ const ( ) var ( - noClose = []string{"尚未列入警戒區。", "今天照常上班、照常上課。", "明天照常上班、照常上課。"} - timeMatch = regexp.MustCompile(`\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}`) + noClose = []string{"尚未列入警戒區", "今天照常上班、照常上課", "明天照常上班、照常上課"} + noClassMap = map[string]void{} + timeMatch = regexp.MustCompile(`\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}`) + countyCityPrefixRegex = regexp.MustCompile("^.{2}[縣市]") ) // const WorkSchoolCloseURL = "https://alerts.ncdr.nat.gov.tw/RssAtomFeed.ashx?AlertType=33" @@ -40,8 +42,14 @@ type WorkSchoolCloseMap struct { Data map[string]WorkSchoolCloseData } type WorkSchoolCloseData struct { - County string - State string + County string + Details []string +} + +func init() { + for _, v := range noClose { + noClassMap[v] = void{} + } } func GetClosedSchool() (*WorkSchoolCloseMap, error) { @@ -68,16 +76,22 @@ func GetClosedSchool() (*WorkSchoolCloseMap, error) { county, state := strings.TrimSpace(values[0]), strings.TrimSpace(values[1]) - for _, v := range noClose { - if state == v { + details := []string{} + for _, v := range regexp.MustCompile("。|\n").Split(state, -1) { + v = strings.TrimSpace(v) + if v == "" { + continue + } + if _, ok := noClassMap[v]; ok { return } - state = strings.TrimLeft(state, v) + + details = append(details, countyCityPrefixRegex.ReplaceAllString(v, "")) } result.Data[county] = WorkSchoolCloseData{ - County: county, - State: strings.ReplaceAll(strings.TrimSpace(state), " ", "\n"), + County: county, + Details: details, } }) @@ -99,7 +113,7 @@ func NotifyLine(values WorkSchoolClose) { text := "\n" for _, v := range values.Data { - text += fmt.Sprintf("%s: %s\n", v.County, strings.ReplaceAll(v.State, "\n", "\n ")) + text += fmt.Sprintf("%s: \n %s\n", v.County, strings.Join(v.Details, "\n ")) } data := url.Values{"message": {text}}.Encode() req, _ := http.NewRequest("POST", LineMessageAPIUrl, strings.NewReader(data)) @@ -134,7 +148,7 @@ func NotifyDiscord(values WorkSchoolClose) { for _, v := range values.Data { fields = append(fields, map[string]any{ "name": v.County, - "value": v.State, + "value": strings.Join(v.Details, "\n"), "inline": true, }) } diff --git a/config.go b/config.go index 8b2ab65..5a3b5e3 100644 --- a/config.go +++ b/config.go @@ -77,8 +77,8 @@ func init() { } config := NewConfig() data, _ := yaml.Marshal(config) - os.MkdirAll(path.Dir(ConfigFilePath), 0o644) - os.WriteFile(ConfigFilePath, data, 0o644) + os.MkdirAll(path.Dir(ConfigFilePath), 0777) + os.WriteFile(ConfigFilePath, data, 0777) } else { yaml.Unmarshal(yamlFile, &ConfigData) } @@ -89,12 +89,14 @@ func init() { err := watchFile(ConfigFilePath) if err != nil { log.Println("config watch error", err) + time.Sleep(time.Second * 5) } // reload config yamlFile, err := os.ReadFile(ConfigFilePath) if err != nil { log.Println("config watch error", err) + time.Sleep(time.Second * 5) } else { yaml.Unmarshal(yamlFile, &ConfigData) } @@ -133,19 +135,19 @@ func watchFile(filePath string) error { return nil } -func GetTmpDate() (value map[string]string) { +func GetTmpDate() (value map[string]map[string]bool) { if data, err := os.ReadFile(TmpFilePath); err == nil { json.Unmarshal(data, &value) return } - return map[string]string{} // if file not exist + return map[string]map[string]bool{} // if file not exist } func WriteTmpDate(v any) (err error) { bytes, err := json.Marshal(v) if err == nil { - os.MkdirAll(path.Dir(ConfigFilePath), 0o644) - os.WriteFile(TmpFilePath, bytes, 0o644) + os.MkdirAll(path.Dir(ConfigFilePath), 0777) + os.WriteFile(TmpFilePath, bytes, 0777) } return } diff --git a/main.go b/main.go index 99513c6..835994d 100644 --- a/main.go +++ b/main.go @@ -2,33 +2,43 @@ package main import ( "log" + "strings" "time" "github.com/robfig/cron/v3" ) func main() { - main := func() { + const specTime = "*/15 * * * *" + // everyMinute, _ := cron.ParseStandard("* * * * *") + + c := cron.New(cron.WithLogger(cron.VerbosePrintfLogger(log.Default()))) + + loop, _ := c.AddFunc(specTime, func() { retryCount := 0 for ; retryCount < 3; retryCount++ { + log.Println("Check and notification") if err := checkAndNotification(); err != nil { + log.Println("checkAndNotification error", err) time.Sleep(time.Second * 5) // retry after 5 seconds continue } + + log.Println(strings.Repeat("-", 70)) break } if retryCount >= 3 { log.Println("Retry 3 times, skip check") } - } + }) + entry := c.Entry(loop) - const specTime = "*/15 * * * *" + go entry.Job.Run() // first run - c := cron.New(cron.WithLogger(cron.VerbosePrintfLogger(log.Default()))) + // oldSchedule := entry.Schedule + // entry.Schedule = everyMinute + // entry.Next = time.Now().In(c.Location()) - c.AddFunc(specTime, main) - - main() // first run c.Run() // loop start } @@ -46,22 +56,41 @@ func checkAndNotification() error { } tmpData := GetTmpDate() - for k, v := range data.Data { - log.Println("---------------") - log.Println(k, v) - if !areaNamesMap[k] || tmpData[k] == v.State { - log.Println("skip") + for county, v := range data.Data { + if !areaNamesMap[county] { continue } - notifications = append(notifications, v) - tmpData[k] = v.State + + countyTmpData, countyExists := tmpData[county] + if !countyExists { + tmpData[county] = map[string]bool{} + } + + for _, detail := range v.Details { + absoluteDetail := ConvertRelativeToAbsoluteTime(detail, *data.Date) + if _, ok := countyTmpData[absoluteDetail]; ok { + continue + } + + notifications = append(notifications, v) + tmpData[county][absoluteDetail] = true + } } - log.Println("---------------") if len(notifications) > 0 { notification(WorkSchoolClose{Date: data.Date, Data: notifications}) } + // clear timeout data + nowTime := time.Now() + for _, data := range tmpData { + for k := range data { + if HasStatusIsOld(k, nowTime) { + delete(data, k) + } + } + } + WriteTmpDate(tmpData) return nil } diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..82a9343 --- /dev/null +++ b/utils.go @@ -0,0 +1,41 @@ +package main + +import ( + "regexp" + "strings" + "time" +) + +type void struct{} + +var dateRegexp = regexp.MustCompile(`\d{4}-\d{2}-\d{2}`) + +func ConvertRelativeToAbsoluteTime(relativeTime string, date time.Time) string { + nowDate := date.Format("2006-01-02") + nextDate := date.AddDate(0, 0, 1).Format("2006-01-02") + dayAfterNextDate := date.AddDate(0, 0, 2).Format("2006-01-02") + + replacer := strings.NewReplacer( + "今天", nowDate, + "明天", nextDate, + "後天", dayAfterNextDate, + ) + + return replacer.Replace(relativeTime) +} + +func HasStatusIsOld(status string, referenceDate time.Time) bool { + referenceDate = referenceDate.Truncate(24 * time.Hour) + + for _, match := range dateRegexp.FindAllString(status, -1) { + parsedDate, err := time.Parse("2006-01-02", match) + if err != nil { + continue + } + if !parsedDate.Before(referenceDate) { + return false + } + } + + return true +}