Skip to content

sync leetcode

sync leetcode #65

Workflow file for this run

name: sync leetcode
# Pulls accepted submissions from LeetCode into this repo, then rebuilds the index.
#
# Needs two repository secrets, both read from leetcode.com cookies:
# LEETCODE_SESSION, LEETCODE_CSRF_TOKEN
# Without them every run exits early instead of failing, so an unconfigured repo
# does not mail a failure notice every few hours.
on:
schedule:
# Every 6 hours, plus one at 21:30 KST so the evening's work is in the repo
# before the 22:00 output check looks for it.
- cron: "0 */6 * * *"
- cron: "30 12 * * *"
workflow_dispatch:
permissions:
contents: write
concurrency:
group: sync-leetcode
cancel-in-progress: false
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check whether LeetCode credentials are configured
id: guard
env:
SESSION: ${{ secrets.LEETCODE_SESSION }}
CSRF: ${{ secrets.LEETCODE_CSRF_TOKEN }}
run: |
if [ -z "$SESSION" ] || [ -z "$CSRF" ]; then
echo "ready=false" >> "$GITHUB_OUTPUT"
echo "::notice::LEETCODE_SESSION / LEETCODE_CSRF_TOKEN are not set — nothing to sync."
exit 0
fi
echo "ready=true" >> "$GITHUB_OUTPUT"
# Lengths only, never the values. A csrftoken as long as the session cookie
# means the wrong thing was pasted into it -- a mix-up the length floor below
# cannot catch, and the one that produces an otherwise inexplicable rejection.
echo "::notice::LEETCODE_SESSION is ${#SESSION} chars, LEETCODE_CSRF_TOKEN is ${#CSRF} chars."
if [ "${#CSRF}" -gt 200 ]; then
echo "::warning::LEETCODE_CSRF_TOKEN is ${#CSRF} chars, far longer than the 32-64 that cookie normally is. The session cookie was probably pasted into it by mistake."
fi
# A malformed paste fails downstream as an opaque
# "submissions is not iterable" TypeError, so say something useful here
# instead. Lengths only -- the values themselves are never printed.
# 300 rather than something lenient: the DevTools cookie grid shows a
# shortened value, and copying that cell yields a plausible-looking
# hundred-odd characters that pass any looser floor and then get rejected.
if [ "${#SESSION}" -lt 300 ]; then
echo "::warning::LEETCODE_SESSION is only ${#SESSION} characters. The real cookie is 600+. The DevTools cookie table truncates what it displays — right-click the LEETCODE_SESSION row and use Copy value, or copy from the detail pane below the table, rather than selecting the cell."
fi
if [ "${#CSRF}" -lt 20 ]; then
echo "::warning::LEETCODE_CSRF_TOKEN is only ${#CSRF} characters, which looks truncated. The csrftoken cookie is normally 32-64 characters."
fi
- name: Sync accepted submissions
if: steps.guard.outputs.ready == 'true'
uses: joshcai/leetcode-sync@v1.7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
leetcode-csrf-token: ${{ secrets.LEETCODE_CSRF_TOKEN }}
leetcode-session: ${{ secrets.LEETCODE_SESSION }}
commit-header: "solve"
# The action throws a bare "submissions is not iterable" when LeetCode returns
# a 200 with no submission list, which it does for a rejected session. Upstream
# has this open as joshcai/leetcode-sync#72; until it lands, translate it here.
- name: Explain a credential failure
if: failure() && steps.guard.outputs.ready == 'true'
run: |
echo "::error::Sync failed. If the log shows 'submissions is not iterable', LeetCode"
echo "::error::rejected the session — the cookies are wrong or have expired."
echo "::error::Re-copy them from leetcode.com (DevTools > Application > Cookies) and run:"
echo "::error:: pbpaste | tr -d '[:space:]' | gh secret set LEETCODE_SESSION -R ${{ github.repository }}"
echo "::error:: pbpaste | tr -d '[:space:]' | gh secret set LEETCODE_CSRF_TOKEN -R ${{ github.repository }}"
# The sync commits through the GitHub API, so this checkout is behind once it
# finishes -- rebuilding without pulling would write the index from stale state.
# A push made with GITHUB_TOKEN also does not trigger other workflows, which is
# why the index is rebuilt here rather than left to the `rebuild index` workflow.
- name: Rebuild the index
if: steps.guard.outputs.ready == 'true'
run: |
git pull --ff-only origin main
python3 scripts/build_readme.py
- name: Commit the index
if: steps.guard.outputs.ready == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add README.md stats.json
if git diff --cached --quiet; then
echo "index already current"
else
git commit -m "index: rebuild after sync"
git push
fi