TL;DR
You can play leetcode contests on this fork. I played last weekend's contests with this; see the User Experience example.
I want feedback for anything and everything -- code smells, CLI design, even "this sucks and I'm rejecting it".
New feature: Playing with contests in leetcode-cli
This is a short overview of the work I've done to get leetcode contests working in this app. First, I cover the client-server relationship for leetcode contests. Then, I briefly explain the architectural decisions I made in implementation. Finally, I demonstrate what usage of the new leetcode contest
command looks like, and go over possible changes.
The git commit history for this PR is dirty; it might be easier to see the files changed here.
Map of the Leetcode contest API
Contests are stored in the leetcode backend as the ContestNode
type (see next section), which mostly corresponds to the information available at leetcode.com/contest/$slug
. We can retrieve information by querying
- sending a GET request to
https://leetcode.com/contest/api/info/$contest_slug
, or
- querying the graphql API for
{ contest(titleSlug: String!) }
Before a contest begins, users must register for the contest to participate in it. This can be done with a simple empty POST request to
https://leetcode.com/contest/api/$contest_slug/register
Once a contest starts, the client needs to make additional requests to the leetcode API to get information about the contest problems. For normal leetcode.com
users, the information appears to be dumped directly into HTML inside a <script>
tag. I did not want to implement/import a full blown HTML parser || use a horrible string/regex search hack, so I found an alternative:
- Contest problems can be queried from the graphql API like normal problems (i.e. both are accessible via
{ question(titleSlug: String!) }
queries). There are some differences between the data returned for a contest problem and a normal problem, and they will need to be handled later in the CLI's code.
After the user finishes implementing the solution for a problem, they need to submit their code to be judged by the leetcode runtime. A dedicated contest API submission route must be used; running code via the normal API routes "works" but doesn't count for gaining contest points.
https://leetcode.com/contest/api/$contest/problems/$slug/interpret_solution/
https://leetcode.com/contest/api/$contest/problems/$slug/submit/
Users might also want to check the contest scoreboard to see their position. I have not worked on querying/implementing this yet.
Interlude: what's what the fun
command?
Leetcode's API is not documented (at all). I discovered all of the information in the section above by a mixture of the Firefox Dev Console && unsolicited queries to leetcode.com/graphql
. The latter is what the fun
subcommand is for; I made it as a quick debugging tool to enumerate leetcode's graphql API.
As an example, you can get the structure of a ContestQuestionNode
like this:
$ leetcode f -t ContestQuestionNode | jq .
"data": {
"__type": {
"name": "ContestQuestionNode",
"fields": [
{
"name": "credit",
"type": {
"name": null,
"kind": "NON_NULL",
"ofType": {
"name": "Int",
"kind": "SCALAR"
}
}
},
{
"name": "title",
"type": {
"name": "String",
"kind": "SCALAR",
"ofType": null
}
},
{
"name": "titleSlug",
"type": {
"name": "String",
"kind": "SCALAR",
"ofType": null
}
},
{
"name": "questionId",
"type": {
"name": "String",
"kind": "SCALAR",
"ofType": null
}
}
]
}
}
None of this is needed for a normal user of leetcode-cli, of course, so I will probably remove it unless you think it would be a good idea to keep it.
IMPLEMENTATION
So, how does the API translate to code?
We need some way to expose the following operations to the user:
1, get contest info (given a slug, like"weekly-contest-295"
)
2. register for a contest
3. get contset problem info
4. submit code to test/run on contest problems
Because contest problems are structurally identical to normal leetcode problems, (4) is actually already solved -- the code for the test
/exec
commands can be used here, with a little modification. (3) is also mostly solved by the existing code, but there are a few issues:
- The existing code relies on the normal problem url (
conf.sys.urls["problem"]
) to read problem descriptions, so I added a longer graphql query (Leetcode::get_contest_question_detail
) for that
- The
QuestionNode
data from leetcode for each problem will be slightly modified after a contest ends && the contest problems are republished as normal problems. This causes a user's code for a contest problem to "disappear" from leetcode-cli after a contest, because the frontendQuestionId
for each problem changes && the user's code file in ~/.leetcode/code
is no longer tracked properly by leetcode-cli. I have some ideas to handle this, but I haven't done anything about it yet.
That leaves (2) and (1). I added the Contest
and ContestQuestionStub
structs to models.rs
; they represent the ContestNode
and ContestQuestionNode
types from the leetcode graphql API. Corresponding methods were added in leetcode,rs
, cache/mod.rs
, and parser.rs
.
The data for the contest structs could (and should) easily be cached, but for the time being I have only implemented direct queries for these structs from the leetcode API.
I've also made a substantial number of changes to existing bodies of code, so it's entirely possible I've accidentally broken a feature or two at some point. I've tried to add TODOs to places where I think the code will probably need to change, but there is probably more.
UX
Currently, the end-user experience with leetcode-cli looks like this:
$ leetcode contest weekly-contest-295 -ru
started 1 seconds ago
[weekly-contest-295] Weekly Contest 295
fID Points Title
------|------|----------------------
2372 | 3 | Rearrange Characters to Make Target String
2373 | 4 | Apply Discount to Prices
2374 | 5 | Steps to Make Array Non-decreasing
2375 | 6 | Minimum Obstacle Removal to Reach Corner
$ leetcode e 2372 # work on the problem
$ leetcode t -c weekly-contest-295 2372 # test solution
Accepted Runtime: 35 ms
Your input: "ilovecodingonleetcode"↩ "code"
Output: 2
Expected: 2
$ leetcode x -c weekly-contest-295 2372 # exec solution
Success
Runtime: 64 ms, faster than 11% of Python3 online submissions for Rearrange Characters to Make Target String.
Memory Usage: 13.7 MB, less than 98% of Python3 Rearrange Characters to Make Target String.
This system works well enough, but it could definitely be a lot more ergonomic. I was hoping to implement an ncurses-like interface for contests, but
- Because of how the other subcommands are implemented, a significant amount of refactoring would be needed to edit/execute code without recursively calling
leetcode-cli
as a subprocess
- Adding an interactive UI would cost a lot more lines of code & maintenance. Ncurses in particular (and its rust wrapper, pancake) is not very good at the thread safety / async thing.
Nonetheless, the current output of the contest
command is rather ugly, and more work ought to be done here.
End
Uh oh!
There was an error while loading. Please reload this page.
TL;DR
You can play leetcode contests on this fork. I played last weekend's contests with this; see the User Experience example.
I want feedback for anything and everything -- code smells, CLI design, even "this sucks and I'm rejecting it".
New feature: Playing with contests in leetcode-cli
This is a short overview of the work I've done to get leetcode contests working in this app. First, I cover the client-server relationship for leetcode contests. Then, I briefly explain the architectural decisions I made in implementation. Finally, I demonstrate what usage of the new
leetcode contest
command looks like, and go over possible changes.The git commit history for this PR is dirty; it might be easier to see the files changed here.
Map of the Leetcode contest API
Contests are stored in the leetcode backend as the
ContestNode
type (see next section), which mostly corresponds to the information available atleetcode.com/contest/$slug
. We can retrieve information by queryinghttps://leetcode.com/contest/api/info/$contest_slug
, or{ contest(titleSlug: String!) }
Before a contest begins, users must register for the contest to participate in it. This can be done with a simple empty POST request to
https://leetcode.com/contest/api/$contest_slug/register
Once a contest starts, the client needs to make additional requests to the leetcode API to get information about the contest problems. For normal
leetcode.com
users, the information appears to be dumped directly into HTML inside a<script>
tag. I did not want to implement/import a full blown HTML parser || use a horrible string/regex search hack, so I found an alternative:{ question(titleSlug: String!) }
queries). There are some differences between the data returned for a contest problem and a normal problem, and they will need to be handled later in the CLI's code.After the user finishes implementing the solution for a problem, they need to submit their code to be judged by the leetcode runtime. A dedicated contest API submission route must be used; running code via the normal API routes "works" but doesn't count for gaining contest points.
https://leetcode.com/contest/api/$contest/problems/$slug/interpret_solution/
https://leetcode.com/contest/api/$contest/problems/$slug/submit/
Users might also want to check the contest scoreboard to see their position. I have not worked on querying/implementing this yet.
Interlude: what's what the
fun
command?Leetcode's API is not documented (at all). I discovered all of the information in the section above by a mixture of the Firefox Dev Console && unsolicited queries to
leetcode.com/graphql
. The latter is what thefun
subcommand is for; I made it as a quick debugging tool to enumerate leetcode's graphql API.As an example, you can get the structure of a
ContestQuestionNode
like this:None of this is needed for a normal user of leetcode-cli, of course, so I will probably remove it unless you think it would be a good idea to keep it.
IMPLEMENTATION
So, how does the API translate to code?
We need some way to expose the following operations to the user:
1, get contest info (given a slug, like
"weekly-contest-295"
)2. register for a contest
3. get contset problem info
4. submit code to test/run on contest problems
Because contest problems are structurally identical to normal leetcode problems, (4) is actually already solved -- the code for the
test
/exec
commands can be used here, with a little modification. (3) is also mostly solved by the existing code, but there are a few issues:conf.sys.urls["problem"]
) to read problem descriptions, so I added a longer graphql query (Leetcode::get_contest_question_detail
) for thatQuestionNode
data from leetcode for each problem will be slightly modified after a contest ends && the contest problems are republished as normal problems. This causes a user's code for a contest problem to "disappear" from leetcode-cli after a contest, because thefrontendQuestionId
for each problem changes && the user's code file in~/.leetcode/code
is no longer tracked properly by leetcode-cli. I have some ideas to handle this, but I haven't done anything about it yet.That leaves (2) and (1). I added the
Contest
andContestQuestionStub
structs tomodels.rs
; they represent theContestNode
andContestQuestionNode
types from the leetcode graphql API. Corresponding methods were added inleetcode,rs
,cache/mod.rs
, andparser.rs
.The data for the contest structs could (and should) easily be cached, but for the time being I have only implemented direct queries for these structs from the leetcode API.
I've also made a substantial number of changes to existing bodies of code, so it's entirely possible I've accidentally broken a feature or two at some point. I've tried to add TODOs to places where I think the code will probably need to change, but there is probably more.
UX
Currently, the end-user experience with leetcode-cli looks like this:
This system works well enough, but it could definitely be a lot more ergonomic. I was hoping to implement an ncurses-like interface for contests, but
leetcode-cli
as a subprocessNonetheless, the current output of the
contest
command is rather ugly, and more work ought to be done here.End