Skip to content

Commit 26595f0

Browse files
feat: add login command with LeetCode session authentication
1 parent fde4fdd commit 26595f0

File tree

6 files changed

+141
-4
lines changed

6 files changed

+141
-4
lines changed

requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
typer
22
requests
33
gql[all]
4-
setuptools
4+
bs4
5+
setuptools
6+
python-leetcode

src/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from .commands.show import show
22
from .commands.details import details
33
from .server.api import fetch_problem_list
4-
54
__version__ = "0.1.0"
65

76
__all__ = ["show", "details", "fetch_problem_list"]

src/commands/login.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import typer
2+
from src.server.auth import Auth
3+
4+
auth_manager = Auth()
5+
6+
def login():
7+
"""Login to LeetCode"""
8+
# First try to use saved session
9+
if auth_manager.is_authenticated:
10+
saved_session = auth_manager.session_manager.load_session()
11+
if saved_session:
12+
typer.echo(typer.style(f"Already logged in as {saved_session['user_name']}!", fg=typer.colors.GREEN))
13+
return
14+
15+
typer.echo(typer.style("To login, you'll need your LEETCODE_SESSION token:", fg=typer.colors.YELLOW))
16+
typer.echo("\n1. Open LeetCode in your browser")
17+
typer.echo("2. Press F12 to open Developer Tools")
18+
typer.echo("3. Go to Application tab > Cookies > leetcode.com")
19+
typer.echo("4. Find and copy the 'LEETCODE_SESSION' value\n")
20+
21+
leetcode_session = typer.prompt("Please enter your LEETCODE_SESSION token")
22+
23+
result = auth_manager.login_with_session(leetcode_session)
24+
25+
if result["success"]:
26+
typer.echo(typer.style(f"\n✓ Successfully logged in as {result['user_name']}!", fg=typer.colors.GREEN))
27+
else:
28+
typer.echo(typer.style(f"\n✗ Login failed: {result['message']}", fg=typer.colors.RED))

src/main.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import typer
22
from src import show, details
3+
from src.commands.login import login
34

45
app = typer.Typer()
56

6-
app.command()(show)
7-
app.command()(details)
7+
app.command(name="show")(show)
8+
app.command(name="user-details")(details)
9+
app.command(name="login")(login)
810

911
if __name__ == "__main__":
1012
app()

src/server/auth.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from typing import Dict
2+
import requests
3+
from .session_manager import SessionManager
4+
5+
class Auth:
6+
def __init__(self):
7+
self.session = requests.Session()
8+
self.BASE_URL = "https://leetcode.com"
9+
self.is_authenticated = False
10+
self.session_manager = SessionManager()
11+
self._load_saved_session()
12+
13+
def _load_saved_session(self):
14+
"""Try to load and validate saved session"""
15+
saved_session = self.session_manager.load_session()
16+
if saved_session:
17+
result = self.login_with_session(saved_session['session_token'])
18+
return result["success"]
19+
return False
20+
21+
def login_with_session(self, leetcode_session: str) -> Dict[str, any]: # type: ignore
22+
"""
23+
Login to LeetCode using LEETCODE_SESSION token.
24+
Returns a dictionary with success status and message.
25+
"""
26+
try:
27+
self.session.cookies.set('LEETCODE_SESSION', leetcode_session, domain='leetcode.com')
28+
29+
response = self.session.get(
30+
f"{self.BASE_URL}/api/problems/all/",
31+
headers={
32+
'Accept': 'application/json',
33+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
34+
}
35+
)
36+
37+
if response.status_code == 200:
38+
user_data = response.json()
39+
if user_data.get('user_name'):
40+
self.is_authenticated = True
41+
# Save the valid session
42+
self.session_manager.save_session(leetcode_session, user_data['user_name'])
43+
return {
44+
"success": True,
45+
"message": "Successfully logged in",
46+
"user_name": user_data['user_name']
47+
}
48+
else:
49+
self.session_manager.clear_session()
50+
return {
51+
"success": False,
52+
"message": "Invalid session token"
53+
}
54+
else:
55+
self.session_manager.clear_session()
56+
return {
57+
"success": False,
58+
"message": f"Login failed with status code: {response.status_code}"
59+
}
60+
61+
except Exception as e:
62+
self.session_manager.clear_session()
63+
return {"success": False, "message": f"Login error: {str(e)}"}
64+
65+
def get_session(self) -> requests.Session:
66+
"""Return the authenticated session."""
67+
return self.session

src/server/session_manager.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import json
2+
import os
3+
from pathlib import Path
4+
from typing import Optional, Dict
5+
6+
class SessionManager:
7+
def __init__(self):
8+
# Create config directory in user's home directory
9+
self.config_dir = Path.home() / '.leetcode-cli'
10+
self.config_file = self.config_dir / 'session.json'
11+
self._ensure_config_dir()
12+
13+
def _ensure_config_dir(self):
14+
"""Ensure the config directory exists"""
15+
self.config_dir.mkdir(parents=True, exist_ok=True)
16+
17+
def save_session(self, session_token: str, user_name: str):
18+
"""Save the session token and username to file"""
19+
data = {
20+
'session_token': session_token,
21+
'user_name': user_name
22+
}
23+
with open(self.config_file, 'w') as f:
24+
json.dump(data, f)
25+
26+
def load_session(self) -> Optional[Dict[str, str]]:
27+
"""Load the session token and username from file"""
28+
try:
29+
if self.config_file.exists():
30+
with open(self.config_file, 'r') as f:
31+
return json.load(f)
32+
except Exception:
33+
pass
34+
return None
35+
36+
def clear_session(self):
37+
"""Clear the stored session"""
38+
if self.config_file.exists():
39+
self.config_file.unlink()

0 commit comments

Comments
 (0)