From 3576a47c6b26a20403d1ff93059dadacf98cc810 Mon Sep 17 00:00:00 2001 From: Jen-Chieh Date: Wed, 28 Oct 2020 13:31:19 +0800 Subject: [PATCH 1/5] Update doc. --- README.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4ab6669..7fb710e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ## Examples -Below is an simple example that how you can use this library for calling +Below is an simple example that how you can use this library for calling Grammarly API interface. ```el @@ -27,15 +27,25 @@ Grammarly API interface. (grammarly-check-text "Hello World") ``` +## Using a Paid Grammarly Account + +You will need the set the following variable in order to use paid version +of Grammarly! + +```el +(setq grammarly-username "") ; Your Grammarly Username +(setq grammarly-password "") ; Your Grammarly Password +``` + ## References * [grammarly-api](https://github.com/dexterleng/grammarly-api) * [reverse-engineered-grammarly-api](https://github.com/c0nn3r/reverse-engineered-grammarly-api) +* [grammarly (vscode)](https://github.com/znck/grammarly) ## Todo List - [ ] Support multiple requests at the same time. -- [ ] Support login and premium account. ## Contribution From 0a19d43f337ef1806fad410a28e60127796d78a4 Mon Sep 17 00:00:00 2001 From: Jen-Chieh Date: Wed, 28 Oct 2020 13:31:31 +0800 Subject: [PATCH 2/5] Update core for premium. --- grammarly.el | 178 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 146 insertions(+), 32 deletions(-) diff --git a/grammarly.el b/grammarly.el index 75de3c0..b4fc483 100644 --- a/grammarly.el +++ b/grammarly.el @@ -45,10 +45,32 @@ :group 'text :link '(url-link :tag "Github" "https://github.com/jcs-elpa/grammarly")) +(defcustom grammarly-username "" + "Grammarly login username." + :type 'string + :group 'grammarly) + +(defcustom grammarly-password "" + "Grammarly login password." + :type 'string + :group 'grammarly) + +(defconst grammarly--user-agent + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0" + "User agent.") + +(defconst grammarly--browser-header + `(("User-Agent" . ,grammarly--user-agent) + ("Accept" . "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") + ("Accept-Language" . "en-GB,en-US;q=0.9,en;q=0.8") + ("Cache-Control" . "no-cache") + ("Pragma" . "no-cache")) + "Header for simulate using a browser.") + (defconst grammarly--authorize-msg - '(("origin" . "chrome-extension://kbfnbcaeplbcioakkpcpgfkobkghlhen") + `(("origin" . "chrome-extension://kbfnbcaeplbcioakkpcpgfkobkghlhen") ("Cookie" . "$COOKIES$") - ("User-Agent" . "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:68.0) Gecko/20100101 Firefox/68.0")) + ("User-Agent" . ,grammarly--user-agent)) "Authorize message for Grammarly API.") (defconst grammarly--init-msg @@ -100,6 +122,25 @@ (defvar grammarly--timer nil "Universal timer for each await use.") +(defvar grammarly--start-checking-p nil + "Flag to after we are done preparing; basically after authentication process.") + +;; +;; (@* "Util" ) +;; + +(defun grammarly--kill-websocket () + "Kill the websocket." + (when grammarly--client + (websocket-close grammarly--client) + (setq grammarly--client nil))) + +(defun grammarly--kill-timer () + "Kill the timer." + (when (timerp grammarly--timer) + (cancel-timer grammarly--timer) + (setq grammarly--timer nil))) + (defun grammarly--execute-function-list (lst &rest args) "Execute all function LST with ARGS." (cond @@ -107,38 +148,118 @@ ((listp lst) (dolist (fnc lst) (apply fnc args))) (t (user-error "[ERROR] Function does not exists: %s" lst)))) +;; +;; (@* "Cookie" ) +;; + +(defvar grammarly--auth-cookie '() + "Authorization cookie container.") + (defun grammarly--last-cookie (cookie cookies) "Check if current COOKIE the last cookie from COOKIES." (equal (nth (1- (length cookies)) cookies) cookie)) +(defun grammarly--get-cookie-by-name (name) + "Return cookie value by cookie NAME." + (let ((len (length grammarly--auth-cookie)) (index 0) break cookie-val) + (while (and (not break) (< index len)) + (let* ((cookie (nth index grammarly--auth-cookie)) + (cookie-name (car cookie))) + (when (string= cookie-name name) + (setq cookie-val (cdr cookie)) + (setq break t))) + (setq index (1+ index))) + cookie-val)) + (defun grammarly--form-cookie () "Form all cookies into one string." + (setq grammarly--auth-cookie '()) (let ((sec-cookies (request-cookie-alist ".grammarly.com" "/" t)) - (cookie-str "")) + (cookie-str "") cookie-name cookie-val) (dolist (cookie sec-cookies) - (setq cookie-str - (format "%s %s=%s%s" cookie-str (car cookie) (cdr cookie) - (if (grammarly--last-cookie cookie sec-cookies) "" ";")))) + (setq cookie-name (car cookie) cookie-val (cdr cookie) + cookie-str + (format "%s %s=%s%s" cookie-str cookie-name cookie-val + (if (grammarly--last-cookie cookie sec-cookies) "" ";"))) + (push (cons cookie-name cookie-val) grammarly--auth-cookie)) + (setq grammarly--auth-cookie (reverse grammarly--auth-cookie)) (string-trim cookie-str))) +(defun grammarly--update-cookie () + "Refresh the cookie once." + (setq grammarly--cookies (grammarly--form-cookie))) + (defun grammarly--get-cookie () "Get cookie." (setq grammarly--cookies "") ; Reset to clean string. (request - "https://grammarly.com/" - :type "GET" - :headers - '(("User-Agent" . ()) - ("Accept" . "application/json, text/plain, */*")) - :success - (cl-function - (lambda (&key _response &allow-other-keys) - (setq grammarly--cookies (grammarly--form-cookie)))) - :error - ;; NOTE: Accept, error. - (cl-function - (lambda (&rest args &key _error-thrown &allow-other-keys) - (user-error "[ERROR] Error while getting cookie"))))) + "https://grammarly.com/signin" + :type "GET" + :headers + (append grammarly--browser-header + '(("Sec-Fetch-Mode" . "navigate") + ("Sec-Fetch-Sit" . "same-origin") + ("Sec-Fetch-User" . "?1") + ("Upgrade-Insecure-Requests" . "1") + ("Referer" . "https://www.grammarly.com/"))) + :success + (cl-function + (lambda (&key _response &allow-other-keys) + (grammarly--update-cookie) + (if (grammarly-premium-p) ; Try login to use paid version. + (grammarly--authenticate) + (setq grammarly--start-checking-p t)))) + :error + ;; NOTE: Accept, error. + (cl-function + (lambda (&rest args &key _error-thrown &allow-other-keys) + (message "[ERROR] Error while getting cookie: %s" args))))) + +;; +;; (@* "Login" ) +;; + +(defun grammarly-premium-p () + "Return non-nil means we are using premium version." + (and (not (string-empty-p grammarly-username)) + (not (string-empty-p grammarly-password)))) + +(defun grammarly--authenticate () + "Login to Grammarly for premium version." + (message "connecting as %s" grammarly-username) + (request + "https://auth.grammarly.com/v3/api/login" + :type "POST" + :headers + `(("accept" . "application/json") + ("accept-language" . "en-GB,en-US;q=0.9,en;q=0.8") + ("content-type" . "application/json") + ("user-agent" . ,grammarly--user-agent) + ("x-client-type" . "funnel") + ("x-client-version" . "1.2.2026") + ("x-container-id" . ,(grammarly--get-cookie-by-name "gnar_containerId")) + ("x-csrf-token" . ,(grammarly--get-cookie-by-name "csrf-token")) + ("sec-fetch-site" . "same-site") + ("sec-fetch-mode" . "cors") + ("cookie" . ,grammarly--cookies)) + :data + (json-encode + `(("email_login" . (("email" . ,grammarly-username) + ("password" . ,grammarly-password) + ("secureLogin" . "false"))))) + :success + (cl-function + (lambda (&key response &allow-other-keys) + (setq grammarly--start-checking-p t))) + :error + ;; NOTE: Accept, error. + (cl-function + (lambda (&rest args &key _error-thrown &allow-other-keys) + (message "[ERROR] Error while authenticating login: %s" args))))) + +;; +;; (@* "WebSocket" ) +;; (defun grammarly--form-authorize-list () "Form the authorize list." @@ -180,17 +301,9 @@ (lambda (_ws) (grammarly--execute-function-list grammarly-on-close-function-list))))) -(defun grammarly--kill-websocket () - "Kill the websocket." - (when grammarly--client - (websocket-close grammarly--client) - (setq grammarly--client nil))) - -(defun grammarly--kill-timer () - "Kill the timer." - (when (timerp grammarly--timer) - (cancel-timer grammarly--timer) - (setq grammarly--timer nil))) +;; +;; (@* "Core" ) +;; (defun grammarly--reset-timer (fnc pred) "Reset the timer for the next run with FNC and PRED." @@ -214,8 +327,9 @@ (user-error "[ERROR] Text can't be 'nil' or 'empty'") (setq grammarly--text text) (grammarly--get-cookie) + ;; Delay, until we get the initial cookie. (grammarly--reset-timer #'grammarly--after-got-cookie - '(lambda () (string-empty-p grammarly--cookies))))) + '(lambda () grammarly--start-checking-p)))) (provide 'grammarly) ;;; grammarly.el ends here From 1c3ded2425a8e947109a58d345a99d3c2565cd5d Mon Sep 17 00:00:00 2001 From: Jen-Chieh Date: Wed, 28 Oct 2020 13:43:09 +0800 Subject: [PATCH 3/5] Reset flag. --- grammarly.el | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/grammarly.el b/grammarly.el index b4fc483..93c5ab8 100644 --- a/grammarly.el +++ b/grammarly.el @@ -191,7 +191,8 @@ (defun grammarly--get-cookie () "Get cookie." - (setq grammarly--cookies "") ; Reset to clean string. + (setq grammarly--start-checking-p nil + grammarly--cookies "") ; Reset to clean string. (request "https://grammarly.com/signin" :type "GET" @@ -255,6 +256,7 @@ ;; NOTE: Accept, error. (cl-function (lambda (&rest args &key _error-thrown &allow-other-keys) + (setq grammarly--start-checking-p t) ; Go back and use anonymous version (message "[ERROR] Error while authenticating login: %s" args))))) ;; @@ -282,8 +284,7 @@ grammarly--client (websocket-open "wss://capi.grammarly.com/freews" - :custom-header-alist - (grammarly--form-authorize-list) + :custom-header-alist (grammarly--form-authorize-list) :on-open (lambda (_ws) (grammarly--execute-function-list grammarly-on-open-function-list) From e2ef5886600a0e5a37a951db0ef5a9a69337f49b Mon Sep 17 00:00:00 2001 From: Jen-Chieh Date: Wed, 28 Oct 2020 14:03:50 +0800 Subject: [PATCH 4/5] Compatible to anynomous version. --- grammarly.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/grammarly.el b/grammarly.el index 93c5ab8..fd79f6a 100644 --- a/grammarly.el +++ b/grammarly.el @@ -60,7 +60,7 @@ "User agent.") (defconst grammarly--browser-header - `(("User-Agent" . ,grammarly--user-agent) + `(("User-Agent" . ()) ("Accept" . "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") ("Accept-Language" . "en-GB,en-US;q=0.9,en;q=0.8") ("Cache-Control" . "no-cache") @@ -250,7 +250,7 @@ ("secureLogin" . "false"))))) :success (cl-function - (lambda (&key response &allow-other-keys) + (lambda (&key _response &allow-other-keys) (setq grammarly--start-checking-p t))) :error ;; NOTE: Accept, error. @@ -330,7 +330,7 @@ (grammarly--get-cookie) ;; Delay, until we get the initial cookie. (grammarly--reset-timer #'grammarly--after-got-cookie - '(lambda () grammarly--start-checking-p)))) + '(lambda () (null grammarly--start-checking-p))))) (provide 'grammarly) ;;; grammarly.el ends here From d682edd4937f3985e729071e9fd8e81c0e7dddd5 Mon Sep 17 00:00:00 2001 From: Jen-Chieh Date: Wed, 28 Oct 2020 14:10:11 +0800 Subject: [PATCH 5/5] Added debug log. --- grammarly.el | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/grammarly.el b/grammarly.el index fd79f6a..c94cc82 100644 --- a/grammarly.el +++ b/grammarly.el @@ -125,10 +125,18 @@ (defvar grammarly--start-checking-p nil "Flag to after we are done preparing; basically after authentication process.") +(defvar grammarly--show-debug-message nil + "Flag to see if we show debug messages.") + ;; ;; (@* "Util" ) ;; +(defun grammarly--debug-message (fmt &rest args) + "Debug message like function `message' with same argument FMT and ARGS." + (when grammarly--show-debug-message + (apply 'message fmt args))) + (defun grammarly--kill-websocket () "Kill the websocket." (when grammarly--client @@ -214,7 +222,7 @@ ;; NOTE: Accept, error. (cl-function (lambda (&rest args &key _error-thrown &allow-other-keys) - (message "[ERROR] Error while getting cookie: %s" args))))) + (grammarly--debug-message "[ERROR] Error while getting cookie: %s" args))))) ;; ;; (@* "Login" ) @@ -257,7 +265,8 @@ (cl-function (lambda (&rest args &key _error-thrown &allow-other-keys) (setq grammarly--start-checking-p t) ; Go back and use anonymous version - (message "[ERROR] Error while authenticating login: %s" args))))) + (grammarly--debug-message + "[ERROR] Error while authenticating login: %s" args))))) ;; ;; (@* "WebSocket" ) @@ -297,7 +306,8 @@ (grammarly--default-callback (websocket-frame-payload frame))) :on-error (lambda (_ws _type err) - (user-error "[ERROR] Connection error while opening websocket: %s" err)) + (grammarly--debug-message + "[ERROR] Connection error while opening websocket: %s" err)) :on-close (lambda (_ws) (grammarly--execute-function-list grammarly-on-close-function-list)))))