blob: 1c189fa37f8afff2bd10f853e8c19ec6da8bc447 (
plain)
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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
|
;;; erc-config --- Preferences for Emacs Relay Chat (IRC Client) -*- lexical-binding: t; coding: utf-8; -*-
;; author Craig Jennings <c@cjennings.net>
;;
;;; Commentary:
;;
;; Enhanced ERC configuration with multi-server support.
;;
;; Main keybindings:
;; - C-; E C : Select and connect to a specific server
;; - C-; E c : Join a channel on current server
;; - C-; E b : Switch between ERC buffers across all servers
;; - C-; E l : List connected servers
;; - C-; E q : Quit current channel
;; - C-; E Q : Quit ERC server
;;; Code:
;; Load cl-lib at compile time and runtime (lightweight, already loaded in most configs)
(require 'cl-lib)
(eval-when-compile (require 'erc)
(require 'user-constants))
;; ------------------------------------ ERC ------------------------------------
;; Server definitions and connection settings
(defvar cj/erc-nick "craigjennings"
"Default IRC nickname for ERC connections.
Change this value to use a different nickname.")
(defvar cj/erc-server-alist
'(("Libera.Chat"
:host "irc.libera.chat"
:port 6697
:tls t
:channels ("#erc" "#emacs" "#emacs-social" "#systemcrafters"))
("IRCnet"
:host "open.ircnet.net"
:port 6697
:tls t
:channels ("#english"))
("Snoonet"
:host "irc.snoonet.org"
:port 6697
:tls t
:channels ("#talk"))
("IRCNow"
:host "irc.ircnow.org"
:port 6697
:tls t
:channels ("#general" "#lounge")))
"Alist of IRC servers and their connection details.")
(defun cj/erc-connect-server (server-name)
"Connect to a server specified by SERVER-NAME from `cj/erc-server-alist'."
(let ((server-info (assoc server-name cj/erc-server-alist)))
(if (not server-info)
(error "Server '%s' not found in cj/erc-server-alist" server-name)
(let ((host (plist-get (cdr server-info) :host))
(port (plist-get (cdr server-info) :port))
(tls (plist-get (cdr server-info) :tls)))
(if tls
(erc-tls :server host
:port port
:nick cj/erc-nick
:full-name user-whole-name)
(erc :server host
:port port
:nick cj/erc-nick
:full-name user-whole-name))))))
(defun cj/erc-connect-server-with-completion ()
"Connect to a server using completion for server selection."
(interactive)
(let ((server-name (completing-read "Connect to IRC server: "
(mapcar #'car cj/erc-server-alist))))
(cj/erc-connect-server server-name)))
(defun cj/erc-connected-servers ()
"Return a list of currently connected servers and display them in echo area."
(interactive)
(let ((server-buffers '()))
(dolist (buf (erc-buffer-list))
(with-current-buffer buf
(when (eq (buffer-local-value 'erc-server-process buf) erc-server-process)
(unless (member (buffer-name) server-buffers)
(push (buffer-name) server-buffers)))))
;; Display the server list when called interactively
(when (called-interactively-p 'any)
(if server-buffers
(message "Connected ERC servers: %s"
(mapconcat #'identity server-buffers ", "))
(message "No active ERC server connections")))
server-buffers))
(defun cj/erc-switch-to-buffer-with-completion ()
"Switch to an ERC buffer using completion.
If no ERC buffers exist, prompt to connect to a server.
Buffer names are shown with server context for clarity."
(interactive)
(let* ((erc-buffers (erc-buffer-list))
(buffer-names (mapcar #'buffer-name erc-buffers)))
(if buffer-names
(let ((selected (completing-read "Switch to ERC buffer: " buffer-names nil t)))
(switch-to-buffer selected))
(message "No ERC buffers found.")
(when (y-or-n-p "Connect to an IRC server? ")
(call-interactively 'cj/erc-connect-server-with-completion)))))
(defun cj/erc-server-buffer-active-p ()
"Return t if the current buffer is an active ERC server buffer."
(and (derived-mode-p 'erc-mode)
(erc-server-process-alive)
(erc-server-buffer-p)))
(defun cj/erc-get-channels-for-current-server ()
"Get list of channels for the currently connected server."
(when (and (derived-mode-p 'erc-mode) erc-server-process)
(let* ((server-host (process-name erc-server-process))
(matching-server (cl-find-if
(lambda (server)
(string-match-p (plist-get (cdr server) :host) server-host))
cj/erc-server-alist)))
(when matching-server
(plist-get (cdr matching-server) :channels)))))
(defun cj/erc-join-channel-with-completion ()
"Join a channel on the current server.
If not in an active ERC server buffer, reconnect first.
Auto-adds # prefix if missing. Offers completion from configured channels."
(interactive)
(unless (cj/erc-server-buffer-active-p)
(if (erc-buffer-list)
;; We have ERC buffers, but current one isn't active
(let ((server-buffers (cl-remove-if-not
(lambda (buf)
(with-current-buffer buf
(and (erc-server-buffer-p)
(erc-server-process-alive))))
(erc-buffer-list))))
(if server-buffers
;; Found active server buffer, switch to it
(switch-to-buffer (car server-buffers))
;; No active server buffer, reconnect
(message "No active ERC connection. Reconnecting...")
(call-interactively 'cj/erc-connect-server-with-completion)))
;; No ERC buffers at all, connect to a server
(message "No active ERC connection. Connecting to server first...")
(call-interactively 'cj/erc-connect-server-with-completion)))
;; At this point we should have an active connection
(if (cj/erc-server-buffer-active-p)
(let* ((channels (cj/erc-get-channels-for-current-server))
(channel (if channels
(completing-read "Join channel: " channels nil nil "#")
(read-string "Join channel: " "#"))))
;; Auto-add # prefix if missing
(unless (string-prefix-p "#" channel)
(setq channel (concat "#" channel)))
(when (> (length channel) 1) ; Must have more than just #
(erc-join-channel channel)))
(message "Failed to establish an active ERC connection")))
;; Keymap for ERC commands (must be defined before use-package erc)
(defvar-keymap cj/erc-keymap
:doc "Keymap for ERC-related commands"
"C" #'cj/erc-connect-server-with-completion ;; Connect to server
"c" #'cj/erc-join-channel-with-completion ;; join channel
"b" #'cj/erc-switch-to-buffer-with-completion ;; switch Buffer
"l" #'cj/erc-connected-servers ;; list connected servers
"q" #'erc-part-from-channel ;; quit channel
"Q" #'erc-quit-server) ;; Quit ERC entirely
(keymap-set cj/custom-keymap "E" cj/erc-keymap)
(with-eval-after-load 'which-key
(which-key-add-key-based-replacements "C-; E" "ERC chat menu"))
;; Main ERC configuration
(use-package erc
:ensure nil ;; built-in
:commands (erc erc-tls)
:hook
(erc-mode . emojify-mode)
:custom
(erc-modules
'(autojoin
button
completion
fill
image
irccontrols
list
log
match
move-to-prompt
noncommands
notifications
readonly
services
stamp
track))
(erc-autojoin-channels-alist
(mapcar (lambda (server)
(cons (car server)
(plist-get (cdr server) :channels)))
cj/erc-server-alist))
(erc-nick cj/erc-nick)
(erc-user-full-name user-whole-name)
(erc-use-auth-source-for-nickserv-password t)
(erc-kill-buffer-on-part t)
(erc-kill-queries-on-quit t)
(erc-kill-server-buffer-on-quit t)
(erc-fill-column 120)
(erc-fill-function #'erc-fill-static)
(erc-fill-static-center 20)
:config
;; use all text mode abbrevs in ercmode
(abbrev-table-put erc-mode-abbrev-table :parents (list text-mode-abbrev-table))
;; create log directory if it doesn't exist
(setq erc-log-channels-directory (concat user-emacs-directory "erc/logs/"))
(if (not (file-exists-p erc-log-channels-directory))
(mkdir erc-log-channels-directory t))
;; Configure buffer naming to include server name
;; Note: erc-rename-buffers is obsolete as of Emacs 29.1 (old behavior is now permanent)
(setq erc-unique-buffers t)
;; Custom buffer naming function
(defun cj/erc-generate-buffer-name (parms)
"Generate buffer name in the format SERVER-CHANNEL."
(let ((network (plist-get parms :server))
(target (plist-get parms :target)))
(if target
(concat (or network "") "-" (or target ""))
(or network ""))))
(setq erc-generate-buffer-name-function 'cj/erc-generate-buffer-name)
;; Configure erc-track (show channel activity in modeline)
(setq erc-track-exclude-types '("JOIN" "NICK" "PART" "QUIT" "MODE"
"324" "329" "332" "333" "353" "477")
erc-track-exclude-server-buffer t
erc-track-visibility 'selected-visible
erc-track-switch-direction 'importance
erc-track-showcount t)
;; Add hooks for notifications
(add-hook 'erc-text-matched-hook #'cj/erc-notify-on-mention))
;; -------------------------------- ERC Track ---------------------------------
;; Better tracking of activity across channels (already included in modules above)
(use-package erc-track
:ensure nil ;; built-in
:after erc
:custom
(erc-track-position-in-mode-line 'before-modes)
(erc-track-shorten-function 'erc-track-shorten-names)
(erc-track-shorten-cutoff 8)
(erc-track-shorten-start 1)
(erc-track-priority-faces-only 'all)
(erc-track-faces-priority-list
'(erc-error-face
erc-current-nick-face
erc-keyword-face
erc-nick-msg-face
erc-direct-msg-face
erc-notice-face
erc-prompt-face)))
;; ------------------------ ERC Desktop Notifications ------------------------
;; Implementation for desktop notifications
(defun cj/erc-notify-on-mention (match-type nick message)
"Display a notification when MATCH-TYPE is \\='current-nick.
NICK is the sender and MESSAGE is the message text."
(when (and (eq match-type 'current-nick)
(not (string= nick (erc-current-nick)))
(display-graphic-p))
(let ((title (format "ERC: %s mentioned you" nick)))
;; Use alert.el if available, otherwise fall back to notifications
(if (fboundp 'alert)
(alert message :title title :category 'erc)
(when (fboundp 'notifications-notify)
(notifications-notify
:title title
:body message
:app-name "Emacs ERC"
:sound-name 'message))))))
;; -------------------------------- ERC Image ---------------------------------
;; show inlined images (png/jpg/gif/svg) in erc buffers.
(use-package erc-image
:after erc
:config
(setq erc-image-inline-rescale 300))
;; -------------------------------- ERC Nicks ---------------------------------
;; Nickname highlighting (built-in to Emacs 29+)
(use-package erc-nicks
:ensure nil ;; built-in
:after erc
:hook (erc-mode . erc-nicks-mode))
;; ------------------------------ ERC Yank To Gist -----------------------------
;; automatically create a Gist if pasting more than 5 lines
;; this module requires https://github.com/defunkt/gist
;; via ruby: 'gem install gist' via the aur: yay -S gist
(use-package erc-yank
:after erc
:bind
(:map erc-mode-map
("C-y" . erc-yank)))
(provide 'erc-config)
;;; erc-config.el ends here
|