You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 lines
7.2 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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=<?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID=\"1\" templateID=\"0\" action=\"web\" brief=\"&#91;提交记录&#93;\" sourceMsgId=\"0\" url=\"http://codeforces.com/contest/%d/submission/%d\" flag=\"0\" adverSign=\"0\" multiMsgFlag=\"0\"><item layout=\"6\" advertiser_id=\"0\" aid=\"0\"><title size=\"34\">%s's Submission</title><summary size=\"26\">%s\n%v ms\t%v KB</summary><hr hidden=\"false\" style=\"0\" /><summary size=\"26\" color=\"#%s\">%s</summary></item><source name=\"Codeforces\" icon=\"http://codeforces.com/favicon.png\" action=\"\" appid=\"-1\" /></msg>,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"&#44;;"config":{"autosize":true&#44;;"ctime":1636448686&#44;;"forward":true&#44;;&#44;;"type":"normal"}&#44;;"desc":"新闻"&#44;;"meta":{"news":{"action":""&#44;;"android_pkg_name":""&#44;;"app_type":1&#44;;"appid":100792067&#44;;"desc":"点击加入,与好友协作文档"&#44;;"jumpUrl":"https://kdocs.cn/el/eucf3fsB8DVh?f=201"&#44;;"preview":"https://qn.cache.wpscdn.cn/s1/images/ficons/icons-xls.png"&#44;;"source_icon":""&#44;;"source_url":""&#44;;"tag":"WPSOffice"&#44;;"title":"邀请你一起编辑「419座位登记」"}}&#44;;"prompt":"&#91;分享&#93;邀请你一起编辑「419座位登记」"&#44;;"ver":"0.0.0.1"&#44;;"view":"news"}]
*/