Skip to content

Commit cab4f6d

Browse files
committed
feat: use aio when login and fetch problems, kaiwk#33
1 parent 5302e70 commit cab4f6d

File tree

2 files changed

+118
-102
lines changed

2 files changed

+118
-102
lines changed

Cask

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
(source gnu)
12
(source melpa)
23

34
(package-file "leetcode.el")
@@ -9,4 +10,5 @@
910
(depends-on "request")
1011
(depends-on "request-deferred")
1112
(depends-on "graphql")
12-
(depends-on "spinner"))
13+
(depends-on "spinner")
14+
(depends-on "aio"))

leetcode.el

Lines changed: 115 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@
3939
(require 'shr)
4040

4141
(require 'seq)
42+
(require 'mm-url)
4243
(require 'request)
4344
(require 'request-deferred) ; Asynchronous HTTP request
4445
(require 'graphql) ; Some requests of LeetCode use GraphQL
46+
(require 'aio)
4547

4648
(require 'spinner)
4749

@@ -76,7 +78,7 @@ The elements of :problems has attributes:
7678
:paid-only Boolean {t|nil}")
7779

7880
(defvar leetcode--problem-titles nil
79-
"Problem titles that have been open in solving layout.")
81+
"Problem titles that have been open in solving layout.")
8082

8183
(defvar leetcode-checkmark "" "Checkmark for accepted problem.")
8284
(defconst leetcode--buffer-name "*leetcode*")
@@ -128,29 +130,35 @@ The elements of :problems has attributes:
128130
(defconst leetcode--api-try (concat leetcode--base-url "/problems/%s/interpret_solution/"))
129131

130132

131-
(defun leetcode--referer (value)
133+
(defsubst leetcode--referer (value)
132134
"It will return an alist as the HTTP Referer Header.
133135
VALUE should be the referer."
134136
(cons "Referer" value))
135137

136-
(defun leetcode--csrf-token ()
138+
(defun leetcode--maybe-csrf-token ()
139+
"Return csrf token if it exists, otherwise return nil."
140+
(if-let ((cookie (seq-find
141+
(lambda (item)
142+
(string= (aref item 1)
143+
"csrftoken"))
144+
(url-cookie-retrieve leetcode--domain "/" t))))
145+
(aref cookie 2)))
146+
147+
(aio-defun leetcode--csrf-token ()
137148
"Return csrf token."
138-
(let ((token (assoc-default
139-
"csrftoken"
140-
(request-cookie-alist leetcode--domain "/" t))))
141-
(or token
142-
(progn
143-
(request leetcode--url-login :sync t)
144-
(leetcode--csrf-token)))))
149+
(unless (leetcode--maybe-csrf-token)
150+
(aio-await (aio-url-retrieve leetcode--url-login)))
151+
(leetcode--maybe-csrf-token))
145152

146153
(defun leetcode--credentials ()
147154
(let ((auth-source-creation-prompts
148155
'((user . "LeetCode user: ")
149156
(secret . "LeetCode password for %u: ")))
150-
(found (car (auth-source-search :max 1
151-
:host leetcode--domain
152-
:require '(:user :secret)
153-
:create t))))
157+
(found (car (auth-source-search
158+
:max 1
159+
:host leetcode--domain
160+
:require '(:user :secret)
161+
:create t))))
154162
(if found
155163
(list (plist-get found :user)
156164
(let ((secret (plist-get found :secret)))
@@ -159,47 +167,56 @@ VALUE should be the referer."
159167
secret))
160168
(plist-get found :save-function)))))
161169

162-
(defun leetcode--login ()
170+
(defsubst leetcode--multipart-form-data (name value)
171+
`("file"
172+
("name" . ,name)
173+
("filedata" . ,value)
174+
("filename" . "")
175+
("content-type" . "")))
176+
177+
(aio-defun leetcode--login ()
163178
"Send login request and return a deferred object.
164179
When ACCOUNT or PASSWORD is empty string it will show a prompt."
180+
(leetcode--loading-mode t)
165181
(let* ((credentials (leetcode--credentials))
166182
(account (nth 0 credentials))
167183
(password (nth 1 credentials))
168-
(save-func (nth 2 credentials)))
169-
(leetcode--loading-mode t)
170-
(request-deferred
171-
leetcode--url-login
172-
:type "POST"
173-
:headers `(,leetcode--User-Agent
174-
,leetcode--X-Requested-With
175-
,(leetcode--referer leetcode--url-login)
176-
,(cons leetcode--X-CSRFToken (leetcode--csrf-token)))
177-
:parser 'buffer-string
178-
:files `(("csrfmiddlewaretoken" . ("" :data ,(leetcode--csrf-token)))
179-
("login" . ("" :data ,account))
180-
("password" . ("" :data ,password)))
181-
:success
182-
(cl-function
183-
(lambda (&key data &allow-other-keys)
184-
(leetcode--loading-mode -1)
185-
(when (functionp save-func)
186-
(funcall save-func))))
187-
:error
188-
(cl-function
189-
(lambda (&rest args &key error-thrown &allow-other-keys)
190-
(leetcode--loading-mode -1)
191-
(message "LeetCode Login ERROR: %S" error-thrown)
192-
(auth-source-forget+ :host leetcode--domain))))))
184+
(save-func (nth 2 credentials))
185+
(boundary (mml-compute-boundary '()))
186+
(csrf-token (aio-await (leetcode--csrf-token)))
187+
(url-request-method "POST")
188+
(url-request-extra-headers
189+
`(("Content-Type" . ,(concat "multipart/form-data; boundary=" boundary))
190+
,leetcode--User-Agent
191+
,leetcode--X-Requested-With
192+
,(leetcode--referer leetcode--url-login)
193+
,(cons leetcode--X-CSRFToken csrf-token)))
194+
(url-request-data
195+
(mm-url-encode-multipart-form-data
196+
(list
197+
(leetcode--multipart-form-data "csrfmiddlewaretoken" csrf-token)
198+
(leetcode--multipart-form-data "login" account)
199+
(leetcode--multipart-form-data "password" password))
200+
boundary))
201+
(result (aio-await (aio-url-retrieve leetcode--url-login))))
202+
(if-let ((error-info (plist-get (car result) :error)))
203+
(progn
204+
(message "LeetCode Login ERROR: %S" error-info)
205+
(auth-source-forget+ :host leetcode--domain))
206+
(when (functionp save-func)
207+
(funcall save-func)))
208+
(leetcode--loading-mode -1)))
193209

194210
(defun leetcode--login-p ()
195211
"Whether user is login."
196212
(let ((username (plist-get leetcode--user :username)))
197213
(and username
198214
(not (string-empty-p username))
199-
(assoc-default
200-
"LEETCODE_SESSION"
201-
(request-cookie-alist
202-
(concat "." leetcode--domain) "/" t)))))
215+
(seq-find
216+
(lambda (item)
217+
(string= (aref item 1)
218+
"LEETCODE_SESSION"))
219+
(url-cookie-retrieve leetcode--domain "/" t)))))
203220

