-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathorg-social-notifications.el
More file actions
234 lines (217 loc) · 11.9 KB
/
Copy pathorg-social-notifications.el
File metadata and controls
234 lines (217 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
;;; org-social-notifications.el --- Notifications functionality for Org-social -*- lexical-binding: t -*- -*- coding: utf-8 -*-
;; SPDX-License-Identifier: GPL-3.0
;; Author: Andros Fenollosa <hi@andros.dev>
;; Version: 2.14
;; URL: https://github.com/tanrax/org-social.el
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation, either version 3 of the
;; License, or (at your option) any later version.
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see
;; <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Notification system for Org-social, handling mentions, replies, polls and other events.
;;; Code:
(require 'org-social-variables)
(require 'org-social-polls)
(require 'org-social-relay)
(require 'org-social-parser)
(defun org-social-notifications--is-feed-followed-p (feed-url)
"Check if FEED-URL is in the user's following list."
;; Always load fresh profile to avoid cache issues
(let ((my-profile (org-social-parser--get-my-profile)))
(when my-profile
(let ((follow-list (alist-get 'follow my-profile)))
(seq-some (lambda (follow)
;; More robust URL comparison - handle trailing slashes and protocol differences
(let ((followed-url (alist-get 'url follow))
(normalized-feed-url (string-trim-right feed-url "/")))
(when followed-url
(let ((normalized-followed-url (string-trim-right followed-url "/")))
(or (string= normalized-followed-url normalized-feed-url)
;; Also check with protocol normalization (http vs https)
(string= (replace-regexp-in-string "^https://" "http://" normalized-followed-url)
(replace-regexp-in-string "^https://" "http://" normalized-feed-url))
(string= (replace-regexp-in-string "^http://" "https://" normalized-followed-url)
(replace-regexp-in-string "^http://" "https://" normalized-feed-url)))))))
follow-list)))))
(defun org-social-notifications--find-mentions (timeline)
"Find all posts that mention the current user.
Argument TIMELINE is the list posts."
(let ((my-nick (alist-get 'nick org-social-variables--my-profile))
(mentions '()))
(when my-nick
(dolist (post timeline)
(let ((text (alist-get 'text post))
(author (alist-get 'author-nick post))
(author-url (alist-get 'author-url post))
(timestamp (alist-get 'timestamp post)))
(when (and text
(not (string= author my-nick)) ; Don't include own posts
(string-match (format "\\[\\[org-social:[^]]+\\]\\[%s\\]\\]" (regexp-quote my-nick)) text))
(let ((is-followed (if author-url
(org-social-notifications--is-feed-followed-p author-url)
t))) ; If no author-url, assume it's followed (local post)
(push (list
(cons 'type 'mention)
(cons 'author author)
(cons 'author-url author-url)
(cons 'timestamp timestamp)
(cons 'followed is-followed)
(cons 'text text)) mentions))))))
(reverse mentions)))
(defun org-social-notifications--find-replies (timeline)
"Find all posts that reply to the current user's posts.
Argument TIMELINE is the list posts."
(let ((my-url (alist-get 'url org-social-variables--my-profile))
(my-nick (alist-get 'nick org-social-variables--my-profile))
(replies '()))
(when my-url
(dolist (post timeline)
(let ((reply-to (alist-get 'reply_to post))
(author (alist-get 'author-nick post))
(author-url (alist-get 'author-url post))
(timestamp (alist-get 'timestamp post))
(text (alist-get 'text post)))
(when (and reply-to
(not (string= author my-nick)) ; Don't include own posts
(string-match-p (regexp-quote my-url) reply-to))
(push (list
(cons 'type 'reply)
(cons 'author author)
(cons 'author-url author-url)
(cons 'timestamp timestamp)
(cons 'reply-to reply-to)
(cons 'text text)) replies)))))
(reverse replies)))
(defun org-social-notifications--render-all-notifications (notifications)
"Render all NOTIFICATIONS in a single list sorted by date."
(if notifications
(dolist (notification notifications)
(let ((author (alist-get 'author notification))
(timestamp (alist-get 'timestamp notification))
(type (alist-get 'type notification)))
(cond
((eq type 'mention)
(let ((followed (alist-get 'followed notification))
(author-url (alist-get 'author-url notification)))
(if followed
;; For followed feeds, use normal link format
(insert (format "- [[#%s][%s mentioned you]]\n" timestamp author))
;; For unfollowed feeds, show warning and feed URL
(insert (format "- %s mentioned you (not following - cannot view post)\n" author))
(insert (format " Feed URL: %s\n" author-url)))))
((eq type 'reply)
(insert (format "- [[#%s][%s replied to your post]]\n" timestamp author)))
((eq type 'active-poll)
(let ((poll-end (alist-get 'poll-end notification))
(author-url (alist-get 'author-url notification))
(question (alist-get 'question notification)))
(insert (format "- [[org-social-poll:%s|%s][Active poll: %s by %s]] (ends: %s)\n"
author-url timestamp question
(or author "Unknown")
(or poll-end "Unknown")))))
((eq type 'poll-result)
(let ((total-votes (alist-get 'total-votes notification))
(results (alist-get 'results notification))
(question (alist-get 'question notification)))
(insert (format "- [[#%s][Poll results: %s by %s]] (%d votes)\n"
timestamp question
(or author "Unknown")
total-votes))
;; Show top result inline
(when results
(let* ((sorted-results (sort results (lambda (a b) (> (cdr a) (cdr b)))))
(winner (car sorted-results))
(option (when winner (car winner)))
(vote-count (when winner (cdr winner)))
(percentage (if (and vote-count (> total-votes 0))
(/ (* vote-count 100.0) total-votes)
0)))
(when winner
(insert (format " Winner: %s (%d votes, %.1f%%)\n"
option vote-count percentage))))))))))
(insert "No new notifications.\n")))
(defun org-social-notifications--render-section (timeline)
"Render the complete notifications section.
Argument TIMELINE is the list posts."
;; Always use local mentions for now to maintain synchronous behavior
;; TODO: Implement async relay mentions in a future version
(let* ((mentions (org-social-notifications--find-mentions timeline))
(replies (org-social-notifications--find-replies timeline))
(active-polls (org-social-polls--get-active-poll-notifications timeline))
(poll-results (org-social-polls--get-poll-result-notifications timeline))
(all-notifications (append mentions replies active-polls poll-results))
;; Sort by date (most recent first)
(sorted-notifications (sort all-notifications
(lambda (a b)
(let ((date-a (alist-get 'date a))
(date-b (alist-get 'date b)))
;; If dates are not available, use timestamp parsing
(unless date-a
(setq date-a (date-to-time (alist-get 'timestamp a))))
(unless date-b
(setq date-b (date-to-time (alist-get 'timestamp b))))
(time-less-p date-b date-a))))) ; b < a for descending order
(total-count (length all-notifications)))
(insert (format "* (%d) Notifications\n" total-count))
(insert ":PROPERTIES:\n")
(insert ":END:\n\n")
(org-social-notifications--render-all-notifications sorted-notifications)
(insert "\n")))
;;;###autoload
(defun org-social-check-relay-mentions ()
"Check and display mentions from the relay server."
(interactive)
(if (and org-social-relay
org-social-my-public-url
(not (string-empty-p org-social-relay))
(not (string-empty-p org-social-my-public-url)))
(progn
(message "Checking relay mentions...")
(org-social-relay--fetch-mentions
(lambda (relay-mentions)
(if relay-mentions
(let ((buffer-name "*Relay Mentions*")
(mentions '()))
;; Convert relay mentions to notification format
(dolist (url relay-mentions)
(when (string-match "\\([^#]+\\)#\\(.+\\)" url)
(let ((feed-url (match-string 1 url))
(timestamp (match-string 2 url)))
;; Extract author info from feed URL - use domain name instead of filename
(let ((author (if (string-match "https?://\\([^/]+\\)" feed-url)
(match-string 1 feed-url)
"Unknown"))
(is-followed (org-social-notifications--is-feed-followed-p feed-url)))
(push (list
(cons 'type 'mention)
(cons 'author author)
(cons 'author-url feed-url)
(cons 'timestamp timestamp)
(cons 'post-url url)
(cons 'followed is-followed)
(cons 'text (format "Mentioned you in a post%s"
(if is-followed
""
" (not following - cannot view post)"))))
mentions)))))
;; Display the mentions
(with-current-buffer (get-buffer-create buffer-name)
(let ((inhibit-read-only t))
(erase-buffer)
(insert (format "* (%d) Relay Mentions\n\n" (length mentions)))
(org-social-notifications--render-all-notifications (reverse mentions))
(org-mode)
(goto-char (point-min))
(display-buffer (current-buffer)))))
(message "No mentions found in relay")))))
(message "Relay not configured. Set org-social-relay and org-social-my-public-url")))
(provide 'org-social-notifications)
;;; org-social-notifications.el ends here