summaryrefslogtreecommitdiff
path: root/elfeed-custom-feeds-github.el
diff options
context:
space:
mode:
Diffstat (limited to 'elfeed-custom-feeds-github.el')
-rw-r--r--elfeed-custom-feeds-github.el161
1 files changed, 95 insertions, 66 deletions
diff --git a/elfeed-custom-feeds-github.el b/elfeed-custom-feeds-github.el
index a857963..5bfdb6c 100644
--- a/elfeed-custom-feeds-github.el
+++ b/elfeed-custom-feeds-github.el
@@ -50,58 +50,83 @@
50 (message "Error fetching GitHub PRs %s [%s]: %s" r dn (plist-get status :error)) 50 (message "Error fetching GitHub PRs %s [%s]: %s" r dn (plist-get status :error))
51 (goto-char (point-min)) 51 (goto-char (point-min))
52 (re-search-forward "\n\n") 52 (re-search-forward "\n\n")
53 (let* ((json-object-type 'alist) 53 ;; Capture JSON text for async parsing
54 (json-array-type 'list) 54 (let ((json-text (buffer-substring-no-properties (point) (point-max))))
55 (response (json-read)) 55 ;; Parse JSON in idle timer to avoid blocking
56 (items (alist-get 'items response))) 56 (run-with-idle-timer 0 nil
57 (funcall cb r dn items)))) 57 (lambda ()
58 (condition-case err
59 (with-temp-buffer
60 (insert json-text)
61 (goto-char (point-min))
62 (let* ((json-object-type 'alist)
63 (json-array-type 'list)
64 (response (json-read))
65 (items (alist-get 'items response)))
66 (funcall cb r dn items)))
67 (error
68 (message "Error parsing JSON for GitHub %s [%s]: %s" r dn err)))))))
58 nil t))))) 69 nil t)))))
59 70
60(defun elfeed-custom-feeds-github--prs-to-elfeed (repo display-name prs completion-callback) 71(defun elfeed-custom-feeds-github--prs-to-elfeed (repo display-name prs completion-callback)
61 "Convert GitHub PRS to elfeed entries for REPO with DISPLAY-NAME. 72 "Convert GitHub PRS to elfeed entries for REPO with DISPLAY-NAME.
62Call COMPLETION-CALLBACK with (repo display-name new-entries) when done." 73Call COMPLETION-CALLBACK with (repo display-name new-entries) when done."
63 (let ((feed-url (format "https://github.com/%s/pulls" repo)) 74 (let ((feed-url (format "https://github.com/%s/pulls" repo))
64 (new-entries ())) 75 (new-entries ())
65 (let ((feed (elfeed-db-get-feed feed-url))) 76 (prs-to-process prs))
66 (setf (elfeed-feed-url feed) feed-url 77 ;; Set up feed metadata asynchronously
67 (elfeed-feed-title feed) (format "GitHub: %s [%s]" repo display-name))) 78 (run-with-idle-timer 0 nil
68 79 (lambda ()
69 (dolist (pr prs) 80 (let ((feed (elfeed-db-get-feed feed-url)))
70 (let* ((number (alist-get 'number pr)) 81 (setf (elfeed-feed-url feed) feed-url
71 (title-text (alist-get 'title pr)) 82 (elfeed-feed-title feed) (format "GitHub: %s [%s]" repo display-name)))))
72 (html-url (alist-get 'html_url pr)) 83
73 (state (alist-get 'state pr)) 84 ;; Process PRs in chunks to avoid blocking
74 (updated (alist-get 'updated_at pr)) 85 (cl-labels ((process-next-chunk ()
75 (created (alist-get 'created_at pr)) 86 (when prs-to-process
76 (user (alist-get 'login (alist-get 'user pr))) 87 ;; Process 5 PRs at a time
77 (body (or (alist-get 'body pr) "No description provided.")) 88 (let ((chunk (cl-subseq prs-to-process 0 (min 5 (length prs-to-process)))))
78 (labels (mapcar (lambda (label) (alist-get 'name label)) 89 (setq prs-to-process (cl-subseq prs-to-process (length chunk)))
79 (alist-get 'labels pr))) 90 ;; Process this chunk
80 (entry-id html-url) 91 (dolist (pr chunk)
81 (title (format "#%d: %s" number title-text)) 92 (let* ((number (alist-get 'number pr))
82 (date (float-time (date-to-time updated))) 93 (title-text (alist-get 'title pr))
83 (content (format "<h2>PR #%d: %s</h2>\n<p><strong>State:</strong> %s</p>\n<p><strong>Author:</strong> %s</p>\n<p><strong>Created:</strong> %s</p>\n<p><strong>Updated:</strong> %s</p>\n%s\n<h3>Description:</h3>\n<pre>%s</pre>" 94 (html-url (alist-get 'html_url pr))
84 number title-text state user created updated 95 (state (alist-get 'state pr))
85 (if labels 96 (updated (alist-get 'updated_at pr))
86 (format "<p><strong>Labels:</strong> %s</p>" 97 (created (alist-get 'created_at pr))
87 (mapconcat 'identity labels ", ")) 98 (user (alist-get 'login (alist-get 'user pr)))
88 "") 99 (body (or (alist-get 'body pr) "No description provided."))
89 (replace-regexp-in-string "<" "&lt;" 100 (labels (mapcar (lambda (label) (alist-get 'name label))
90 (replace-regexp-in-string ">" "&gt;" body))))) 101 (alist-get 'labels pr)))
91 (unless (elfeed-db-get-entry entry-id) 102 (entry-id html-url)
92 (let ((entry (elfeed-entry--create 103 (title (format "#%d: %s" number title-text))
93 :id entry-id 104 (date (float-time (date-to-time updated)))
94 :title title 105 (content (format "<h2>PR #%d: %s</h2>\n<p><strong>State:</strong> %s</p>\n<p><strong>Author:</strong> %s</p>\n<p><strong>Created:</strong> %s</p>\n<p><strong>Updated:</strong> %s</p>\n%s\n<h3>Description:</h3>\n<pre>%s</pre>"
95 :link html-url 106 number title-text state user created updated
96 :date date 107 (if labels
97 :content content 108 (format "<p><strong>Labels:</strong> %s</p>"
98 :content-type 'html 109 (mapconcat 'identity labels ", "))
99 :feed-id feed-url 110 "")
100 :tags '(unread github)))) 111 (replace-regexp-in-string "<" "&lt;"
101 (push entry new-entries))))) 112 (replace-regexp-in-string ">" "&gt;" body)))))
102 113 (unless (elfeed-db-get-entry entry-id)
103 ;; Call completion callback with results 114 (let ((entry (elfeed-entry--create
104 (funcall completion-callback repo display-name (nreverse new-entries)))) 115 :id entry-id
116 :title title
117 :link html-url
118 :date date
119 :content content
120 :content-type 'html
121 :feed-id feed-url
122 :tags '(unread github))))
123 (push entry new-entries)))))
124 ;; Schedule next chunk or call completion
125 (if prs-to-process
126 (run-with-idle-timer 0 nil #'process-next-chunk)
127 ;; All done - call completion callback
128 (funcall completion-callback repo display-name (nreverse new-entries)))))))
129 (process-next-chunk))))
105 130
106;;;###autoload 131;;;###autoload
107(defun elfeed-custom-feeds-github-update () 132(defun elfeed-custom-feeds-github-update ()
@@ -120,26 +145,30 @@ Batches all entries and updates UI once at the end to avoid blocking."
120 ;; Completion callback that batches everything 145 ;; Completion callback that batches everything
121 (let ((completion-callback 146 (let ((completion-callback
122 (lambda (repo display-name new-entries) 147 (lambda (repo display-name new-entries)
123 (setq processed (1+ processed)) 148 (run-with-idle-timer 0 nil
124 (when new-entries 149 (lambda ()
125 (setq all-new-entries (append all-new-entries new-entries))) 150 (setq processed (1+ processed))
126 (push (list repo display-name (length new-entries)) results) 151 (when new-entries
127 152 (setq all-new-entries (append all-new-entries new-entries)))
128 ;; When all queries are done, batch add to DB and update UI once 153 (push (list repo display-name (length new-entries)) results)
129 (when (= processed total) 154
130 (let ((total-count (length all-new-entries))) 155 ;; When all queries are done, batch add to DB and update UI once
131 (when (> total-count 0) 156 (when (= processed total)
132 (elfeed-db-add all-new-entries)) 157 (let ((total-count (length all-new-entries)))
133 ;; Report results for each feed 158 (when (> total-count 0)
134 (dolist (result (nreverse results)) 159 (run-with-idle-timer 0 nil
135 (message "GitHub feed %s [%s]: %d new entries" 160 (lambda ()
136 (nth 0 result) (nth 1 result) (nth 2 result))) 161 (elfeed-db-add all-new-entries))))
137 ;; Defer UI update to avoid blocking 162 ;; Report results for each feed
138 (run-with-idle-timer 0.1 nil 163 (dolist (result (nreverse results))
139 (lambda () 164 (message "GitHub feed %s [%s]: %d new entries"
140 (when (get-buffer "*elfeed-search*") 165 (nth 0 result) (nth 1 result) (nth 2 result)))
141 (with-current-buffer "*elfeed-search*" 166 ;; Defer UI update to avoid blocking
142 (elfeed-search-update--force)))))))))) 167 (run-with-idle-timer 0.2 nil
168 (lambda ()
169 (when (get-buffer "*elfeed-search*")
170 (with-current-buffer "*elfeed-search*"
171 (elfeed-search-update--force)))))))))))
143 172
144 ;; Start all async fetches 173 ;; Start all async fetches
145 (cl-loop for (repo query display-name) in elfeed-custom-github-pr-queries 174 (cl-loop for (repo query display-name) in elfeed-custom-github-pr-queries