package codeforcesbot import ( "encoding/json" "fmt" "io/ioutil" "net/http" "time" ) type Contest struct { Name string Phase string DurationSeconds int64 StartTimeSeconds int64 } type CFProblem struct { ContestId int Index string Name string Rating int } type Participant struct { Handle string } type Team struct { ContestId int Members []Participant } type Submission struct { Id int64 ContestId int64 CreationTimeSeconds int64 RelativeTimeSeconds int64 ProgrammingLanguage string Verdict string TimeConsumedMillis int64 MemoryConsumedBytes int64 Problem CFProblem Author Team } type ContestSelector func(*Contest) bool func isUpcoming(c *Contest) bool { if c.Phase == "BEFORE" && c.StartTimeSeconds - time.Now().Unix() <= 604800 { return true } return false } func (c Contest) String() (s string) { s = s + c.Name s = s + "\n开始时间: " + time.Unix(c.StartTimeSeconds, 0).Format(time.UnixDate) + "\n时长: " s = s + fmt.Sprintf("%02d: %02d\n", c.DurationSeconds / 3600, (c.DurationSeconds / 60) % 60) return s } type ContestRequest struct { Status string Result []Contest } type SubmissionRequest struct { Status string Result []Submission } var __contests []Contest var __lst_update int64 func getContests() (contests []Contest) { r, err := http.Get("https://codeforces.com/api/contest.list") if err == nil && r.StatusCode == http.StatusOK { body, _ := ioutil.ReadAll(r.Body) //fmt.Printf("%+v\n", string(body)) var c ContestRequest json.Unmarshal(body, &c) //fmt.Printf("%+v\n", c) contests = c.Result } else { fmt.Printf("ERROR status: %d\n", r.StatusCode) } __contests = contests __lst_update = time.Now().Unix() return contests } func getContestsCached() []Contest { const TIME_ALIVE int64 = 3600 now := time.Now().Unix() if now - __lst_update > TIME_ALIVE { getContests() } return __contests } func getRecentSubmissions() (submissions []Submission) { r, err := http.Get("https://codeforces.com/api/problemset.recentStatus?count=1000") if err == nil && r.StatusCode == http.StatusOK { body, _ := ioutil.ReadAll(r.Body) var s SubmissionRequest json.Unmarshal(body, &s) //fmt.Printf("%+v\n", c) submissions = s.Result } else { fmt.Printf("ERROR status: %d\n", r.StatusCode) } return submissions } func formatContests(contests []Contest, selector ContestSelector) (count int, s string){ for _, singleContest := range contests { if selector(&singleContest) { s = s + fmt.Sprintln(singleContest) count ++ } } return count, s } func getUserSubmissions(handle string, count int) []Submission { r, err := http.Get(fmt.Sprintf("https://codeforces.com/api/user.status?handle=%s&from=1&count=%d", handle, count)) if err == nil && r.StatusCode == http.StatusOK { body, _ := ioutil.ReadAll(r.Body) var s SubmissionRequest json.Unmarshal(body, &s) //fmt.Printf("%+v\n", c) return s.Result } else { if err == nil { fmt.Printf("ERROR status: %d\n", r.StatusCode) } else { fmt.Println(err.Error()) } } return nil } func getSubmissionJson(submission Submission) string { color := "777777" if submission.Verdict == "OK" { submission.Verdict = "Accepted" color = "00aa00" } return fmt.Sprintf("[CQ:xml,data=%s's Submission%s\n%v ms\t%v KB%s,resid=1]", submission.ContestId, submission.Id, submission.Author.Members[0].Handle, submission.Problem.Name, submission.TimeConsumedMillis, submission.MemoryConsumedBytes / 1024, color, submission.Verdict) } func countStuckSubmissions(submissions []Submission) (inQueue int, testing int) { for _, val := range submissions { if val.Verdict == "TESTING" { testing ++ } else if val.Verdict == "" { inQueue ++ } } return inQueue, testing } func getHeatValue(handles []string) (heatValue int64) { submitted := make(map[string]int64) nowSeconds := time.Now().Unix() for _, handle := range handles { submissions := getUserSubmissions(handle, 1000000000) fmt.Printf("%d submissions received.\n", submissions) for _, sub := range submissions { if sub.Verdict == "OK" { problemId := fmt.Sprint(sub.Problem.ContestId) + sub.Problem.Index if val, ok := submitted[problemId]; ok { heatValue -= int64(sub.Problem.Rating) * val } if bonus := int64(sub.Problem.Rating) - (nowSeconds - sub.CreationTimeSeconds) / 3600; bonus > 0 { heatValue += int64(sub.Problem.Rating) * bonus submitted[problemId] = bonus } else { heatValue += int64(sub.Problem.Rating) submitted[problemId] = 1 } } } } return heatValue } func getRegisteredGroups() []int64 { return SqlGetNotifiedGroup() } func isTodaysContest(c *Contest) bool { y1, m1, d1 := time.Now().Date() y2, m2, d2 := time.Unix(c.StartTimeSeconds, 0).Date() return y1 == y2 && m1 == m2 && d1 == d2 } func isContestInHour(c *Contest) bool { nowSeconds := time.Now().Unix() return c.StartTimeSeconds - nowSeconds <= 3600 && c.StartTimeSeconds > nowSeconds } func setupNotification(contests []Contest) { nowSeconds := time.Now().Unix() for _, contest := range contests { if contest.StartTimeSeconds > nowSeconds && contest.StartTimeSeconds - nowSeconds <= 60 * 60 * 24 { go addTimer(time.Until(time.Unix(contest.StartTimeSeconds -60 * 60, 0)), notifyContestsInHour) } } } func notifyTodaysContests(t time.Time) { fmt.Println("notify todays contests") groups := getRegisteredGroups() contests := getContestsCached() count, s := formatContests(contests, isTodaysContest) dateString := time.Now().Format("2006 年 1 月 2 日") if count <= 0 { s = "今天CF没有比赛,该摸了。" } for _, g := range groups { sendGroupMessage(g, StringToImg(fmt.Sprintf("早上好!今天是 %s。\n今天有%d场比赛:\n%s", dateString, count, s))) } setupNotification(contests) } func notifyContestsInHour(t time.Time) { groups := getRegisteredGroups() contests := getContestsCached() count, s := formatContests(contests, isContestInHour) if count <= 0 { return } for _, g := range groups { sendGroupMessage(g, fmt.Sprintf("以下比赛将在1小时内开始,请做好准备。\n%s", s)) } } /* json. [CQ:json,data={"app":"com.tencent.structmsg",;"config":{"autosize":true,;"ctime":1636448686,;"forward":true,;,;"type":"normal"},;"desc":"新闻",;"meta":{"news":{"action":"",;"android_pkg_name":"",;"app_type":1,;"appid":100792067,;"desc":"点击加入,与好友协作文档",;"jumpUrl":"https://kdocs.cn/el/eucf3fsB8DVh?f=201",;"preview":"https://qn.cache.wpscdn.cn/s1/images/ficons/icons-xls.png",;"source_icon":"",;"source_url":"",;"tag":"WPSOffice",;"title":"邀请你一起编辑「419座位登记」"}},;"prompt":"[分享]邀请你一起编辑「419座位登记」",;"ver":"0.0.0.1",;"view":"news"}] */