File tree

15 files changed

+271
-25
lines changed

15 files changed

+271
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -128,15 +128,23 @@ func (a *APP) getBadge(value url.Values, key, left, right string, color string)
128128
return body, nil
129129
}
130130

131-
badgeBody, err := shield.Badge(value, left, right, color)
131+
fn := func() (interface{}, error) {
132+
badgeBody, err := shield.Badge(value, left, right, color)
133+
if err != nil {
134+
return nil, err
135+
}
136+
if err = a.cache.SaveByteBody(key, badgeBody, 7*24*time.Hour); err != nil {
137+
log.Err(err).Str("Key", key).Str("Left", left).Str("Right", right).Str("Color", color).Msg("save badge data error")
138+
}
139+
return badgeBody, err
140+
}
141+
142+
result, err, _ := a.group.Do(key, fn)
132143
if err != nil {
133144
return nil, err
134145
}
135-
if err = a.cache.SaveByteBody(key, badgeBody, 7*24*time.Hour); err != nil {
136-
log.Err(err).Str("Key", key).Str("Left", left).Str("Right", right).Str("Color", color).Msg("save badge data error")
137-
}
138146

139-
return badgeBody, nil
147+
return result.([]byte), nil
140148
}
141149

142150
func (a *APP) saveUser(info *models.UserProfile, isCN bool) error {
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ func (a *APP) getCard(badgeType BadgeType, name string, r *http.Request) ([]byte
1515
switch badgeType {
1616
case BadgeTypeQuestionProcessCard:
1717
f = a.getQuestionProcess
18+
case BadgeTypeContestRankingCard:
19+
f = a.getContestRankingInfo
1820
default:
1921
return nil, errors.Errorf("not found card function")
2022
}
@@ -54,6 +56,20 @@ func (a *APP) getQuestionProcess(name string, r *http.Request) ([]byte, error) {
5456
return nil, ErrUserNotSupport
5557
}
5658

57-
body, err := card.Build(data)
59+
body, err := card.QuestionProcess(name, data)
60+
return body, err
61+
}
62+
63+
func (a *APP) getContestRankingInfo(name string, r *http.Request) ([]byte, error) {
64+
data, err := leetcodecn.GetUserContestRankingInfo(name)
65+
if err != nil {
66+
return nil, err
67+
68+
}
69+
if data == nil {
70+
return nil, ErrUserNotSupport
71+
}
72+
73+
body, err := card.ContestRanking(name, data)
5874
return body, err
5975
}
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ func Router(r *mux.Router, a *APP, w io.Writer) {
7878
api.Methods(http.MethodGet).Path("/card/question-process/{name:.+}.svg").Handler(
7979
handlers.CombinedLoggingHandler(w, a.HandlerFunc(BadgeTypeQuestionProcessCard, isCN)),
8080
)
81+
82+
// [card] 竞赛信息 todo: 美化
83+
api.Methods(http.MethodGet).Path("/card/contest-ranking/{name:.+}.svg").Handler(
84+
handlers.CombinedLoggingHandler(w, a.HandlerFunc(BadgeTypeContestRankingCard, isCN)),
85+
)
8186
// [basic] 获得个人信息
8287
api.Methods(http.MethodGet).Path("/{name:.+}.svg").Handler(
8388
handlers.CombinedLoggingHandler(w, a.HandlerFunc(BadgeTypeProfile, isCN)),
@@ -99,7 +104,7 @@ func (a *APP) HandlerFunc(badgeType BadgeType, isCN bool) http.Handler {
99104
// f = a.Badge
100105
case BadgeTypeChartSubmissionCalendar:
101106
f = a.SubCal
102-
case BadgeTypeQuestionProcessCard:
107+
case BadgeTypeQuestionProcessCard, BadgeTypeContestRankingCard:
103108
f = a.Card
104109
}
105110

Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818
BadgeTypeChartSolved
1919
BadgeTypeChartSubmissionCalendar
2020
BadgeTypeQuestionProcessCard
21+
BadgeTypeContestRankingCard
2122
)
2223

2324
func (b BadgeType) String() string {
@@ -48,6 +49,8 @@ func (b BadgeType) String() string {
4849
return "submission-calendar"
4950
case BadgeTypeQuestionProcessCard:
5051
return "question_process_card"
52+
case BadgeTypeContestRankingCard:
53+
return "contest_ranking_card"
5154
}
5255

5356
return ""
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package card
22

33
import (
44
"bytes"
5+
"fmt"
56
"text/template"
67

78
".com/pkg/errors"
@@ -10,7 +11,7 @@ import (
1011
".com/haozibi/leetcode-badge/internal/statics"
1112
)
1213

13-
func Build(data *models.UserQuestionPrecess) ([]byte, error) {
14+
func QuestionProcess(name string, data *models.UserQuestionPrecess) ([]byte, error) {
1415
var (
1516
baseLen = 215
1617
)
@@ -19,49 +20,83 @@ func Build(data *models.UserQuestionPrecess) ([]byte, error) {
1920
return int(float64(baseLen) * (float64(num) / float64(total)))
2021
}
2122

22-
info := Info{
23+
info := QuestionProcessInfo{
24+
Name: name,
25+
2326
BaseLen: baseLen,
2427
EasyLen: getLen(data.Easy.AcceptedNum, data.Easy.TotalNum),
2528
MediumLen: getLen(data.Medium.AcceptedNum, data.Medium.TotalNum),
2629
HardLen: getLen(data.Hard.AcceptedNum, data.Hard.TotalNum),
2730

28-
EasyNum: data.Easy.AcceptedNum,
31+
EasyNum: fmt.Sprintf("% 4d", data.Easy.AcceptedNum),
2932
EasyTotal: data.Easy.TotalNum,
30-
MediumNum: data.Medium.AcceptedNum,
33+
MediumNum: fmt.Sprintf("% 4d", data.Medium.AcceptedNum),
3134
MediumTotal: data.Medium.TotalNum,
32-
HardNum: data.Hard.AcceptedNum,
35+
HardNum: fmt.Sprintf("% 4d", data.Hard.AcceptedNum),
3336
HardTotal: data.Hard.TotalNum,
3437

3538
AcceptNum: data.Overview.AcceptedNum,
3639
}
3740

38-
t, err := template.New("foo").Parse(string(statics.TemplateQuestionProcess()))
41+
return build(statics.TemplateQuestionProcess(), info)
42+
}
43+
44+
func ContestRanking(name string, data *models.UserContestRankingInfo) ([]byte, error) {
45+
info := ContestRankingInfo{
46+
Name: name,
47+
Rating: fmt.Sprintf("%d", int(data.Rating)),
48+
LocalRanking: fmt.Sprintf("% 6d", data.LocalRanking),
49+
GlobalRanking: fmt.Sprintf("% 6d", data.GlobalRanking),
50+
51+
LocalTotal: fmt.Sprintf("/%d", data.LocalTotalParticipants),
52+
GlobalTotal: fmt.Sprintf("/%d", data.GlobalTotalParticipants),
53+
54+
Top: fmt.Sprintf("%0.2f", 100.0-data.TopPercentage),
55+
}
56+
return build(statics.TemplateContestRanking(), info)
57+
}
58+
59+
func build(temp []byte, data interface{}) ([]byte, error) {
60+
t, err := template.New("foo").Parse(string(temp))
3961
if err != nil {
4062
return nil, errors.WithStack(err)
4163
}
4264

4365
buf := bytes.NewBuffer(nil)
44-
err = t.Execute(buf, info)
66+
err = t.Execute(buf, data)
4567
if err != nil {
4668
return nil, errors.WithStack(err)
4769
}
4870

4971
return buf.Bytes(), nil
5072
}
5173

52-
type Info struct {
74+
type ContestRankingInfo struct {
75+
Name string
76+
Rating string
77+
LocalRanking string
78+
GlobalRanking string
79+
80+
LocalTotal string
81+
GlobalTotal string
82+
83+
Top string
84+
}
85+
86+
type QuestionProcessInfo struct {
87+
Name string
5388
// 长度
5489
BaseLen int
5590
EasyLen int
5691
MediumLen int
5792
HardLen int
5893

5994
// 数量
60-
EasyNum int
95+
EasyNum string
6196
EasyTotal int
62-
MediumNum int
97+
MediumNum string
6398
MediumTotal int
64-
HardNum int
99+
HardNum string
65100
HardTotal int
66101

67102
AcceptNum int
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,37 @@ type LCSubmissionProgress struct {
7979
QuestionTotal int `json:"questionTotal"`
8080
Typename string `json:"__typename"`
8181
}
82+
83+
// user contest info
84+
85+
type LCUserContestRanking struct {
86+
AttendedContestsCount int `json:"attendedContestsCount"`
87+
Rating float64 `json:"rating"`
88+
GlobalRanking int `json:"globalRanking"`
89+
LocalRanking int `json:"localRanking"`
90+
GlobalTotalParticipants int `json:"globalTotalParticipants"`
91+
LocalTotalParticipants int `json:"localTotalParticipants"`
92+
TopPercentage float64 `json:"topPercentage"`
93+
}
94+
95+
// type LCContest struct {
96+
// Title string `json:"title"`
97+
// TitleCn string `json:"titleCn"`
98+
// StartTime int `json:"startTime"`
99+
// }
100+
101+
// type LCUserContestRankingHistory struct {
102+
// Attended bool `json:"attended"`
103+
// TotalProblems int `json:"totalProblems"`
104+
// TrendingDirection string `json:"trendingDirection"`
105+
// FinishTimeInSeconds int `json:"finishTimeInSeconds"`
106+
// Rating interface{} `json:"rating"`
107+
// Score int `json:"score"`
108+
// Ranking int `json:"ranking"`
109+
// Contest LCContest `json:"contest"`
110+
// }
111+
112+
type LeetCodeUserContestRankingInfo struct {
113+
UserContestRanking *LCUserContestRanking `json:"userContestRanking"`
114+
// UserContestRankingHistory []LCUserContestRankingHistory `json:"userContestRankingHistory"`
115+
}
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,27 @@ func GetUserProfile(name string) (*models.UserProfile, error) {
9797
return userProfile, nil
9898
}
9999

100+
func GetUserContestRankingInfo(name string) (*models.UserContestRankingInfo, error) {
101+
ptr, err := Get(models.RequestTypeUserContestRankingInfo, name)
102+
if err != nil {
103+
return nil, err
104+
}
105+
p := ptr.(*LeetCodeUserContestRankingInfo)
106+
if p.UserContestRanking == nil {
107+
return nil, nil
108+
}
109+
110+
return &models.UserContestRankingInfo{
111+
AttendedContestsCount: p.UserContestRanking.AttendedContestsCount,
112+
Rating: p.UserContestRanking.Rating,
113+
GlobalRanking: p.UserContestRanking.GlobalRanking,
114+
LocalRanking: p.UserContestRanking.LocalRanking,
115+
GlobalTotalParticipants: p.UserContestRanking.GlobalTotalParticipants,
116+
LocalTotalParticipants: p.UserContestRanking.LocalTotalParticipants,
117+
TopPercentage: p.UserContestRanking.TopPercentage,
118+
}, nil
119+
}
120+
100121
func Get(rt models.RequestType, name string) (any, error) {
101122
if name == "" {
102123
return nil, errors.Errorf("miss name")
@@ -111,7 +132,7 @@ func Get(rt models.RequestType, name string) (any, error) {
111132

112133
var (
113134
uri = cfg.URI
114-
method = http.MethodPost
135+
method = cfg.Method
115136
client = http.DefaultClient
116137
query = fmt.Sprintf(cfg.Query, name)
117138
)
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,17 @@ func TestGetUserQuestionProgress(t *testing.T) {
2020
spew.Dump(p)
2121
}
2222
}
23+
24+
func TestGetUserContestRankingInfo(t *testing.T) {
25+
names := []string{
26+
"haozibi",
27+
//"oooooooooooxxxxxx",
28+
//"ac_oier",
29+
}
30+
31+
for _, v := range names {
32+
p, err := GetUserContestRankingInfo(v)
33+
fmt.Println(err)
34+
spew.Dump(p)
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package leetcodecn
22

33
import (
4+
"net/http"
45
"reflect"
56

67
".com/haozibi/leetcode-badge/internal/models"
@@ -10,14 +11,22 @@ func init() {
1011
models.Register(models.RequestTypeUserProfile, models.RequestConfig{
1112
Desc: "user_profile",
1213
URI: "https://leetcode-cn.com/graphql",
14+
Method: http.MethodPost,
1315
Query: "{\"operationName\":\"userPublicProfile\",\"variables\":{\"userSlug\":\"%s\"},\"query\":\"query userPublicProfile($userSlug: String!) {\\nuserProfilePublicProfile(userSlug: $userSlug) {\\nusername\\nhaveFollowed\\nsiteRanking\\nprofile {\\nuserSlug\\nrealName\\nuserAvatar\\nlocation\\ncontestCount\\nasciiCode\\n__typename\\n}\\n submissionProgress {\\ntotalSubmissions\\nwaSubmissions\\nacSubmissions\\nreSubmissions\\notherSubmissions\\nacTotal\\nquestionTotal\\n__typename\\n}\\n__typename\\n}\\n}\\n\"}",
1416
Response: reflect.TypeOf(LeetCodeUserProfile{}),
1517
})
16-
1718
models.Register(models.RequestTypeUserQuestionProgress, models.RequestConfig{
1819
Desc: "user_request_process",
1920
URI: "https://leetcode-cn.com/graphql",
21+
Method: http.MethodPost,
2022
Query: `{"query": "\n query userQuestionProgress($userSlug: String!) {\n userProfileUserQuestionProgress(userSlug: $userSlug) {\n numAcceptedQuestions {\n difficulty\n count\n }\n numFailedQuestions {\n difficulty\n count\n }\n numUntouchedQuestions {\n difficulty\n count\n }\n }\n}\n ","variables": {"userSlug": "%s"}}`,
2123
Response: reflect.TypeOf(LeetCodeUserQuestionProgress{}),
2224
})
25+
models.Register(models.RequestTypeUserContestRankingInfo, models.RequestConfig{
26+
Desc: "user_contest_rank_info",
27+
URI: "https://leetcode.cn/graphql/noj-go/",
28+
Method: http.MethodPost,
29+
Query: `{"query":"\n query userContestRankingInfo($userSlug: String!) {\n userContestRanking(userSlug: $userSlug) {\n attendedContestsCount\n rating\n globalRanking\n localRanking\n globalTotalParticipants\n localTotalParticipants\n topPercentage\n }\n userContestRankingHistory(userSlug: $userSlug) {\n attended\n totalProblems\n trendingDirection\n finishTimeInSeconds\n rating\n score\n ranking\n contest {\n title\n titleCn\n startTime\n }\n }\n}\n ","variables":{"userSlug":"%s"}}`,
30+
Response: reflect.TypeOf(LeetCodeUserContestRankingInfo{}),
31+
})
2332
}
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ type UserProfile struct {
1616
}
1717

1818
type UserQuestionProcessStat struct {
19-
AcceptedNum int
20-
FailedNum int
21-
UntouchedNum int
22-
TotalNum int
19+
AcceptedNum int // ac 数量
20+
FailedNum int // 失败数量
21+
UntouchedNum int // 没有尝试过的数量
22+
TotalNum int // 总共数量,即上面 3 者之和
2323
}
2424

2525
type UserQuestionPrecess struct {
@@ -28,3 +28,13 @@ type UserQuestionPrecess struct {
2828
Medium UserQuestionProcessStat
2929
Hard UserQuestionProcessStat
3030
}
31+
32+
type UserContestRankingInfo struct {
33+
AttendedContestsCount int // 完赛数量
34+
Rating float64 // 竞赛分数
35+
GlobalRanking int // 全球排名
36+
LocalRanking int // 全国排名
37+
GlobalTotalParticipants int // 全球总人数
38+
LocalTotalParticipants int // 全国总人数
39+
TopPercentage float64 // 前 top
40+
}
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ type RequestType int
99
const (
1010
RequestTypeUserProfile RequestType = iota + 1
1111
RequestTypeUserQuestionProgress
12+
RequestTypeUserContestRankingInfo
1213
)
1314

1415
type RequestConfig struct {
1516
Desc string
1617
URI string
18+
Method string
1719
Query string
1820
Response reflect.Type
1921
}
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ func TemplateQuestionProcess() []byte {
1919
return readBody("template/question_process.svg")
2020
}
2121

22+
func TemplateContestRanking() []byte {
23+
return readBody("template/contest_ranking.svg")
24+
}
25+
2226
func TTF() []byte {
2327
return readBody("charts/Sunflower-Medium.ttf")
2428
}

0 commit comments

Comments
 (0)