Skip to content

Commit 2a205fd

Browse files
committed
Add verbose and commit-header functionality
1 parent b5371be commit 2a205fd

File tree

5 files changed

+111
-25
lines changed

5 files changed

+111
-25
lines changed

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ GitHub Action for automatically syncing LeetCode submissions to a GitHub reposit
5454
leetcode-csrf-token: ${{ secrets.LEETCODE_CSRF_TOKEN }}
5555
leetcode-session: ${{ secrets.LEETCODE_SESSION }}
5656
destination-folder: my-folder
57+
verbose: true
58+
commit-header: '[LeetCode Sync]'
5759
```
5860
5961
6. After you've submitted a LeetCode solution, run the workflow by going to the `Actions` tab, clicking the action name, e.g. `Sync Leetcode`, and then clicking `Run workflow`. The workflow will also automatically run once a week by default (can be configured via the `cron` parameter).
@@ -64,25 +66,27 @@ GitHub Action for automatically syncing LeetCode submissions to a GitHub reposit
6466
- `leetcode-csrf-token` _(required)_: The LeetCode CSRF token for retrieving submissions from LeetCode
6567
- `leetcode-session` _(required)_: The LeetCode session value for retrieving submissions from LeetCode
6668
- `filter-duplicate-secs`: Number of seconds after an accepted solution to ignore other accepted solutions for the same problem, default: 86400 (1 day)
67-
- `destination-folder` _(optional)_: The folder in your repo to save the submissions to (necessary for shared repos)
69+
- `destination-folder` _(optional)_: The folder in your repo to save the submissions to (necessary for shared repos), default: _none_
70+
- `verbose` _(optional)_: Requires an additional API call but adds submission percentiles and question numbers to the repo, default: true
71+
- `commit-header` _(optional)_: How the automated commits should be prefixed, default: '[LeetCode Sync]'
6872

6973
## Shared Repos
7074

71-
A single repo can be shared by multiple users by using the `destination-folder` input field to sync each user's files to a separate folder. This is useful for users who want to add a more social, collaborative, or competitive aspect to their LeetCode sync repo.
75+
Problems can be routed to a specific folder within a single repo using the `destination-folder` input field. This is useful for users who want to share a repo to add a more social, collaborative, or competitive aspect to their LeetCode sync repo.
7276

7377
## Contributing
7478

7579
#### Testing locally
7680

77-
If you want to test changes to the action locally without having to commit and run the workflow on GitHub, you can edit `src/test_config.js` to have the required config values and then run:
81+
If you want to test changes to the action locally without having to commit and run the workflow on GitHub, you can edit `src/test_config.js` to have the required config values and then run:
7882

7983
`$ node index.js test`
8084

8185
If you're using Replit, you can also just use the `Run` button, which is already configured to the above command.
8286

8387
#### Adding a new workflow parameter
8488

85-
If you add a workflow parameter, please make sure to also add it in `src/test_config.js`, so that it can be tested locally.
89+
If you add a workflow parameter, please make sure to also add it in `src/test_config.js`, so that it can be tested locally.
8690

8791
You will need to manually run:
8892

action.yml

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: 'LeetCode Sync'
22
description: 'Sync LeetCode submissions to GitHub'
3-
branding:
3+
branding:
44
icon: git-commit
55
color: yellow
66
inputs:
@@ -20,6 +20,15 @@ inputs:
2020
destination-folder:
2121
description: 'The folder to save the synced files in (relative to the top level of your repo)'
2222
required: false
23+
default: null
24+
verbose:
25+
description: 'Requires an additional API call but adds submission percentiles and question numbers to the repo'
26+
required: false
27+
default: true
28+
commit-header:
29+
description: 'How the automated commits should be prefixed'
30+
required: false
31+
default: '[LeetCode Sync]'
2332
runs:
2433
using: 'node16'
25-
main: 'index.js'
34+
main: 'index.js'

index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ async function main() {
1818
leetcodeSession = config.LEETCODE_SESSION;
1919
filterDuplicateSecs = config.FILTER_DUPLICATE_SECS;
2020
destinationFolder = config.DESTINATION_FOLDER;
21+
verbose = config.VERBOSE;
22+
commitHeader = config.COMMIT_HEADER;
2123
} else {
2224
githubToken = core.getInput('github-token');
2325
owner = context.repo.owner;
@@ -26,9 +28,11 @@ async function main() {
2628
leetcodeSession = core.getInput('leetcode-session');
2729
filterDuplicateSecs = core.getInput('filter-duplicate-secs');
2830
destinationFolder = core.getInput('destination-folder');
31+
verbose = core.getInput('verbose');
32+
commitHeader = core.getInput('commit-header');
2933
}
3034

31-
await action.sync({ githubToken, owner, repo, filterDuplicateSecs, leetcodeCSRFToken, leetcodeSession, destinationFolder });
35+
await action.sync({ githubToken, owner, repo, leetcodeCSRFToken, leetcodeSession, filterDuplicateSecs, destinationFolder, verbose, commitHeader });
3236
}
3337

3438
main().catch((error) => {

src/action.js

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const axios = require('axios');
22
const { Octokit } = require('@octokit/rest');
33

4-
const COMMIT_MESSAGE = 'Sync LeetCode submission';
4+
const COMMIT_MESSAGE = '[LeetCode Sync]';
55
const LANG_TO_EXTENSION = {
66
'bash': 'sh',
77
'c': 'c',
@@ -33,8 +33,60 @@ function log(message) {
3333
console.log(`[${new Date().toUTCString()}] ${message}`);
3434
}
3535

36+
function pad(n) {
37+
var s = '000' + n;
38+
return s.substring(s.length-4);
39+
}
40+
3641
function normalizeName(problemName) {
37-
return problemName.toLowerCase().replace(/\s/g, '_');
42+
return problemName.toLowerCase().replace(/\s/g, '-');
43+
}
44+
45+
async function getInfo(submission, session, csrf) {
46+
let data = JSON.stringify({
47+
query: `query submissionDetails($submissionId: Int!) {
48+
submissionDetails(submissionId: $submissionId) {
49+
runtimePercentile
50+
memoryPercentile
51+
question {
52+
questionId
53+
}
54+
}
55+
}`,
56+
variables: {'submissionId':submission.id}
57+
});
58+
59+
let config = {
60+
maxBodyLength: Infinity,
61+
headers: {
62+
'Content-Type': 'application/json',
63+
'Cookie': `LEETCODE_SESSION=${session};csrftoken=${csrf};`,
64+
},
65+
data : data
66+
};
67+
68+
// No need to break on first request error since that would be done when getting submissions
69+
const getInfo = async (maxRetries = 5, retryCount = 0) => {
70+
try {
71+
const response = await axios.post('https://leetcode.com/graphql/', data, config);
72+
const runtimePercentile = `${response.data.data.submissionDetails.runtimePercentile.toFixed(2)}%`;
73+
const memoryPercentile = `${response.data.data.submissionDetails.memoryPercentile.toFixed(2)}%`;
74+
const questionId = pad(response.data.data.submissionDetails.question.questionId.toString());
75+
76+
log(`Got info for submission #${submission.id}`);
77+
return { runtimeperc: runtimePercentile, memoryperc: memoryPercentile, qid: questionId };
78+
} catch (exception) {
79+
if (retryCount >= maxRetries) {
80+
throw exception;
81+
}
82+
log('Error fetching submission info, retrying in ' + 3 ** retryCount + ' seconds...');
83+
await delay(3 ** retryCount * 1000);
84+
return getInfo(maxRetries, retryCount + 1);
85+
}
86+
};
87+
88+
info = await getInfo();
89+
return {...submission, ...info};
3890
}
3991

