Skip to content

Commit d40a51a

Browse files
committed
Add backup LeetCode submission sync
1 parent 55b38fb commit d40a51a

4 files changed

Lines changed: 303 additions & 58 deletions

File tree

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Sync Recent LeetCode Submissions
2+
3+
on:
4+
schedule:
5+
- cron: "*/5 * * * *"
6+
workflow_dispatch:
7+
8+
concurrency:
9+
group: sync-leetcode-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
permissions:
13+
contents: write
14+
15+
jobs:
16+
sync-leetcode:
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Check out repository
21+
uses: actions/checkout@v4
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: "3.x"
27+
28+
- name: Sync recent accepted submissions from LeetCode
29+
env:
30+
LEETCODE_COOKIES: ${{ secrets.LEETCODE_COOKIES }}
31+
LEETCODE_SESSION: ${{ secrets.LEETCODE_SESSION }}
32+
LEETCODE_CSRFTOKEN: ${{ secrets.LEETCODE_CSRFTOKEN }}
33+
run: python3 scripts/sync_recent_leetcode_submissions.py
34+
35+
- name: Refresh knowledge notes
36+
run: python3 scripts/sync_problem_notes.py
37+
38+
- name: Generate AI note drafts
39+
env:
40+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
41+
OPENAI_MODEL: gpt-5-mini
42+
run: python3 scripts/generate_ai_problem_notes.py
43+
44+
- name: Refresh README progress
45+
run: python3 scripts/update_progress.py
46+
47+
- name: Commit generated updates
48+
run: |
49+
if git diff --quiet -- .leetcode README.md notes data/problem_metadata.json data/leetcode_sync_state.json; then
50+
echo "No generated changes to commit."
51+
exit 0
52+
fi
53+
git config user.name "github-actions[bot]"
54+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
55+
git add .leetcode README.md notes data/problem_metadata.json data/leetcode_sync_state.json
56+
git commit -m "Sync recent LeetCode submissions"
57+
git push

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,10 @@ Tracked unique problems solved across all sheets: `0 / 293`
3030

3131
- Solve on `LeetCode`
3232
- Let `LeetSync` push the accepted submission into this repository
33+
- A backup GitHub Action can also pull recent accepted submissions directly from LeetCode when `LEETCODE_COOKIES` or `LEETCODE_SESSION` + `LEETCODE_CSRFTOKEN` are configured as repo secrets
3334
- The GitHub Action scans the synced solution names and refreshes the progress table in this README
3435
- The same workflow creates or updates per-problem notes in [`notes/problems/`](notes/problems), syncs the problem statement, and refreshes the index at [`notes/INDEX.md`](notes/INDEX.md)
35-
- If the repo has an `OPENAI_API_KEY` secret, the workflow also generates a draft summary, data structures list, approach, and complexity directly from your synced accepted solution
36+
- If the repo has an `OPENAI_API_KEY` secret, the workflow also refreshes the summary, data structures list, approach, and complexity directly from your latest synced accepted solution
3637

3738
## Knowledge Capture
3839

@@ -49,7 +50,7 @@ Tracked unique problems solved across all sheets: `0 / 293`
4950
- data structures used
5051
- approach
5152
- time/space complexity
52-
- You can still edit the generated note manually afterward; existing non-`TODO` sections are preserved
53+
- The AI-generated sections are refreshed from the latest synced accepted solution
5354

5455
To save your own approach quickly after solving, use:
5556

@@ -68,3 +69,4 @@ python3 scripts/update_problem_note.py two-sum \
6869
- `Striver's SDE Sheet` tracking only covers the LeetCode-backed problems from the official sheet
6970
- If a Striver problem is not solved on LeetCode, `LeetSync` cannot sync it into this repository
7071
- For full note automation, add `OPENAI_API_KEY` as a repository secret in GitHub Actions
72+
- For backup LeetCode-side syncing without relying only on the extension, add `LEETCODE_COOKIES` as a repository secret, or add both `LEETCODE_SESSION` and `LEETCODE_CSRFTOKEN`

scripts/generate_ai_problem_notes.py

Lines changed: 24 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,6 @@
2222

