diff --git a/gptel-gh.el b/gptel-gh.el index 7b974c4d..5fd74b1b 100644 --- a/gptel-gh.el +++ b/gptel-gh.el @@ -180,7 +180,7 @@ (cl-defstruct (gptel--gh (:include gptel-openai) (:copier nil) (:constructor gptel--make-gh)) - token github-token sessionid machineid) + sessionid machineid) (defcustom gptel-gh-github-token-file (expand-file-name ".cache/copilot-chat/github-token" user-emacs-directory) @@ -188,11 +188,11 @@ :type 'string :group 'gptel) -(defcustom gptel-gh-token-file (expand-file-name ".cache/copilot-chat/token" - user-emacs-directory) - "File where the chat token is cached." - :type 'string - :group 'gptel) +(defvar gptel-gh-github-token-load 'gptel--gh-restore-from-file + "Function to load the current github token.") + +(defvar gptel-gh-github-token-save 'gptel--gh-save-to-file + "Function to save the new github token.") (defconst gptel--gh-auth-common-headers `(("editor-plugin-version" . "gptel/*") @@ -200,6 +200,8 @@ (defconst gptel--gh-client-id "Iv1.b507a08c87ecfe98") +(defvar gptel--gh-token nil "Variable that holds the chat token in memory") + ;; https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random) (defun gptel--gh-uuid () "Generate a UUID v4-1." @@ -219,25 +221,25 @@ (setq hex (nconc hex (list (aref hex-chars (random 16)))))) (apply #'string hex))) -(defun gptel--gh-restore (file) - "Restore saved object from FILE." - (when (file-exists-p file) +(defun gptel--gh-restore-from-file () + "Restore saved object from gptel-gh-github-token-file." + (when (file-exists-p gptel-gh-github-token-file) ;; We set the coding system to `utf-8-auto-dos' when reading so that ;; files with CR EOL can still be read properly (let ((coding-system-for-read 'utf-8-auto-dos)) (with-temp-buffer (set-buffer-multibyte nil) - (insert-file-contents-literally file) + (insert-file-contents-literally gptel-gh-github-token-file) (goto-char (point-min)) (read (current-buffer)))))) -(defun gptel--gh-save (file obj) - "Save OBJ to FILE." +(defun gptel--gh-save-to-file (obj) + "Save OBJ to gptel--gh-save-to-file." (let ((print-length nil) (print-level nil) (coding-system-for-write 'utf-8-unix)) - (make-directory (file-name-directory file) t) - (write-region (prin1-to-string obj) nil file nil :silent) + (make-directory (file-name-directory gptel-gh-github-token-file) t) + (write-region (prin1-to-string obj) nil gptel-gh-github-token-file nil :silent) obj)) (defun gptel-gh-login () @@ -249,20 +251,7 @@ In SSH sessions, the URL and code will be displayed for manual entry instead of attempting to open a browser automatically." (interactive) ;; Determine which GitHub backend to use - (let ((gh-backend - (cond - ;; If current backend is GitHub, use it - ((and (boundp 'gptel-backend) - gptel-backend - (gptel--gh-p gptel-backend)) - gptel-backend) - ;; Otherwise, find any GitHub backend - ((cl-find-if (lambda (b) (gptel--gh-p b)) - (mapcar #'cdr gptel--known-backends))) - ;; No GitHub backend found - (t (user-error "No GitHub Copilot backend found. \ -Please set one up with `gptel-make-gh-copilot' first")))) - ;; Detect SSH sessions + (let (;; Detect SSH sessions (in-ssh-session (or (getenv "SSH_CLIENT") (getenv "SSH_CONNECTION") (getenv "SSH_TTY")))) @@ -287,28 +276,24 @@ in your local browser, enter the code, and authorize. Press ENTER after authori (format "Your one-time code %s is copied. \ Press ENTER to open GitHub in your browser. \ If your browser does not open automatically, browse to %s." - user_code verification_uri)) - (browse-url verification_uri) - (read-from-minibuffer "Press ENTER after authorizing. ")) - ;; Use gh-backend for token storage - (thread-last - (plist-get - (gptel--url-retrieve - "https://github.com/login/oauth/access_token" - :method 'post - :headers gptel--gh-auth-common-headers - :data `( :client_id ,gptel--gh-client-id - :device_code ,device_code - :grant_type "urn:ietf:params:oauth:grant-type:device_code")) - :access_token) - (gptel--gh-save gptel-gh-github-token-file) - (setf (gptel--gh-github-token gh-backend)))) - ;; Check gh-backend for success - (if (and (gptel--gh-github-token gh-backend) - (not (string-empty-p - (gptel--gh-github-token gh-backend)))) - (message "Successfully logged in to GitHub Copilot.") - (user-error "Error: You might not have access to GitHub Copilot Chat!")))) + user_code verification_uri)) + (browse-url verification_uri) + (read-from-minibuffer "Press ENTER after authorizing.")) + (thread-last + (plist-get + (gptel--url-retrieve + "https://github.com/login/oauth/access_token" + :method 'post + :headers gptel--gh-auth-common-headers + :data `( :client_id ,gptel--gh-client-id + :device_code ,device_code + :grant_type "urn:ietf:params:oauth:grant-type:device_code")) + :access_token) + (funcall gptel-gh-github-token-save)) + (let ((github-token (funcall gptel-gh-github-token-load))) + (if (and github-token (not (string-empty-p github-token))) + (message "Successfully logged in to GitHub Copilot") + (user-error "Error: You might not have access to GitHub Copilot Chat!")))))) (defun gptel--gh-renew-token () "Renew session token." @@ -317,38 +302,25 @@ If your browser does not open automatically, browse to %s." "https://api.github.com/copilot_internal/v2/token" :method 'get :headers `(("authorization" - . ,(format "token %s" (gptel--gh-github-token gptel-backend))) + . ,(format "token %s" (funcall gptel-gh-github-token-load))) ,@gptel--gh-auth-common-headers)))) (if (not (plist-get token :token)) - (progn - (setf (gptel--gh-github-token gptel-backend) nil) - (user-error "Error: You might not have access to GitHub Copilot Chat!")) - (thread-last - (gptel--gh-save gptel-gh-token-file token) - (setf (gptel--gh-token gptel-backend)))))) + (user-error "Error: You might not have access to GitHub Copilot Chat!") + (setq gptel--gh-token token)))) (defun gptel--gh-auth () "Authenticate with GitHub Copilot API. We first need github authorization (github token). Then we need a session token." - (unless (gptel--gh-github-token gptel-backend) - (let ((token (gptel--gh-restore gptel-gh-github-token-file))) - (if token - (setf (gptel--gh-github-token gptel-backend) token) - (gptel-gh-login)))) - - (when (null (gptel--gh-token gptel-backend)) - ;; try to load token from `gptel-gh-token-file' - (setf (gptel--gh-token gptel-backend) - (gptel--gh-restore gptel-gh-token-file))) + (unless (funcall gptel-gh-github-token-load) + (gptel-gh-login)) (pcase-let (((map :token :expires_at) - (gptel--gh-token gptel-backend))) + gptel--gh-token)) (when (or (null token) (and expires_at - (> (round (float-time (current-time))) - expires_at))) + (> (round (float-time)) expires_at))) (gptel--gh-renew-token)))) ;;;###autoload @@ -357,8 +329,7 @@ Then we need a session token." (header (lambda () (gptel--gh-auth) `(("openai-intent" . "conversation-panel") - ("authorization" . ,(concat "Bearer " - (plist-get (gptel--gh-token gptel-backend) :token))) + ("authorization" . ,(concat "Bearer " (plist-get gptel--gh-token :token))) ("x-request-id" . ,(gptel--gh-uuid)) ("vscode-sessionid" . ,(or (gptel--gh-sessionid gptel-backend) "")) ("vscode-machineid" . ,(or (gptel--gh-machineid gptel-backend) ""))