├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── README_en.md ├── cover.sh ├── go.mod ├── go.sum ├── internal ├── app.go ├── cache.go ├── config.go ├── helper.go ├── internal_test.go ├── leetcode.go ├── log.go └── shields.go └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | .idea/ 14 | testjs/ 15 | bin/ 16 | log.txt 17 | ttt/ 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: required 3 | 4 | go: 5 | - 1.10.x 6 | 7 | before_install: 8 | - go get -u github.com/haya14busa/goverage 9 | 10 | script: 11 | - make build 12 | - ./cover.sh 13 | 14 | notifications: 15 | email: 16 | on_success: never 17 | 18 | after_success: 19 | - bash <(curl -s https://codecov.io/bash) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chyroc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | build: 4 | CGO_ENABLED=0 go build -o ./bin/leetcode-badge github.com/Chyroc/leetcode-badge 5 | 6 | linux_build: 7 | GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o ./bin/leetcode-badge github.com/Chyroc/leetcode-badge 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Leetcode Badge 2 | 3 | [![Build Status](https://travis-ci.org/Chyroc/leetcode-badge.svg?branch=master)](https://travis-ci.org/Chyroc/leetcode-badge) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/Chyroc/leetcode-badge)](https://goreportcard.com/report/github.com/Chyroc/leetcode-badge) 5 | [![codecov](https://codecov.io/gh/Chyroc/leetcode-badge/branch/master/graph/badge.svg)](https://codecov.io/gh/Chyroc/leetcode-badge) 6 | 7 | [![leetcode badge](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Ranking-{{.ranking}}-green.svg&refresh=true)](https://github.com/Chyroc/leetcode-badge) 8 | [![leetcode badge](https://leetcode-badge.chyroc.cn/?name=chyroc&refresh=true)](https://github.com/Chyroc/leetcode-badge) 9 | [![leetcode badge](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Question-{{.solved_question_rate}}-{{%20if%20le%20.solved_question_rate_float%200.3}}red{{%20else%20if%20le%20.solved_question_rate_float%200.6}}yellow{{%20else%20}}green{{%20end%20}}.svg&refresh=true)](https://github.com/Chyroc/leetcode-badge) 10 | [![leetcode badge](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Submission-{{.accepted_submission_rate}}-{{%20if%20le%20.accepted_submission_rate_float%200.3}}red{{%20else%20if%20le%20.solved_question_rate_float%200.6}}yellow{{%20else%20}}green{{%20end%20}}.svg&refresh=true)](https://github.com/Chyroc/leetcode-badge) 11 | 12 | [English Document](./README_en.md) 13 | 14 | Leetcode Badge是一个展示leetcode通过情况徽标的项目。 15 | 16 | svg绘制依赖于[shields.io](http://shields.io/),所以任何shields.io支持的语法,这里都适用。 17 | 18 | ## 示例 19 | 20 | * 默认风格 ![](https://leetcode-badge.chyroc.cn/?name=chyroc&refresh=true) 21 | > `https://leetcode-badge.chyroc.cn/?name=chyroc` 22 | 23 | > 注意:这里的颜色是会变化的 红(低于等于30%),黄(低于等于60%),绿(其他) 24 | 25 | * 排名 ![leetcode badge](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Ranking-{{.ranking}}-green.svg&refresh=true) 26 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Ranking-{{.ranking}}-green.svg` 27 | 28 | * 通过题目/总题目数 ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-green.svg&refresh=true) 29 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-green.svg` 30 | 31 | * 通过的提交/总提交数 ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Accepted/Total-{{.accepted_submission}}/{{.all_submission}}-green.svg&refresh=true) 32 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Accepted/Total-{{.accepted_submission}}/{{.all_submission}}-green.svg` 33 | 34 | * 通过题目/总题目数 + 自定义的style ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-green.svg?style=flat-square&refresh=true) 35 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-green.svg?style=flat-square` 36 | 37 | * 通过题目/总题目数 + 自定义的颜色 ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-red.svg&refresh=true) 38 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-red.svg` 39 | 40 | * 计算不同的比例显示不同的颜色 ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Solved/Total-{{.solved_question}}/{{.all_question}}-{{if%20le%20.solved_question_rate_float%200.3}}red.svg{{else%20if%20le%20.solved_question_rate_float%200.6}}yellow.svg{{else}}green.svg{{end}}&refresh=true) 41 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode | Solved/Total-{{.solved_question}}/{{.all_question}}-{{ if le .solved_question_rate_float 0.3}}red.svg{{ else if le .solved_question_rate_float 0.6}}yellow.svg{{ else }}green.svg{{ end }}` 42 | 43 | > 注意:这里的颜色是会变化的 红(低于等于30%),黄(低于等于60%),绿(其他) 44 | 45 | * 通过题目/总题目 比例 ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Question-{{.solved_question_rate}}-{{%20if%20le%20.solved_question_rate_float%200.3}}red{{%20else%20if%20le%20.solved_question_rate_float%200.6}}yellow{{%20else%20}}green{{%20end%20}}.svg&refresh=true) 46 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode | Question-{{.solved_question_rate}}-{{ if le .solved_question_rate_float 0.3}}red{{ else if le .solved_question_rate_float 0.6}}yellow{{ else }}green{{ end }}.svg` 47 | 48 | * 通过的提交/总提交数 比例 ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Submission-{{.accepted_submission_rate}}-{{%20if%20le%20.accepted_submission_rate_float%200.3}}red{{%20else%20if%20le%20.solved_question_rate_float%200.6}}yellow{{%20else%20}}green{{%20end%20}}.svg&refresh=true) 49 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode | Submission-{{.accepted_submission_rate}}-{{ if le .accepted_submission_rate_float 0.3}}red{{ else if le .solved_question_rate_float 0.6}}yellow{{ else }}green{{ end }}.svg` 50 | 51 | 52 | 53 | ## 语法 54 | 55 | ### 使用querystring传递参数 56 | * name:leetcode用户名 57 | * leetcode_badge_style:自定义的badge显示格式 58 | 59 | ### 使用go的模板作为leetcode_badge_style语法 60 | 61 | #### go的模板语法 62 | 63 | * 参考:https://godoc.org/text/template 64 | * 简述 65 | * `{{ .xxx }}`可以引用下面的6个变量 66 | * `{{ le .xx 0.3 }} a {{ else if le 0.6 }} b {{ else }} c` xx小于等于0.3返回a,小于等于0.6返回b,否则返回c 67 | 68 | #### 可以使用go的模板语法使用6个变量: 69 | * {{.ranking}} 排名(整数) 70 | * {{.accepted_submission}} 通过的提交的个数(整数) 71 | * {{.all_submission}} 所有的提交的个数(整数) 72 | * {{.solved_question}} 通过的题目的个数(整数) 73 | * {{.all_question}} 所有的题目的个数(整数) 74 | * {{.solved_question_rate_float}} 通过的题目占总题目数的比例(小数) 75 | * {{.accepted_submission_rate_float}} 提交通过的占总提交数的比例(小数) 76 | * {{.solved_question_rate}} 通过的题目占总题目数的比例(形如23%的字符串) 77 | * {{.accepted_submission_rate}} 提交通过的占总提交数的比例(形如23%的字符串) 78 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | # Leetcode Badge 2 | 3 | [![leetcode badge](https://leetcode-badge.chyroc.cn/?name=chyroc&refresh=true)](https://github.com/Chyroc/leetcode-badge) 4 | [![leetcode badge](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Question-{{.solved_question_rate}}-{{%20if%20le%20.solved_question_rate_float%200.3}}red{{%20else%20if%20le%20.solved_question_rate_float%200.6}}yellow{{%20else%20}}green{{%20end%20}}.svg&refresh=true)](https://github.com/Chyroc/leetcode-badge) 5 | [![leetcode badge](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Submission-{{.accepted_submission_rate}}-{{%20if%20le%20.accepted_submission_rate_float%200.3}}red{{%20else%20if%20le%20.solved_question_rate_float%200.6}}yellow{{%20else%20}}green{{%20end%20}}.svg&refresh=true)](https://github.com/Chyroc/leetcode-badge) 6 | 7 | [中文文档](./README.md) 8 | 9 | Leetcode Badge is a project that showcases the leetcode pass situation logo. 10 | 11 | Svg drawing relies on [shields.io](http://shields.io/), so any shields.io supported syntax is applicable here. 12 | 13 | ## Example 14 | 15 | * Default style ![](https://leetcode-badge.chyroc.cn/?name=chyroc&refresh=true) 16 | > `https://leetcode-badge.chyroc.cn/?name=chyroc` 17 | 18 | > Note: The color here is red (less than or equal to 30%), yellow (less than or equal to 60%), green (other) 19 | 20 | * Pass the title/total number of topics ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{solve_question}}/{{all_question}}-green.svg&refresh=true) 21 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-green.svg` 22 | 23 | * Number of submitted/total submissions ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Accepted/Total-{{accepted_submission}}/{{all_submission}}-green.svg&refresh=true) 24 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Accepted/Total-{{accepted_submission}}/{{.all_submission}}-green.svg` 25 | 26 | * Pass the title/total number of topics + custom style ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-green.svg?style=flat-square&refresh=true) 27 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-green.svg?style=flat-square` 28 | 29 | * Pass the title/total number of topics + custom colors ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-red.svg&refresh=true) 30 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Solved/Total-{{.solved_question}}/{{.all_question}}-red.svg` 31 | 32 | * Calculate different proportions to show different colors ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Solved/Total-{{solve_question}}/{{.all_question}}-{{if%20le%20.solved_question_rate_float%200.3}}red.svg{{else%20if%20le%20.solved_question_rate_float%200.6}}yellow.svg{{else}}green.svg{{end}}&refresh=true) 33 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode | Solved/Total-{{.solved_question}}/{{.all_question}}-{{ if le .solved_question_rate_float 0.3}}red. Svg{{ else if le .solved_question_rate_float 0.6}}yellow.svg{{ else }}green.svg{{ end }}` 34 | 35 | > Note: The color here is red (less than or equal to 30%), yellow (less than or equal to 60%), green (other) 36 | 37 | * Pass the title/total title ratio ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Question-{{.solved_question_rate}}-{{%20if%20le%20.solved_question_rate_float%200.3}}red{{%20else%20if%20le%20.solved_question_rate_float%200.6}}yellow{{%20else%20}}green{{%20end%20}}.svg&refresh=true) 38 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode | Question-{{.solved_question_rate}}-{{ if le .solved_question_rate_float 0.3}}red{{ else if le .solved_question_rate_float 0.6}} Yellow{{ else }}green{{ end }}.svg` 39 | 40 | * Proportion of submitted/total submissions ![](https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode%20|%20Submission-{{.accepted_submission_rate}}-{{%20if%20le%20.accepted_submission_rate_float%200.3}}red{{%20else%20if%20le%20.solved_question_rate_float%200.6}}yellow{{%20else%20}}green{{%20end%20}}.svg&refresh=true) 41 | > `https://leetcode-badge.chyroc.cn/?name=chyroc&leetcode_badge_style=Leetcode | Submission-{{.accepted_submission_rate}}-{{ if le .accepted_submission_rate_float 0.3}}red{{ else if le .solved_question_rate_float 0.6}} Yellow{{ else }}green{{ end }}.svg` 42 | 43 | ## Syntax 44 | 45 | ### Using querystring to pass parameters 46 | * name: leetcode username 47 | * leetcode_badge_style: custom badge display format 48 | 49 | ### Using go's template as the leetcode_badge_style syntax 50 | 51 | #### go's template syntax 52 | 53 | * Reference: https://godoc.org/text/template 54 | * Brief description 55 | * `{{ .xxx }}` can reference the following 6 variables 56 | * `{{ le .xx 0.3 }} a {{ else if le 0.6 }} b {{else }} c` xx is less than or equal to 0.3 returns a, less than or equal to 0.6 returns b, otherwise returns c 57 | 58 | #### You can use go's template syntax to use 6 variables: 59 | * {{.accepted_submission}} Number of submitted submissions (integer) 60 | * {{.all_submission}} The number of all submissions (integer) 61 | * {{.solved_question}} Number of questions passed (integer) 62 | * {{.all_question}} The number of all questions (integer) 63 | * {{.solved_question_rate_float}} Proportion of the total number of questions passed by the topic (decimal) 64 | * {{.accepted_submission_rate_float}} Proportion of total submissions submitted (decimal) 65 | * {{.solved_question_rate}} The percentage of questions passed (as a string of 23%) 66 | * {{.accepted_submission_rate}} Proportion of submissions passed (as a string of 23%) -------------------------------------------------------------------------------- /cover.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.txt 5 | 6 | for d in $(go list ./... | grep -v vendor | grep -v ttt); do 7 | go test -race -coverprofile=profile.out -covermode=atomic "$d" 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.txt 10 | rm profile.out 11 | fi 12 | done -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/chyroc/leetcode-badge 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.7.1 7 | github.com/gin-contrib/cors v1.3.1 8 | github.com/gin-gonic/gin v1.7.7 9 | github.com/go-playground/validator/v10 v10.9.0 // indirect 10 | github.com/golang/protobuf v1.5.2 // indirect 11 | github.com/json-iterator/go v1.1.11 // indirect 12 | github.com/mattn/go-isatty v0.0.13 // indirect 13 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 14 | github.com/modern-go/reflect2 v1.0.1 // indirect 15 | github.com/patrickmn/go-cache v2.1.0+incompatible 16 | github.com/pmezard/go-difflib v1.0.0 // indirect 17 | github.com/stretchr/testify v1.7.0 18 | github.com/ugorji/go v1.2.6 // indirect 19 | github.com/yuin/goldmark v1.4.13 // indirect 20 | golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b // indirect 21 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect 22 | golang.org/x/term v0.1.0 // indirect 23 | golang.org/x/text v0.3.8 // indirect 24 | google.golang.org/protobuf v1.27.1 // indirect 25 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect 26 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect 27 | gopkg.in/yaml.v2 v2.4.0 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/PuerkitoBio/goquery v1.4.0 h1:13fV4AYmaSopdNp8KWDUlLyU5INklBkYk0tsTfxRO2U= 2 | github.com/PuerkitoBio/goquery v1.4.0/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA= 3 | github.com/PuerkitoBio/goquery v1.7.1 h1:oE+T06D+1T7LNrn91B4aERsRIeCLJ/oPSa6xB9FPnz4= 4 | github.com/PuerkitoBio/goquery v1.7.1/go.mod h1:XY0pP4kfraEmmV1O7Uf6XyjoslwsneBbgeDjLYuN8xY= 5 | github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= 6 | github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 7 | github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE= 8 | github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY= 9 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 10 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 11 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/gin-contrib/cors v0.0.0-20170318125340-cf4846e6a636 h1:oGgJA7DJphAc81EMHZ+2G7Ai2xyg5eoq7bbqzCsiWFc= 14 | github.com/gin-contrib/cors v0.0.0-20170318125340-cf4846e6a636/go.mod h1:cw+u9IsAkC16e42NtYYVCLsHYXE98nB3M7Dr9mLSeH4= 15 | github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA= 16 | github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk= 17 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= 18 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= 19 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 20 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 21 | github.com/gin-gonic/gin v1.1.5-0.20170702092826-d459835d2b07 h1:Gm6bjW5SQ/sIib9Zcgyyw5chSE6SLcgVZIflI0qGI6s= 22 | github.com/gin-gonic/gin v1.1.5-0.20170702092826-d459835d2b07/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= 23 | github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= 24 | github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM= 25 | github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 26 | github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= 27 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 28 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 29 | github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= 30 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 31 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 32 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 33 | github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= 34 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 35 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 36 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 37 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 38 | github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= 39 | github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= 40 | github.com/golang/protobuf v1.1.0 h1:0iH4Ffd/meGoXqF2lSAhZHt8X+cPgkfn/cb6Cce5Vpc= 41 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 42 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 44 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 45 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 46 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 47 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 48 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 49 | github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 50 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 51 | github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= 52 | github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 53 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 54 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 55 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 56 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 57 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 58 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 59 | github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= 60 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 61 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 62 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 63 | github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= 64 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 65 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 66 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 67 | github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= 68 | github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 69 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 70 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 71 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 72 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 73 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 74 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 75 | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= 76 | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= 77 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 78 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 79 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 80 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 81 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 82 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 83 | github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= 84 | github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 85 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 86 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 87 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 88 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 89 | github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w= 90 | github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= 91 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 92 | github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= 93 | github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= 94 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 95 | github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ= 96 | github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= 97 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 98 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 99 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 100 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 101 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= 102 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 103 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 104 | golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b h1:Qwe1rC8PSniVfAFPFJeyUkB+zcysC3RgJBAGk7eqBEU= 105 | golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 106 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 107 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 108 | golang.org/x/net v0.0.0-20180522190444-9ef9f5bb98a1 h1:+2KJRQYVS4oeIZqXrgNLH7FxyXp+eVs2kLuG5pWupfY= 109 | golang.org/x/net v0.0.0-20180522190444-9ef9f5bb98a1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 110 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 111 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 112 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 113 | golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 114 | golang.org/x/net v0.0.0-20210825183410-e898025ed96a h1:bRuuGXV8wwSdGTB+CtJf+FjgO1APK1CoO39T4BN/XBw= 115 | golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 116 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 117 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 118 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= 119 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 120 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 121 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= 122 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 123 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 124 | golang.org/x/sys v0.0.0-20180522224204-88eb85aaee56 h1:ALdc3t+jAqSs6+pzVEioBeI8YtMICgYVFhEy6P69czc= 125 | golang.org/x/sys v0.0.0-20180522224204-88eb85aaee56/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 126 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 127 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 128 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 129 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 130 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 131 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 132 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 133 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 134 | golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf h1:2ucpDCmfkl8Bd/FsLtiD653Wf96cW37s+iGx93zsu4k= 135 | golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 136 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0= 137 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 138 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 139 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 140 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 141 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 142 | golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 143 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 144 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 145 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 146 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 147 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 148 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 149 | golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= 150 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 151 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 152 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 153 | golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= 154 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 155 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 156 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 157 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 158 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 159 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 160 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 161 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 162 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 163 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 164 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 165 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 166 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= 167 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= 168 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= 169 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= 170 | gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= 171 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 172 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 173 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 174 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 175 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 176 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 177 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 178 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 179 | -------------------------------------------------------------------------------- /internal/app.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "github.com/gin-contrib/cors" 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func APP() *gin.Engine { 10 | fmt.Printf("config %#v\n", Conf) 11 | if Conf.Release { 12 | gin.SetMode(gin.ReleaseMode) 13 | } 14 | app := gin.Default() 15 | app.Use(cors.Default()) 16 | 17 | app.GET("", func(c *gin.Context) { 18 | name := c.Query("name") 19 | if name == "" { 20 | c.String(400, "name is empty") 21 | return 22 | } 23 | 24 | logVisitor(name) 25 | 26 | leetcodeData, err := fetchLeetcodeData(name) 27 | if err != nil { 28 | c.String(400, err.Error()) 29 | return 30 | } else if leetcodeData == nil { 31 | c.String(400, "fetch leetcode data of account: %s, result: nil", name) 32 | return 33 | } 34 | 35 | shieldsData, err := fetchShieldsData(c.Query("leetcode_badge_style"), leetcodeData) 36 | if err != nil { 37 | c.String(400, err.Error()) 38 | return 39 | } 40 | 41 | c.Writer.WriteHeader(200) 42 | c.Writer.Header().Add("Content-Type", "image/svg+xml; charset=utf-8") 43 | c.Writer.Header().Add("Access-Control-Expose-Headers", "Content-Type, Cache-Control, Expires") 44 | c.Writer.Header().Add("Cache-Control", "no-cache, no-store, must-revalidate, max-age=0") 45 | c.Writer.Header().Add("Expires", "0") 46 | c.Writer.Header().Add("Pragma", "no-cache") 47 | c.Writer.WriteString(shieldsData) 48 | 49 | return 50 | }) 51 | 52 | return app 53 | } 54 | -------------------------------------------------------------------------------- /internal/cache.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "github.com/patrickmn/go-cache" 5 | ) 6 | 7 | var defaultCache *cache.Cache 8 | 9 | func cacheGetLeetcode(key string) (*LeetcodeData, bool) { 10 | if defaultCache == nil { 11 | return nil, false 12 | } 13 | data, ok := defaultCache.Get("Leetcode" + key) 14 | if !ok { 15 | return nil, false 16 | } 17 | 18 | r, ok := data.(*LeetcodeData) 19 | if !ok || r == nil { 20 | return nil, false 21 | } 22 | 23 | return r, true 24 | } 25 | 26 | func cacheSetLeetcode(key string, r *LeetcodeData) { 27 | if defaultCache != nil { 28 | defaultCache.Set("Leetcode"+key, r, Conf.CacheTTL) 29 | } 30 | } 31 | 32 | func cacheGetShields(key string) (string, bool) { 33 | if defaultCache == nil { 34 | return "", false 35 | } 36 | data, ok := defaultCache.Get("Shields" + key) 37 | if !ok { 38 | return "", false 39 | } 40 | 41 | r, ok := data.(string) 42 | if !ok || r == "" { 43 | return "", false 44 | } 45 | 46 | return r, true 47 | } 48 | 49 | func cacheSetShields(key string, r string) { 50 | if defaultCache != nil { 51 | defaultCache.Set("Shields"+key, r, cache.NoExpiration) // shields svg key相同,返回的数据一定相同 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /internal/config.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | "github.com/patrickmn/go-cache" 10 | ) 11 | 12 | var Conf = new(config) 13 | 14 | type config struct { 15 | Port int 16 | Cache bool 17 | CacheTTL time.Duration 18 | Release bool 19 | LogPath string 20 | } 21 | 22 | func init() { 23 | Conf = &config{ 24 | Port: 9090, 25 | Cache: false, 26 | CacheTTL: time.Hour * 2, 27 | } 28 | } 29 | 30 | func InitConfig() error { 31 | if Conf.Cache { 32 | defaultCache = cache.New(Conf.CacheTTL, Conf.CacheTTL*2) 33 | } 34 | 35 | if Conf.LogPath != "" { 36 | f, err := os.OpenFile(Conf.LogPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "open logfile err: %v\n", err) 39 | return nil 40 | } 41 | defaultLoger = log.New(f, "nopre", log.LstdFlags) 42 | } 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /internal/helper.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "net/http" 7 | "text/template" 8 | ) 9 | 10 | func parseTmpl(tmpl string, data map[string]interface{}) ([]byte, error) { 11 | parsedTmpl, err := template.New("tmpl").Parse(tmpl) 12 | if err != nil { 13 | return nil, err 14 | } 15 | 16 | var result bytes.Buffer 17 | if err := parsedTmpl.Execute(&result, data); err != nil { 18 | return nil, err 19 | } 20 | 21 | return result.Bytes(), nil 22 | } 23 | 24 | func requestGet(url string) ([]byte, error) { 25 | resp, err := http.Get(url) 26 | if err != nil { 27 | return nil, err 28 | } 29 | defer resp.Body.Close() 30 | 31 | b, err := ioutil.ReadAll(resp.Body) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return b, nil 37 | } 38 | -------------------------------------------------------------------------------- /internal/internal_test.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | func initEnv(t *testing.T) *assert.Assertions { 12 | Conf = &config{Cache: true, CacheTTL: time.Minute, Release: false} 13 | InitConfig() 14 | return assert.New(t) 15 | } 16 | 17 | func TestParseTmpl(t *testing.T) { 18 | as := initEnv(t) 19 | var data = make(map[string]interface{}) 20 | 21 | data["solved_question"] = 1 22 | data["all_question"] = 2 23 | tmpl := `Leetcode | Solved/Total-{{.solved_question}}/{{.all_question}}-{{ if le .solved_question_rate_float 0.3}}red.svg{{ else if le .solved_question_rate_float 0.6}}yellow.svg{{ else }}green.svg{{ end }}` 24 | 25 | { 26 | data["solved_question_rate_float"] = 0.3 27 | x, err := parseTmpl(tmpl, data) 28 | as.Nil(err) 29 | as.Equal("Leetcode | Solved/Total-1/2-red.svg", string(x)) 30 | } 31 | { 32 | data["solved_question_rate_float"] = 0.6 33 | x, err := parseTmpl(tmpl, data) 34 | as.Nil(err) 35 | as.Equal("Leetcode | Solved/Total-1/2-yellow.svg", string(x)) 36 | } 37 | { 38 | data["solved_question_rate_float"] = 0.9 39 | x, err := parseTmpl(tmpl, data) 40 | as.Nil(err) 41 | as.Equal("Leetcode | Solved/Total-1/2-green.svg", string(x)) 42 | } 43 | } 44 | 45 | func TestCache(t *testing.T) { 46 | as := initEnv(t) 47 | 48 | { 49 | key := "test" + strconv.Itoa(int(time.Now().Unix())) 50 | cacheSetLeetcode(key, &LeetcodeData{AllQuestion: 1000}) 51 | r, ok := cacheGetLeetcode(key) 52 | as.True(ok) 53 | as.NotNil(r) 54 | as.Equal(1000, r.AllQuestion) 55 | } 56 | 57 | { 58 | key := "test" + strconv.Itoa(int(time.Now().Unix())) 59 | cacheSetShields(key, "test 2") 60 | r, ok := cacheGetShields(key) 61 | as.True(ok) 62 | as.Equal("test 2", r) 63 | } 64 | } 65 | 66 | func TestLeetcode(t *testing.T) { 67 | as := initEnv(t) 68 | var r *LeetcodeData 69 | var err error 70 | 71 | { 72 | r, err = fetchLeetcodeData("chyroc") 73 | as.Nil(err) 74 | as.NotNil(r) 75 | 76 | // 下面的数据是已经验证的 77 | as.True(r.SolvedQuestion >= 80) 78 | as.True(r.AllQuestion >= 500) 79 | as.NotEmpty(r.SolvedQuestionRate) 80 | as.True(r.SolvedQuestionRateFloat > 0.1) 81 | 82 | as.True(r.AcceptedSubmission >= 100) 83 | as.True(r.AllSubmission >= 200) 84 | as.NotEmpty(r.AcceptedSubmissionRate) 85 | as.True(r.AcceptedSubmissionRateFloat >= 0.1) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /internal/leetcode.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/PuerkitoBio/goquery" 11 | ) 12 | 13 | var reRanking = regexp.MustCompile(`(?m)pc\.init\(([\s\S]*?){`) 14 | 15 | type LeetcodeData struct { 16 | SolvedQuestion int `json:"solved_question"` 17 | AllQuestion int `json:"all_question"` 18 | AcceptedSubmission int `json:"accepted_submission"` 19 | AllSubmission int `json:"all_submission"` 20 | SolvedQuestionRateFloat float64 `json:"solved_question_rate_float"` 21 | AcceptedSubmissionRateFloat float64 `json:"accepted_submission_rate_float"` 22 | SolvedQuestionRate string `json:"solved_question_rate"` 23 | AcceptedSubmissionRate string `json:"accepted_submission_rate"` 24 | Ranking int `json:"ranking"` 25 | } 26 | 27 | func (r *LeetcodeData) Dump() map[string]interface{} { 28 | return map[string]interface{}{ 29 | "solved_question": r.SolvedQuestion, 30 | "all_question": r.AllQuestion, 31 | "accepted_submission": r.AcceptedSubmission, 32 | "all_submission": r.AllSubmission, 33 | "solved_question_rate_float": r.SolvedQuestionRateFloat, 34 | "accepted_submission_rate_float": r.AcceptedSubmissionRateFloat, 35 | "solved_question_rate": r.SolvedQuestionRate, 36 | "accepted_submission_rate": r.AcceptedSubmissionRate, 37 | "ranking": r.Ranking, 38 | } 39 | } 40 | 41 | func analysis(selection *goquery.Selection, data *LeetcodeData) (err error) { 42 | var solvedQuestion = "Solved Question" 43 | var acceptedSubmission = "Accepted Submission" 44 | keys := []string{solvedQuestion, acceptedSubmission} 45 | for _, key := range keys { 46 | if strings.Contains(selection.Text(), key) { 47 | span := selection.Find("span").Text() 48 | span = strings.Replace(span, " ", "", -1) 49 | span = strings.Replace(span, "\n", "", -1) 50 | 51 | splited := strings.Split(span, "/") 52 | if len(splited) != 2 { 53 | return 54 | } 55 | 56 | if key == solvedQuestion { 57 | solved, err := strconv.Atoi(splited[0]) 58 | if err != nil { 59 | return err 60 | } 61 | data.SolvedQuestion = solved 62 | 63 | all, err := strconv.Atoi(splited[1]) 64 | if err != nil { 65 | return err 66 | } 67 | data.AllQuestion = all 68 | } else if key == acceptedSubmission { 69 | accepted, err := strconv.Atoi(splited[0]) 70 | if err != nil { 71 | return err 72 | } 73 | data.AcceptedSubmission = accepted 74 | 75 | all, err := strconv.Atoi(splited[1]) 76 | if err != nil { 77 | return err 78 | } 79 | data.AllSubmission = all 80 | } 81 | return 82 | } 83 | } 84 | 85 | return 86 | } 87 | 88 | func getRanking(html string) (int, error) { 89 | x := reRanking.FindStringSubmatch(html) 90 | if len(x) != 2 { 91 | return 0, fmt.Errorf("get ranking length invalid") 92 | } 93 | 94 | l := strings.Replace(x[1], "\n", "", -1) 95 | l = strings.Replace(l, " ", "", -1) 96 | ll := strings.Split(l, ",") 97 | if len(ll) < 7 { 98 | return 0, fmt.Errorf("get ranking length invalid") 99 | } 100 | 101 | return strconv.Atoi(strings.Replace(ll[6], "'", "", -1)) 102 | } 103 | 104 | func fetchLeetcodeData(name string) (*LeetcodeData, error) { 105 | r, ok := cacheGetLeetcode(name) 106 | if ok { 107 | return r, nil 108 | } 109 | 110 | b, err := requestGet(fmt.Sprintf("https://leetcode.com/%s/", name)) 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | doc, err := goquery.NewDocumentFromReader(bytes.NewReader(b)) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | var data = new(LeetcodeData) 121 | doc.Find(".list-group-item").Each(func(i int, selection *goquery.Selection) { 122 | if err2 := analysis(selection, data); err2 != nil { 123 | err = err2 124 | } 125 | }) 126 | if err != nil { 127 | return nil, err 128 | } 129 | 130 | ranking, err := getRanking(string(b)) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | data.SolvedQuestionRateFloat = float64(data.SolvedQuestion) / float64(data.AllQuestion) 136 | data.AcceptedSubmissionRateFloat = float64(data.AcceptedSubmission) / float64(data.AllSubmission) 137 | data.SolvedQuestionRate = fmt.Sprintf("%.0f%", data.SolvedQuestionRateFloat*100) 138 | data.AcceptedSubmissionRate = fmt.Sprintf("%.0f%", data.AcceptedSubmissionRateFloat*100) 139 | data.Ranking = ranking 140 | 141 | cacheSetLeetcode(name, data) 142 | 143 | return data, nil 144 | } 145 | -------------------------------------------------------------------------------- /internal/log.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | var defaultLoger *log.Logger 8 | 9 | func logVisitor(msg string) { 10 | if defaultLoger != nil { 11 | defaultLoger.SetPrefix("visitor ") 12 | defaultLoger.Printf(msg) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /internal/shields.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | ) 7 | 8 | func fetchShieldsData(style string, leetcodeData *LeetcodeData) (string, error) { 9 | if style == "" { 10 | style = `Leetcode | Solved/Total-{{.solved_question}}/{{.all_question}}-{{ if le .solved_question_rate_float 0.3}}red.svg{{ else if le .solved_question_rate_float 0.6}}yellow.svg{{ else }}green.svg{{ end }}` 11 | } 12 | 13 | style, err := url.QueryUnescape(style) 14 | if err != nil { 15 | return "", err 16 | } 17 | 18 | newUrl, err := parseTmpl(style, leetcodeData.Dump()) 19 | if err != nil { 20 | return "", err 21 | } 22 | 23 | r, ok := cacheGetShields(string(newUrl)) 24 | if ok { 25 | return r, nil 26 | } 27 | 28 | b, err := requestGet(fmt.Sprintf("https://img.shields.io/badge/%s", newUrl)) 29 | if err != nil { 30 | return "", err 31 | } 32 | 33 | cacheSetShields(string(newUrl), string(b)) 34 | 35 | return string(b), nil 36 | } 37 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/chyroc/leetcode-badge/internal" 8 | ) 9 | 10 | func init() { 11 | flag.IntVar(&internal.Conf.Port, "port", 9090, "port") 12 | flag.BoolVar(&internal.Conf.Cache, "cache", false, "use cache") 13 | flag.BoolVar(&internal.Conf.Release, "release", false, "release or debug") 14 | flag.StringVar(&internal.Conf.LogPath, "log", "", "log path") 15 | flag.Parse() 16 | } 17 | 18 | func main() { 19 | err := internal.InitConfig() 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | app := internal.APP() 25 | app.Run(fmt.Sprintf(":%d", internal.Conf.Port)) 26 | } 27 | --------------------------------------------------------------------------------