2323
OPENAI_RESPONSES_URL = "https://api.openai.com/v1/responses"
2424
DEFAULT_MODEL = "gpt-5-mini"
25-
TARGET_SECTIONS = (
26-
"Problem Summary",
27-
"Data Structures Used",
28-
"Approach",
29-
"Complexity",
30-
"Revision Notes",
31-
)
32-
3325
SYSTEM_INSTRUCTIONS = """
3426
You generate concise LeetCode study notes from a problem statement and an accepted solution.
3527
Return only valid JSON with this exact shape:
@@ -77,15 +69,6 @@ def extract_section_body(content: str, heading: str) -> str:
7769
return match.group(1).strip()
7870

7971

80-
def section_needs_update(content: str, heading: str) -> bool:
81-
return "TODO" in extract_section_body(content, heading)
82-
83-
84-
def complexity_needs_update(content: str) -> bool:
85-
body = extract_section_body(content, "Complexity")
86-
return "TODO" in body
87-
88-
8972
def format_bullets(values: list[str], fallback: str) -> str:
9073
cleaned = [value.strip() for value in values if value and value.strip()]
9174
if not cleaned:
@@ -210,42 +193,31 @@ def build_prompt(slug: str, metadata: dict[str, Any], solution_paths: list[str])
210193
def apply_ai_draft(note_path: Path, draft: dict[str, Any]) -> bool:
211194
content = note_path.read_text()
212195
updated = content
213-
changed = False
214-
215-
if section_needs_update(updated, "Problem Summary"):
216-
updated = replace_section(updated, "Problem Summary", str(draft.get("summary") or "TODO"))
217-
changed = True
218-
219-
if section_needs_update(updated, "Data Structures Used"):
220-
data_structures = draft.get("data_structures")
221-
body = format_bullets(data_structures if isinstance(data_structures, list) else [], "TODO")
222-
updated = replace_section(updated, "Data Structures Used", body)
223-
changed = True
224-
225-
if section_needs_update(updated, "Approach"):
226-
updated = replace_section(updated, "Approach", str(draft.get("approach") or "TODO"))
227-
changed = True
228-
229-
if complexity_needs_update(updated):
230-
updated = replace_complexity(
231-
updated,
232-
str(draft.get("time_complexity") or "TODO"),
233-
str(draft.get("space_complexity") or "TODO"),
234-
)
235-
changed = True
236-
237-
if section_needs_update(updated, "Revision Notes"):
238-
revision_notes = draft.get("revision_notes")
239-
body = format_bullets(
240-
revision_notes if isinstance(revision_notes, list) else [],
241-
"- TODO",
242-
)
243-
updated = replace_section(updated, "Revision Notes", body)
244-
changed = True
245-
246-
if changed:
196+
data_structures = draft.get("data_structures")
197+
revision_notes = draft.get("revision_notes")
198+
199+
updated = replace_section(updated, "Problem Summary", str(draft.get("summary") or "TODO"))
200+
updated = replace_section(
201+
updated,
202+
"Data Structures Used",
203+
format_bullets(data_structures if isinstance(data_structures, list) else [], "TODO"),
204+
)
205+
updated = replace_section(updated, "Approach", str(draft.get("approach") or "TODO"))
206+
updated = replace_complexity(
207+
updated,
208+
str(draft.get("time_complexity") or "TODO"),
209+
str(draft.get("space_complexity") or "TODO"),
210+
)
211+
updated = replace_section(
212+
updated,
213+
"Revision Notes",
214+
format_bullets(revision_notes if isinstance(revision_notes, list) else [], "- TODO"),
215+
)
216+
217+
if updated != content:
247218
note_path.write_text(updated)
248-
return changed
219+
return True
220+
return False
249221

250222

251223
def parse_args() -> argparse.Namespace:
@@ -287,10 +259,6 @@ def main() -> int:
287259
if not note_path.exists():
288260
continue
289261

290-
note_content = note_path.read_text()
291-
if not any(section_needs_update(note_content, heading) for heading in TARGET_SECTIONS if heading != "Complexity") and not complexity_needs_update(note_content):
292-
continue
293-
294262
metadata = get_problem_metadata(slug, cache)
295263
prompt = build_prompt(slug, metadata, solution_paths)
296264

0 commit comments

Comments
 (0)