4092
async function commit(params) {
@@ -47,7 +99,8 @@ async function commit(params) {
4799
treeSHA,
48100
latestCommitSHA,
49101
submission,
50-
destinationFolder
102+
destinationFolder,
103+
commitHeader
51104
} = params;
52105

53106
const name = normalizeName(submission.title);
@@ -58,13 +111,23 @@ async function commit(params) {
58111
}
59112

60113
const prefix = !!destinationFolder ? `${destinationFolder}/` : '';
61-
const path = `${prefix}problems/${name}/solution.${LANG_TO_EXTENSION[submission.lang]}`
114+
const commitName = !!commitHeader ? commitHeader : COMMIT_MESSAGE;
115+
116+
if ('runtimeperc' in submission) {
117+
message = `${commitName} Runtime - ${submission.runtime} (${submission.runtimeperc}), Memory - ${submission.memory} (${submission.memoryperc})`;
118+
qid = `${submission.qid}-`;
119+
} else {
120+
message = `${commitName} Runtime - ${submission.runtime}, Memory - ${submission.memory}`;
121+
qid = '';
122+
}
123+
124+
const path = `${prefix}${qid}${name}/solution.${LANG_TO_EXTENSION[submission.lang]}`
62125

63126
const treeData = [
64127
{
65128
path,
66129
mode: '100644',
67-
content: submission.code,
130+
content: `${submission.code}\n`, // Adds newline at EOF to conform to git recommendations
68131
}
69132
];
70133

@@ -79,7 +142,7 @@ async function commit(params) {
79142
const commitResponse = await octokit.git.createCommit({
80143
owner: owner,
81144
repo: repo,
82-
message: `${COMMIT_MESSAGE} - ${submission.title} (${submission.lang})`,
145+
message: message,
83146
tree: treeResponse.data.sha,
84147
parents: [latestCommitSHA],
85148
author: {
@@ -144,10 +207,12 @@ async function sync(inputs) {
144207
githubToken,
145208
owner,
146209
repo,
147-
filterDuplicateSecs,
148210
leetcodeCSRFToken,
149211
leetcodeSession,
150-
destinationFolder
212+
filterDuplicateSecs,
213+
destinationFolder,
214+
verbose,
215+
commitHeader
151216
} = inputs;
152217

153218
const octokit = new Octokit({
@@ -162,7 +227,7 @@ async function sync(inputs) {
162227
});
163228

164229
let lastTimestamp = 0;
165-
// commitInfo is used to get the original name / email to use for the author / committer.
230+
// commitInfo is used to get the original name / email to use for the author / committer.
166231
// Since we need to modify the commit time, we can't use the default settings for the
167232
// authenticated user.
168233
let commitInfo = commits.data[commits.data.length - 1].commit.author;
@@ -205,7 +270,7 @@ async function sync(inputs) {
205270
throw exception;
206271
}
207272
log('Error fetching submissions, retrying in ' + 3 ** retryCount + ' seconds...');
208-
// There's a rate limit on LeetCode API, so wait with backoff before retrying.
273+
// There's a rate limit on LeetCode API, so wait with backoff before retrying.
209274
await delay(3 ** retryCount * 1000);
210275
return getSubmissions(maxRetries, retryCount + 1);
211276
}
@@ -214,7 +279,7 @@ async function sync(inputs) {
214279
// the tokens are configured incorrectly.
215280
const maxRetries = (response === null) ? 0 : 5;
216281
if (response !== null) {
217-
// Add a 1 second delay before all requests after the initial request.
282+
// Add a 1 second delay before all requests after the initial request.
218283
await delay(1000);
219284
}
220285
response = await getSubmissions(maxRetries);
@@ -239,8 +304,11 @@ async function sync(inputs) {
239304
let latestCommitSHA = commits.data[0].sha;
240305
let treeSHA = commits.data[0].commit.tree.sha;
241306
for (i = submissions.length - 1; i >= 0; i--) {
242-
submission = submissions[i];
243-
[treeSHA, latestCommitSHA] = await commit({ octokit, owner, repo, defaultBranch, commitInfo, treeSHA, latestCommitSHA, submission, destinationFolder });
307+
let submission = submissions[i];
308+
if (verbose != 'false') {
309+
submission = await getInfo(submission, leetcodeSession, leetcodeCSRFToken);
310+
}
311+
[treeSHA, latestCommitSHA] = await commit({ octokit, owner, repo, defaultBranch, commitInfo, treeSHA, latestCommitSHA, submission, destinationFolder, commitHeader })
244312
}
245313
log('Done syncing all submissions.');
246314
}

src/test_config.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
// Modify this file to run index.js locally and not as a GitHub Action.
22

33
module.exports = {
4+
// These parameters are required.
45
GITHUB_TOKEN: null,
5-
// Form of "<owner>/<repo_name>"
6-
GITHUB_REPO: null,
7-
6+
GITHUB_REPO: null, // Form of '<owner>/<repo_name>'
87
LEETCODE_CSRF_TOKEN: null,
98
LEETCODE_SESSION: null,
109

11-
// These parameters are optional and have default values.
10+
// These parameters are optional and have default values if needed.
1211
FILTER_DUPLICATE_SECS: 86400,
1312
DESTINATION_FOLDER: null,
14-
}
13+
VERBOSE: true,
14+
COMMIT_HEADER: '[LeetCode Sync]'
15+
}

0 commit comments

Comments
 (0)