|
|
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=\"[提交记录]\" 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",;"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"}]
|
|
|
*/ |