Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 5e55354

Browse files
authored
Merge pull request joshcai#46 from ravibrock/master
Add verbose and commit-header functionality
2 parents f62f2bf + 23719bc commit 5e55354

File tree

5 files changed

+115
-28
lines changed

5 files changed

+115
-28
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)_: Adds submission percentiles and question numbers to the repo (requires an additional API call), default: true
71+
- `commit-header` _(optional)_: How the automated commits should be prefixed, default: 'Sync LeetCode submission'
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: 'Adds submission percentiles and question numbers to the repo (requires an additional API call)'
26+
required: false
27+
default: true
28+
commit-header:
29+
description: 'How the automated commits should be prefixed'
30+
required: false
31+
default: 'Sync LeetCode submission'
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.toString(); // Convert to string to match core.getInput('verbose') return type
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: 85 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,63 @@ function log(message) {
3333
console.log(`[${new Date().toUTCString()}] ${message}`);
3434
}
3535

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

4095
async function commit(params) {
@@ -48,7 +103,8 @@ async function commit(params) {
48103
latestCommitSHA,
49104
submission,
50105
destinationFolder,
51-
question_data
106+
commitHeader,
107+
questionData
52108
} = params;
53109

54110
const name = normalizeName(submission.title);
@@ -59,21 +115,29 @@ async function commit(params) {
59115
}
60116

61117
const prefix = !!destinationFolder ? `${destinationFolder}/` : '';
62-
const questionPath = `${prefix}problems/${name}/question.md`; // Markdown file for the problem with question data
63-
const solutionPath = `${prefix}problems/${name}/solution.${LANG_TO_EXTENSION[submission.lang]}`; // Separate file for the solution
118+
const commitName = !!commitHeader ? commitHeader : COMMIT_MESSAGE;
64119

120+
if ('runtimePerc' in submission) {
121+
message = `${commitName} Runtime - ${submission.runtime} (${submission.runtimePerc}), Memory - ${submission.memory} (${submission.memoryPerc})`;
122+
qid = `${submission.qid}-`;
123+
} else {
124+
message = `${commitName} Runtime - ${submission.runtime}, Memory - ${submission.memory}`;
125+
qid = '';
126+
}
127+
const questionPath = `${prefix}${qid}${name}/README.md`; // Markdown file for the problem with question data
128+
const solutionPath = `${prefix}${qid}${name}/solution.${LANG_TO_EXTENSION[submission.lang]}`; // Separate file for the solution
65129

66130
const treeData = [
67131
{
68132
path: questionPath,
69133
mode: '100644',
70-
content: question_data,
134+
content: questionData,
71135
},
72136
{
73137
path: solutionPath,
74138
mode: '100644',
75-
content: submission.code,
76-
},
139+
content: `${submission.code}\n`,// Adds newline at EOF to conform to git recommendations
140+
}
77141
];
78142

79143
const treeResponse = await octokit.git.createTree({
@@ -87,7 +151,7 @@ async function commit(params) {
87151
const commitResponse = await octokit.git.createCommit({
88152
owner: owner,
89153
repo: repo,
90-
message: `${COMMIT_MESSAGE} - ${submission.title} (${submission.lang})`,
154+
message: message,
91155
tree: treeResponse.data.sha,
92156
parents: [latestCommitSHA],
93157
author: {
@@ -179,10 +243,12 @@ async function sync(inputs) {
179243
githubToken,
180244
owner,
181245
repo,
182-
filterDuplicateSecs,
183246
leetcodeCSRFToken,
184247
leetcodeSession,
185-
destinationFolder
248+
filterDuplicateSecs,
249+
destinationFolder,
250+
verbose,
251+
commitHeader
186252
} = inputs;
187253

188254
const octokit = new Octokit({
@@ -197,7 +263,7 @@ async function sync(inputs) {
197263
});
198264

199265
let lastTimestamp = 0;
200-
// commitInfo is used to get the original name / email to use for the author / committer.
266+
// commitInfo is used to get the original name / email to use for the author / committer.
201267
// Since we need to modify the commit time, we can't use the default settings for the
202268
// authenticated user.
203269
let commitInfo = commits.data[commits.data.length - 1].commit.author;
@@ -240,7 +306,7 @@ async function sync(inputs) {
240306
throw exception;
241307
}
242308
log('Error fetching submissions, retrying in ' + 3 ** retryCount + ' seconds...');
243-
// There's a rate limit on LeetCode API, so wait with backoff before retrying.
309+
// There's a rate limit on LeetCode API, so wait with backoff before retrying.
244310
await delay(3 ** retryCount * 1000);
245311
return getSubmissions(maxRetries, retryCount + 1);
246312
}
@@ -249,7 +315,7 @@ async function sync(inputs) {
249315
// the tokens are configured incorrectly.
250316
const maxRetries = (response === null) ? 0 : 5;
251317
if (response !== null) {
252-
// Add a 1 second delay before all requests after the initial request.
318+
// Add a 1 second delay before all requests after the initial request.
253319
await delay(1000);
254320
}
255321
response = await getSubmissions(maxRetries);
@@ -275,10 +341,13 @@ async function sync(inputs) {
275341
let treeSHA = commits.data[0].commit.tree.sha;
276342
for (i = submissions.length - 1; i >= 0; i--) {
277343
submission = submissions[i];
278-
344+
if (verbose != 'false') {
345+
submission = await getInfo(submission, leetcodeSession, leetcodeCSRFToken);
346+
}
347+
279348
// Get the question data for the submission.
280-
const question_data = await getQuestionData(submission.title_slug, leetcodeSession);
281-
[treeSHA, latestCommitSHA] = await commit({ octokit, owner, repo, defaultBranch, commitInfo, treeSHA, latestCommitSHA, submission, destinationFolder, question_data });
349+
const questionData = await getQuestionData(submission.title_slug, leetcodeSession);
350+
[treeSHA, latestCommitSHA] = await commit({ octokit, owner, repo, defaultBranch, commitInfo, treeSHA, latestCommitSHA, submission, destinationFolder, commitHeader, questionData });
282351
}
283352
log('Done syncing all submissions.');
284353
}

‎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: 'Sync LeetCode submission'
15+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /