Skip to content

Commit

Permalink
feat: display today streak counter (#305)
Browse files Browse the repository at this point in the history
* feat: display today streak counter

* fix streak query
  • Loading branch information
j178 committed Aug 11, 2024
1 parent 2e85ee7 commit 2c09036
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 21 deletions.
2 changes: 1 addition & 1 deletion cmd/contest.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func selectUpcomingContest(c leetcode.Client, registeredOnly bool) (string, erro
for i, ct := range contestList {
mark := " "
if ct.Registered {
mark = ""
mark = ""
}
contestNames[i] = fmt.Sprintf(
"%s %s at %s",
Expand Down
19 changes: 19 additions & 0 deletions cmd/submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cmd

import (
"fmt"
"strconv"
"strings"

"github.com/charmbracelet/log"
Expand Down Expand Up @@ -63,6 +64,11 @@ leetgo submit w330/
}
}

err = showTodayStreak(c, cmd)
if err != nil {
log.Debug("failed to show today's streak", "err", err)
}

if hasFailedCase {
return exitCode(1)
}
Expand Down Expand Up @@ -145,3 +151,16 @@ func appendToTestCases(q *leetcode.QuestionData, result *leetcode.SubmitCheckRes
err = utils.WriteFile(testCasesFile.GetPath(), content)
return true, err
}

func showTodayStreak(c leetcode.Client, cmd *cobra.Command) error {
streak, err := c.GetStreakCounter()
if err != nil {
return err
}
today := ""
if streak.TodayCompleted {
today = config.PassedStyle.Render("+1")
}
cmd.Printf("\nTotal streak: %s%s\n", strconv.Itoa(streak.StreakCount-1), today)
return nil
}
13 changes: 11 additions & 2 deletions cmd/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ leetgo test w330/`,
submitLimiter := newLimiter(user)

var hasFailedCase bool
var hasSubmitted bool
for _, q := range qs {
var (
localPassed = true
Expand Down Expand Up @@ -115,6 +116,7 @@ leetgo test w330/`,

if autoSubmit && remotePassed && (localPassed || forceSubmit) {
log.Info("submitting solution", "user", user.Whoami(c))
hasSubmitted = true
result, err := submitSolution(cmd, q, c, gen, submitLimiter)
if err != nil {
submitAccepted = false
Expand All @@ -125,7 +127,7 @@ leetgo test w330/`,
submitAccepted = false
added, _ := appendToTestCases(q, result)
if added {
log.Info("added failed case to testcases.txt")
log.Info("added failed cases to `testcases.txt`")
}
}
}
Expand All @@ -136,6 +138,13 @@ leetgo test w330/`,
}
}

if hasSubmitted {
err = showTodayStreak(c, cmd)
if err != nil {
log.Debug("failed to show today's streak", "err", err)
}
}

if hasFailedCase {
return exitCode(1)
}
Expand Down Expand Up @@ -213,7 +222,7 @@ func runTestRemotely(
if err != nil {
log.Debug("failed to update test cases", "err", err)
} else {
log.Info("testcases.txt updated")
log.Info("`testcases.txt` updated")
}
}
}
Expand Down
41 changes: 37 additions & 4 deletions leetcode/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func (e UnexpectedStatusCode) IsError() bool {

func (e UnexpectedStatusCode) Error() string {
body := "<empty body>"
if len(e.Body) > 100 {
body = e.Body[:100] + "..."
if len(e.Body) > 500 {
body = e.Body[:500] + "..."
}
return fmt.Sprintf("[%d %s] %s", e.Code, http.StatusText(e.Code), body)
}
Expand Down Expand Up @@ -81,6 +81,7 @@ type Client interface {
GetContestQuestionData(contestSlug string, questionSlug string) (*QuestionData, error)
RegisterContest(slug string) error
UnregisterContest(slug string) error
GetStreakCounter() (StreakCounter, error)
}

type cnClient struct {
Expand Down Expand Up @@ -162,6 +163,7 @@ func nonFollowRedirect(req *http.Request, via []*http.Request) error {
}

type graphqlRequest struct {
path string
query string
operationName string
variables map[string]any
Expand All @@ -178,6 +180,7 @@ const (

const (
graphQLPath = "/graphql"
graphQLNoj = "/graphql/noj-go/"
accountLoginPath = "/accounts/login/"
contestInfoPath = "/contest/api/info/%s/"
contestProblemsPath = "/contest/%s/problems/%s/"
Expand Down Expand Up @@ -265,7 +268,11 @@ func (c *cnClient) graphqlGet(req graphqlRequest, result any) (*http.Response, e
v, _ := json.Marshal(req.variables)
p.Variables = string(v)
}
r, err := c.http.New().Get(graphQLPath).QueryStruct(p).Request()
path := graphQLPath
if req.path != "" {
path = req.path
}
r, err := c.http.New().Get(path).QueryStruct(p).Request()
if err != nil {
return nil, err
}
Expand All @@ -282,7 +289,11 @@ func (c *cnClient) graphqlPost(req graphqlRequest, result any) (*http.Response,
"operationName": req.operationName,
"variables": v,
}
r, err := c.http.New().Post(graphQLPath).BodyJSON(body).Request()
path := graphQLPath
if req.path != "" {
path = req.path
}
r, err := c.http.New().Post(path).BodyJSON(body).Request()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1012,3 +1023,25 @@ func (c *cnClient) GetQuestionTags() ([]QuestionTag, error) {
}
return tags, nil
}

func (c *cnClient) GetStreakCounter() (StreakCounter, error) {
query := `
query getStreakCounter {
problemsetStreakCounter {
today
streakCount
daysSkipped
todayCompleted
}
}`
var resp gjson.Result
_, err := c.graphqlPost(
graphqlRequest{path: graphQLNoj, query: query, authType: requireAuth}, &resp,
)
if err != nil {
return StreakCounter{}, err
}
var counter StreakCounter
err = json.Unmarshal(utils.StringToBytes(resp.Get("data.problemsetStreakCounter").Raw), &counter)
return counter, err
}
35 changes: 21 additions & 14 deletions leetcode/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,15 @@ func (r *SubmitCheckResult) Display(q *QuestionData) string {
case Accepted:
return fmt.Sprintf(
"\n%s%s%s%s\n",
config.PassedStyle.Render(fmt.Sprintf(" %s\n", r.StatusMsg)),
config.PassedStyle.Render(fmt.Sprintf(" %s\n", r.StatusMsg)),
fmt.Sprintf("\nPassed cases: %d/%d", r.TotalCorrect, r.TotalTestcases),
fmt.Sprintf("\nRuntime: %s, better than %.0f%%", r.StatusRuntime, r.RuntimePercentile),
fmt.Sprintf("\nMemory: %s, better than %.0f%%", r.StatusMemory, r.MemoryPercentile),
)
case WrongAnswer:
return fmt.Sprintf(
"\n%s%s%s%s%s%s\n",
config.FailedStyle.Render(" × Wrong Answer\n"),
config.FailedStyle.Render(" Wrong Answer\n"),
fmt.Sprintf("\nPassed cases: %d/%d", r.TotalCorrect, r.TotalTestcases),
fmt.Sprintf("\nLast case: %s", utils.TruncateString(strings.ReplaceAll(r.LastTestcase, "\n", "↩ "), 100)),
fmt.Sprintf("\nOutput: %s", utils.TruncateString(strings.ReplaceAll(r.CodeOutput, "\n", "↩ "), 100)),
Expand All @@ -105,25 +105,25 @@ func (r *SubmitCheckResult) Display(q *QuestionData) string {
case MemoryLimitExceeded, TimeLimitExceeded, OutputLimitExceeded:
return fmt.Sprintf(
"\n%s%s%s\n",
config.ErrorStyle.Render(fmt.Sprintf(" × %s\n", r.StatusMsg)),
config.ErrorStyle.Render(fmt.Sprintf(" %s\n", r.StatusMsg)),
fmt.Sprintf("\nPassed cases: %d/%d", r.TotalCorrect, r.TotalTestcases),
fmt.Sprintf("\nLast case: %s", utils.TruncateString(r.LastTestcase, 100)),
)
case RuntimeError:
return fmt.Sprintf(
"\n%s%s%s\n",
config.ErrorStyle.Render(fmt.Sprintf(" × %s\n", r.StatusMsg)),
config.ErrorStyle.Render(fmt.Sprintf(" %s\n", r.StatusMsg)),
fmt.Sprintf("\nPassed cases: %s", formatCompare(r.CompareResult)),
"\n"+config.StdoutStyle.Render(r.FullRuntimeError),
)
case CompileError:
return fmt.Sprintf(
"\n%s%s\n",
config.ErrorStyle.Render(fmt.Sprintf(" × %s\n", r.StatusMsg)),
config.ErrorStyle.Render(fmt.Sprintf(" %s\n", r.StatusMsg)),
"\n"+config.StdoutStyle.Render(r.FullCompileError),
)
default:
return config.FailedStyle.Render(fmt.Sprintf("\n × %s\n", r.StatusMsg))
return config.FailedStyle.Render(fmt.Sprintf("\n %s\n", r.StatusMsg))
}
}

Expand Down Expand Up @@ -181,9 +181,9 @@ func formatCompare(s string) string {
var sb strings.Builder
for _, c := range s {
if c == '1' {
sb.WriteString(config.PassedStyle.Render(""))
sb.WriteString(config.PassedStyle.Render(""))
} else {
sb.WriteString(config.ErrorStyle.Render("×"))
sb.WriteString(config.ErrorStyle.Render(""))
}
}
return sb.String()
Expand All @@ -199,7 +199,7 @@ func (r *RunCheckResult) Display(_ *QuestionData) string {
if r.CorrectAnswer {
return fmt.Sprintf(
"\n%s%s%s%s%s%s\n",
config.PassedStyle.Render(fmt.Sprintf(" %s\n", r.StatusMsg)),
config.PassedStyle.Render(fmt.Sprintf(" %s\n", r.StatusMsg)),
fmt.Sprintf("\nPassed cases: %s", formatCompare(r.CompareResult)),
fmt.Sprintf("\nInput: %s", utils.TruncateString(strings.ReplaceAll(r.InputData, "\n", "↩ "), 100)),
fmt.Sprintf("\nOutput: %s", utils.TruncateString(strings.Join(r.CodeAnswer, "↩ "), 100)),
Expand All @@ -209,7 +209,7 @@ func (r *RunCheckResult) Display(_ *QuestionData) string {
} else {
return fmt.Sprintf(
"\n%s%s%s%s%s%s\n",
config.ErrorStyle.Render("\n × Wrong Answer\n"),
config.ErrorStyle.Render("\n Wrong Answer\n"),
fmt.Sprintf("\nPassed cases: %s", formatCompare(r.CompareResult)),
fmt.Sprintf("\nInput: %s", utils.TruncateString(strings.ReplaceAll(r.InputData, "\n", "↩ "), 100)),
fmt.Sprintf("\nOutput: %s", utils.TruncateString(strings.Join(r.CodeAnswer, "↩ "), 100)),
Expand All @@ -218,22 +218,22 @@ func (r *RunCheckResult) Display(_ *QuestionData) string {
)
}
case MemoryLimitExceeded, TimeLimitExceeded, OutputLimitExceeded:
return config.ErrorStyle.Render(fmt.Sprintf("\n × %s\n", r.StatusMsg))
return config.ErrorStyle.Render(fmt.Sprintf("\n %s\n", r.StatusMsg))
case RuntimeError:
return fmt.Sprintf(
"\n%s%s%s\n",
config.ErrorStyle.Render(fmt.Sprintf(" × %s\n", r.StatusMsg)),
config.ErrorStyle.Render(fmt.Sprintf(" %s\n", r.StatusMsg)),
fmt.Sprintf("Passed cases: %s", formatCompare(r.CompareResult)),
"\n"+config.StdoutStyle.Render(r.FullRuntimeError),
)
case CompileError:
return fmt.Sprintf(
"\n%s%s\n",
config.ErrorStyle.Render(fmt.Sprintf(" × %s\n", r.StatusMsg)),
config.ErrorStyle.Render(fmt.Sprintf(" %s\n", r.StatusMsg)),
"\n"+config.StdoutStyle.Render(r.FullCompileError),
)
default:
return config.FailedStyle.Render(fmt.Sprintf("\n × %s\n", r.StatusMsg))
return config.FailedStyle.Render(fmt.Sprintf("\n %s\n", r.StatusMsg))
}
}

Expand All @@ -259,3 +259,10 @@ type QuestionTag struct {
TypeName string `json:"typeName"`
TypeTransName string `json:"typeTransName"`
}

type StreakCounter struct {
Today string `json:"today"`
StreakCount int `json:"streakCount"`
DaysSkipped int `json:"daysSkipped"`
TodayCompleted bool `json:"todayCompleted"`
}

0 comments on commit 2c09036

Please sign in to comment.