Skip to content

Commit 43e8bb9

Browse files
committed
refactor
1 parent 8d236a3 commit 43e8bb9

16 files changed

+218
-213
lines changed
Lines changed: 6 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,10 @@
11
import json
2-
import time
3-
from http.cookiejar import Cookie
4-
from typing import cast
52

6-
import browser_cookie3
73
import tornado
8-
from tornado.httpclient import AsyncHTTPClient
9-
from tornado.httputil import HTTPHeaders
104

11-
from ..utils.utils import first
5+
from ..utils.utils import get_leetcode_cookie
126
from .base_handler import BaseHandler
137

14-
BROWSER_COOKIE_METHOD_MAP = {
15-
"chrome": browser_cookie3.chrome,
16-
"chromium": browser_cookie3.chromium,
17-
"opera": browser_cookie3.opera,
18-
"opera_gx": browser_cookie3.opera_gx,
19-
"brave": browser_cookie3.brave,
20-
"edge": browser_cookie3.edge,
21-
"vivaldi": browser_cookie3.vivaldi,
22-
"firefox": browser_cookie3.firefox,
23-
"librewolf": browser_cookie3.librewolf,
24-
"safari": browser_cookie3.safari,
25-
"arc": browser_cookie3.arc,
26-
}
27-
288

299
class GetCookieHandler(BaseHandler):
3010
route = r"cookies"
@@ -33,71 +13,12 @@ class GetCookieHandler(BaseHandler):
3313
def get(self):
3414
self.log.debug("Loading all cookies for LeetCode...")
3515
browser = self.get_query_argument("browser", "", strip=True)
36-
if not browser:
37-
self.set_status(400)
38-
self.finish(json.dumps({"message": "Browser parameter is required"}))
39-
return
40-
41-
if browser not in BROWSER_COOKIE_METHOD_MAP:
42-
self.set_status(400)
43-
self.finish(json.dumps({"message": f"Unsupported browser: {browser}"}))
44-
return
45-
4616
try:
47-
cj = BROWSER_COOKIE_METHOD_MAP[browser](domain_name="leetcode.com")
48-
except browser_cookie3.BrowserCookieError as e:
49-
self.set_status(418)
50-
self.finish(
51-
json.dumps(
52-
{
53-
"message": "Failed to retrieve cookies. Maybe not installed the browser?"
54-
}
55-
)
17+
cookie = get_leetcode_cookie(
18+
browser, self.settings, self.request.headers.get("User-Agent", "")
5619
)
57-
return
20+
self.set_cookie("leetcode_browser", browser, expires=cookie["expires"])
21+
self.finish(json.dumps(cookie))
5822
except Exception as e:
59-
self.set_status(418)
23+
self.set_status(400)
6024
self.finish(json.dumps({"message": str(e.args)}))
61-
return
62-
63-
cookie_session = first(cj, lambda c: c.name == "LEETCODE_SESSION")
64-
cookie_csrf = first(cj, lambda c: c.name == "csrftoken")
65-
exist = bool(cookie_session and cookie_csrf)
66-
expired = exist and (
67-
cast(Cookie, cookie_session).is_expired()
68-
or cast(Cookie, cookie_csrf).is_expired()
69-
)
70-
checked = exist and not expired
71-
72-
resp = {"exist": exist, "expired": expired, "checked": checked}
73-
74-
if checked:
75-
cookie_session_expires = cast(Cookie, cookie_session).expires
76-
max_age = (
77-
cookie_session_expires - int(time.time())
78-
if cookie_session_expires is not None
79-
else 3600 * 24 * 14
80-
)
81-
self.set_cookie("leetcode_browser", browser, max_age=max_age)
82-
self.settings.update(
83-
leetcode_browser=browser,
84-
leetcode_cookiejar=cj,
85-
leetcode_headers=HTTPHeaders(
86-
{
87-
"Cookie": "; ".join(f"{c.name}={c.value}" for c in cj),
88-
"Content-Type": "application/json",
89-
"Origin": "https://leetcode.com",
90-
"Referer": "https://leetcode.com/",
91-
"X-CsrfToken": (
92-
cookie_csrf.value
93-
if cookie_csrf and cookie_csrf.value
94-
else ""
95-
),
96-
}
97-
),
98-
)
99-
AsyncHTTPClient.configure(
100-
None, defaults=dict(user_agent=self.request.headers.get("user-agent"))
101-
)
102-
103-
self.finish(json.dumps(resp))

jupyterlab_leetcode/handlers/leetcode_handler.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from tornado.websocket import WebSocketHandler
1212

1313
from ..utils.notebook_generator import NotebookGenerator
14-
from ..utils.utils import first, request
14+
from ..utils.utils import first, get_leetcode_cookie, request
1515
from .base_handler import BaseHandler
1616

1717
LEETCODE_URL = "https://leetcode.com"
@@ -22,6 +22,21 @@
2222
class LeetCodeHandler(BaseHandler):
2323
"""Base handler for LeetCode-related requests."""
2424

25+
async def prepare(self) -> None:
26+
await super().prepare()
27+
if not self.settings.get("leetcode_headers"):
28+
browser = self.get_cookie("leetcode_browser")
29+
if not browser:
30+
self.set_status(400)
31+
self.finish(
32+
json.dumps({"message": "LeetCode browser cookie is required"})
33+
)
34+
return
35+
36+
get_leetcode_cookie(
37+
browser, self.settings, self.request.headers.get("User-Agent", "")
38+
)
39+
2540
@overload
2641
async def graphql(self, name: str, query: Mapping[str, Any]) -> None: ...
2742

@@ -187,7 +202,7 @@ async def post(self):
187202
body = cast("dict[str, str|int]", body)
188203
skip = cast(int, body.get("skip", 0))
189204
limit = cast(int, body.get("limit", 0))
190-
keyword = cast(str, body.get("keyword", ""))
205+
query = cast("dict[str, Any]", body.get("query", ""))
191206
sortField = cast(str, body.get("sortField", "CUSTOM"))
192207
sortOrder = cast(str, body.get("sortOrder", "ASCENDING"))
193208

@@ -234,7 +249,7 @@ async def post(self):
234249
"variables": {
235250
"skip": skip,
236251
"limit": limit,
237-
"searchKeyword": keyword,
252+
"searchKeyword": query["keyword"],
238253
"categorySlug": "algorithms",
239254
"filters": {
240255
"filterCombineType": "ALL",

jupyterlab_leetcode/utils/utils.py

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import json
2+
import time
23
from collections.abc import Callable, Iterable, Mapping
3-
from typing import Any, TypeVar
4+
from http.cookiejar import Cookie
5+
from typing import Any, TypeVar, cast
46

7+
import browser_cookie3
58
from tornado.concurrent import Future
69
from tornado.httpclient import AsyncHTTPClient, HTTPRequest, HTTPResponse
710
from tornado.httputil import HTTPHeaders
@@ -26,3 +29,61 @@ def request(
2629
)
2730

2831
return client.fetch(req)
32+
33+
34+
BROWSER_COOKIE_METHOD_MAP = {
35+
"chrome": browser_cookie3.chrome,
36+
"chromium": browser_cookie3.chromium,
37+
"opera": browser_cookie3.opera,
38+
"opera_gx": browser_cookie3.opera_gx,
39+
"brave": browser_cookie3.brave,
40+
"edge": browser_cookie3.edge,
41+
"vivaldi": browser_cookie3.vivaldi,
42+
"firefox": browser_cookie3.firefox,
43+
"librewolf": browser_cookie3.librewolf,
44+
"safari": browser_cookie3.safari,
45+
"arc": browser_cookie3.arc,
46+
}
47+
48+
49+
def get_leetcode_cookie(browser: str, settings: dict[str, Any], ua: str):
50+
if not browser:
51+
raise ValueError("Browser parameter is required")
52+
53+
if browser not in BROWSER_COOKIE_METHOD_MAP:
54+
raise ValueError(f"Unsupported browser: {browser}")
55+
try:
56+
cj = BROWSER_COOKIE_METHOD_MAP[browser](domain_name="leetcode.com")
57+
except browser_cookie3.BrowserCookieError as e:
58+
raise Exception(
59+
"Failed to retrieve cookies. Maybe not installed the browser?"
60+
) from e
61+
except Exception as e:
62+
raise Exception(f"An error occurred: {str(e)}") from e
63+
64+
cookie_session = first(cj, lambda c: c.name == "LEETCODE_SESSION")
65+
cookie_csrf = first(cj, lambda c: c.name == "csrftoken")
66+
exist = bool(cookie_session and cookie_csrf)
67+
expired = exist and (
68+
cast(Cookie, cookie_session).is_expired()
69+
or cast(Cookie, cookie_csrf).is_expired()
70+
)
71+
checked = exist and not expired
72+
73+
expires = cast(Cookie, cookie_session).expires
74+
75+
settings.update(
76+
leetcode_headers=HTTPHeaders(
77+
{
78+
"Cookie": "; ".join(f"{c.name}={c.value}" for c in cj),
79+
"Content-Type": "application/json",
80+
"Origin": "https://leetcode.com",
81+
"Referer": "https://leetcode.com/",
82+
"X-CsrfToken": (
83+
cookie_csrf.value if cookie_csrf and cookie_csrf.value else ""
84+
),
85+
}
86+
),
87+
)
88+
AsyncHTTPClient.configure(None, defaults=dict(user_agent=ua))
89+
return {"exist": exist, "expired": expired, "checked": checked, "expires": expires}

src/components/BrowserMenu.tsx

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from 'react';
1+
import React from 'react';
22
import { Notification } from '@jupyterlab/apputils';
33
import {
44
IconChevronDown,
@@ -14,115 +14,105 @@ import {
1414
import { Button, Menu, useMantineTheme } from '@mantine/core';
1515
import { getCookie } from '../services/cookie';
1616

17+
const BrowserIconProps = { size: 16, stroke: 1.5 };
18+
1719
const Browsers = [
1820
{
1921
name: 'Chrome',
2022
icon: (color: string) => (
21-
<IconBrandChrome size={16} color={color} stroke={1.5} />
23+
<IconBrandChrome color={color} {...BrowserIconProps} />
2224
)
2325
},
2426
{
2527
name: 'Firefox',
2628
icon: (color: string) => (
27-
<IconBrandFirefox size={16} color={color} stroke={1.5} />
29+
<IconBrandFirefox color={color} {...BrowserIconProps} />
2830
)
2931
},
3032
{
3133
name: 'Safari',
3234
icon: (color: string) => (
33-
<IconBrandSafari size={16} color={color} stroke={1.5} />
35+
<IconBrandSafari color={color} {...BrowserIconProps} />
3436
)
3537
},
3638
{
3739
name: 'Edge',
3840
icon: (color: string) => (
39-
<IconBrandEdge size={16} color={color} stroke={1.5} />
41+
<IconBrandEdge color={color} {...BrowserIconProps} />
4042
)
4143
},
4244
{
4345
name: 'Opera',
4446
icon: (color: string) => (
45-
<IconBrandOpera size={16} color={color} stroke={1.5} />
47+
<IconBrandOpera color={color} {...BrowserIconProps} />
4648
)
4749
},
4850
{
4951
name: 'Brave',
5052
icon: (color: string) => (
51-
<IconWorldWww size={16} color={color} stroke={1.5} />
53+
<IconWorldWww color={color} {...BrowserIconProps} />
5254
)
5355
},
5456
{
5557
name: 'Vivaldi',
5658
icon: (color: string) => (
57-
<IconBrandVivaldi size={16} color={color} stroke={1.5} />
59+
<IconBrandVivaldi color={color} {...BrowserIconProps} />
5860
)
5961
},
6062
{
6163
name: 'Chromium',
6264
icon: (color: string) => (
63-
<IconBrandChrome size={16} color={color} stroke={1.5} />
65+
<IconBrandChrome color={color} {...BrowserIconProps} />
6466
)
6567
},
6668
{
6769
name: 'Arc',
6870
icon: (color: string) => (
69-
<IconBrandArc size={16} color={color} stroke={1.5} />
71+
<IconBrandArc color={color} {...BrowserIconProps} />
7072
)
7173
},
7274
{
7375
name: 'LibreWolf',
7476
icon: (color: string) => (
75-
<IconWorldWww size={16} color={color} stroke={1.5} />
77+
<IconWorldWww color={color} {...BrowserIconProps} />
7678
)
7779
},
7880
{
7981
name: 'Opera GX',
8082
icon: (color: string) => (
81-
<IconBrandOpera size={16} color={color} stroke={1.5} />
83+
<IconBrandOpera color={color} {...BrowserIconProps} />
8284
)
8385
}
8486
];
8587

86-
const normalizeBrowserName = (name: string) =>
87-
name.toLowerCase().replace(/\s+/g, '_');
88-
8988
const BrowserMenu: React.FC<{
90-
className?: string;
91-
setCookieLoggedIn: (b: string) => void;
92-
}> = ({ className, setCookieLoggedIn }) => {
93-
const [browser, setBrowser] = useState('');
94-
const [checked, setChecked] = useState(false);
95-
96-
useEffect(() => {
89+
onCheckSuccess: () => void;
90+
}> = ({ onCheckSuccess }) => {
91+
const checkBrowser = (browser: string) => {
9792
if (!browser) {
9893
return;
9994
}
100-
if (browser === 'safari') {
95+
if (browser === 'Safari') {
10196
Notification.error(
10297
'Safari does not support getting cookies from the browser. Please use another browser.',
10398
{ autoClose: 3000 }
10499
);
105100
return;
106101
}
107102

108-
getCookie(browser)
103+
getCookie(browser.toLowerCase().replace(/\s+/g, '_'))
109104
.then(resp => {
110105
if (!resp['checked']) {
111106
Notification.error(
112-
`Failed to check cookie for ${browser}. Please ensure you are logged in to LeetCode in this browser.`,
107+
`Failed to check cookie for ${browser}. Please ensure you are logged in to LeetCode in ${browser}.`,
113108
{ autoClose: 3000 }
114109
);
110+
return;
115111
}
116-
setChecked(resp['checked']);
112+
onCheckSuccess();
117113
})
118114
.catch(e => Notification.error(e.message, { autoClose: 3000 }));
119-
}, [browser]);
120-
121-
useEffect(() => {
122-
if (checked) {
123-
setCookieLoggedIn(browser);
124-
}
125-
}, [checked]);
115+
};
126116

127117
const theme = useMantineTheme();
128118
return (
@@ -139,7 +129,6 @@ const BrowserMenu: React.FC<{
139129
pr={12}
140130
radius="md"
141131
size="md"
142-
className={className}
143132
>
144133
Load from browser
145134
</Button>
@@ -150,7 +139,7 @@ const BrowserMenu: React.FC<{
150139
<Menu.Item
151140
key={name}
152141
leftSection={icon(theme.colors.blue[6])}
153-
onClick={() => setBrowser(normalizeBrowserName(name))}
142+
onClick={() => checkBrowser(name)}
154143
>
155144
{name}
156145
</Menu.Item>

0 commit comments

Comments
 (0)