39
39
(require 'shr )
40
40
41
41
(require 'seq )
42
+ (require 'mm-url )
42
43
(require 'request )
43
44
(require 'request-deferred ) ; Asynchronous HTTP request
44
45
(require 'graphql ) ; Some requests of LeetCode use GraphQL
46
+ (require 'aio )
45
47
46
48
(require 'spinner )
47
49
@@ -76,7 +78,7 @@ The elements of :problems has attributes:
76
78
:paid-only Boolean {t|nil}" )
77
79
78
80
(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." )
80
82
81
83
(defvar leetcode-checkmark " ✓" " Checkmark for accepted problem." )
82
84
(defconst leetcode--buffer-name " *leetcode*" )
@@ -128,29 +130,35 @@ The elements of :problems has attributes:
128
130
(defconst leetcode--api-try (concat leetcode--base-url " /problems/%s/interpret_solution/" ))
129
131
130
132
131
- (defun leetcode--referer (value )
133
+ (defsubst leetcode--referer (value )
132
134
" It will return an alist as the HTTP Referer Header.
133
135
VALUE should be the referer."
134
136
(cons " Referer" value))
135
137
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 ()
137
148
" 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))
145
152
146
153
(defun leetcode--credentials ()
147
154
(let ((auth-source-creation-prompts
148
155
'((user . " LeetCode user: " )
149
156
(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 ))))
154
162
(if found
155
163
(list (plist-get found :user )
156
164
(let ((secret (plist-get found :secret )))
@@ -159,47 +167,56 @@ VALUE should be the referer."
159
167
secret))
160
168
(plist-get found :save-function )))))
161
169
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 ()
163
178
" Send login request and return a deferred object.
164
179
When ACCOUNT or PASSWORD is empty string it will show a prompt."
180
+ (leetcode--loading-mode t )
165
181
(let* ((credentials (leetcode--credentials))
166
182
(account (nth 0 credentials))
167
183
(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 )))
193
209
194
210
(defun leetcode--login-p ()
195
211
" Whether user is login."
196
212
(let ((username (plist-get leetcode--user :username )))
197
213
(and username
198
214
(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 )))))
203
220
204
221
(defun leetcode--set-user-and-problems (res )
205
222
" Set `leetcode--user' and `leetcode--problems' .
@@ -351,58 +368,56 @@ Return a list of rows, each row is a vector:
351
368
rows)))
352
369
rows))
353
370
354
- (defun leetcode-problems-refresh ()
371
+ (aio- defun leetcode--fetch-user-and-problems ()
355
372
" Refresh problems and update `tabulated-list-entries' ."
356
373
(interactive )
357
374
(if leetcode--loading-mode
358
- (deferred:next
359
- (lambda ()
360
- (message " LeetCode has been refreshing... " )))
375
+ (message " LeetCode has been refreshing... " )
361
376
(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 ))))
388
410
389
411
;;;### autoload
390
- (defun leetcode ()
412
+ (aio- defun leetcode ()
391
413
" Show leetcode problems buffer."
392
414
(interactive )
393
415
(if (get-buffer leetcode--buffer-name)
394
416
(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)))
406
421
407
422
(defun leetcode--buffer-content (buf )
408
423
" Get content without text properties of BUF."
@@ -693,21 +708,20 @@ Get current entry by using `tabulated-list-get-entry' and use
693
708
(leetcode--problem-description-mode)
694
709
(switch-to-buffer (current-buffer ))))))
695
710
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))
711
725
712
726
(defvar leetcode-prefer-language " python3"
713
727
" LeetCode programming language.
0 commit comments