204221
(defun leetcode--set-user-and-problems (res)
205222
"Set `leetcode--user' and `leetcode--problems'.
@@ -351,58 +368,56 @@ Return a list of rows, each row is a vector:
351368
rows)))
352369
rows))
353370

354-
(defun leetcode-problems-refresh ()
371+
(aio-defun leetcode--fetch-user-and-problems ()
355372
"Refresh problems and update `tabulated-list-entries'."
356373
(interactive)
357374
(if leetcode--loading-mode
358-
(deferred:next
359-
(lambda ()
360-
(message "LeetCode has been refreshing...")))
375+
(message "LeetCode has been refreshing...")
361376
(leetcode--loading-mode t)
362-
(deferred:$
363-
(request-deferred
364-
leetcode--api-all-problems
365-
:headers `(,leetcode--User-Agent
366-
,leetcode--X-Requested-With
367-
,(leetcode--referer leetcode--url-login))
368-
:parser 'json-read)
369-
(deferred:nextc it
370-
(lambda (response)
371-
(leetcode--set-user-and-problems (request-response-data response))))
372-
(deferred:nextc it
373-
(lambda ()
374-
(let* ((header-names '(" " "#" "Problem" "Acceptance" "Difficulty"))
375-
(rows (leetcode--problems-rows))
376-
(headers (leetcode--make-tabulated-headers header-names rows)))
377-
(with-current-buffer (get-buffer-create leetcode--buffer-name)
378-
(leetcode--problems-mode)
379-
(setq tabulated-list-format headers)
380-
(setq tabulated-list-entries
381-
(-zip-with
382-
(lambda (i x) (list i x))
383-
(-iterate '1+ 0 (length rows))
384-
rows))
385-
(tabulated-list-init-header)
386-
(tabulated-list-print t)
387-
(leetcode--loading-mode -1))))))))
377+
(let ((url-request-method "GET")
378+
(url-request-extra-headers
379+
`(,leetcode--User-Agent
380+
,leetcode--X-Requested-With
381+
,(leetcode--referer leetcode--url-login)))
382+
(result (aio-await (aio-url-retrieve leetcode--api-all-problems))))
383+
(leetcode--loading-mode -1)
384+
(if-let ((error-info (plist-get (car result) :error)))
385+
(message "LeetCode login ERROR: %S" error-info)
386+
(with-current-buffer (cdr result)
387+
(goto-char url-http-end-of-headers)
388+
(json-read))))))
389+
390+
(aio-defun leetcode--refresh ()
391+
"Refresh problems and update `tabulated-list-entries'."
392+
(if-let ((users-and-problems
393+
(aio-await (leetcode--fetch-user-and-problems))))
394+
(leetcode--set-user-and-problems users-and-problems)
395+
(message "LeetCode parse user and problems failed"))
396+
(let* ((header-names '(" " "#" "Problem" "Acceptance" "Difficulty"))
397+
(rows (leetcode--problems-rows))
398+
(headers (leetcode--make-tabulated-headers header-names rows)))
399+
(with-current-buffer (get-buffer-create leetcode--buffer-name)
400+
(leetcode--problems-mode)
401+
(setq tabulated-list-format headers)
402+
(setq tabulated-list-entries
403+
(-zip-with
404+
(lambda (i x) (list i x))
405+
(-iterate '1+ 0 (length rows))
406+
rows))
407+
(tabulated-list-init-header)
408+
(tabulated-list-print t)
409+
(leetcode--loading-mode -1))))
388410

389411
;;;###autoload
390-
(defun leetcode ()
412+
(aio-defun leetcode ()
391413
"Show leetcode problems buffer."
392414
(interactive)
393415
(if (get-buffer leetcode--buffer-name)
394416
(switch-to-buffer leetcode--buffer-name)
395-
(deferred:$
396-
(if (leetcode--login-p)
397-
(deferred:next
398-
(lambda ()
399-
(message "User have been login in.")))
400-
(leetcode--login))
401-
(deferred:nextc it
402-
(lambda ()
403-
(deferred:nextc (leetcode-problems-refresh)
404-
(lambda ()
405-
(switch-to-buffer leetcode--buffer-name))))))))
417+
(unless (leetcode--login-p)
418+
(aio-await (leetcode--login)))
419+
(aio-await (leetcode--refresh))
420+
(switch-to-buffer leetcode--buffer-name)))
406421

407422
(defun leetcode--buffer-content (buf)
408423
"Get content without text properties of BUF."
@@ -693,21 +708,20 @@ Get current entry by using `tabulated-list-get-entry' and use
693708
(leetcode--problem-description-mode)
694709
(switch-to-buffer (current-buffer))))))
695710

696-
(defun leetcode--kill-buff-and-delete-window (buf)
697-
"Kill buff and delete its window"
698-
(delete-windows-on buf t)
699-
(kill-buffer buf))
700-
701-
(defun leetcode-quit ()
702-
"Close and delete leetcode related buffers and windows"
703-
(interactive)
704-
(leetcode--kill-buff-and-delete-window (get-buffer leetcode--buffer-name))
705-
(leetcode--kill-buff-and-delete-window (get-buffer leetcode--description-buffer-name))
706-
(leetcode--kill-buff-and-delete-window (get-buffer leetcode--result-buffer-name))
707-
(leetcode--kill-buff-and-delete-window (get-buffer leetcode--testcase-buffer-name))
708-
(mapc (lambda (x) (leetcode--kill-buff-and-delete-window (get-buffer (leetcode--get-code-buffer-name x))))
709-
leetcode--problem-titles)
710-
)
711+
(defun leetcode--kill-buff-and-delete-window (buf)
712+
"Kill buff and delete its window"
713+
(delete-windows-on buf t)
714+
(kill-buffer buf))
715+
716+
(defun leetcode-quit ()
717+
"Close and delete leetcode related buffers and windows"
718+
(interactive)
719+
(leetcode--kill-buff-and-delete-window (get-buffer leetcode--buffer-name))
720+
(leetcode--kill-buff-and-delete-window (get-buffer leetcode--description-buffer-name))
721+
(leetcode--kill-buff-and-delete-window (get-buffer leetcode--result-buffer-name))
722+
(leetcode--kill-buff-and-delete-window (get-buffer leetcode--testcase-buffer-name))
723+
(mapc (lambda (x) (leetcode--kill-buff-and-delete-window (get-buffer (leetcode--get-code-buffer-name x))))
724+
leetcode--problem-titles))
711725

712726
(defvar leetcode-prefer-language "python3"
713727
"LeetCode programming language.

0 commit comments

Comments
 (0)