-
Notifications
You must be signed in to change notification settings - Fork 21
/
znc.el
executable file
·241 lines (206 loc) · 9.09 KB
/
znc.el
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
;;; znc.el --- ZNC + ERC
;;;
;; Author: Yaroslav Shirokov
;; URL: https://github.com/sshirokov/ZNC.el
;; Version: 0.0.4
;; Package-Requires: ((cl-lib "0.2"))
;; Also available via Marmalade http://marmalade-repo.org/
;;;;;;
(require 'cl)
(require 'erc)
(defgroup znc nil
"ZNC IRC Bouncer assistance and opinions.
This is a thin wrapper around `erc' that makes using
the ZNC (http://en.znc.in/) IRC bouncer and irons out
some of the quirks that arise from using it with a naive ERC. "
:group 'erc)
;; Default vars
(defvar *znc-server-default-host* "localhost" "Default host to use in `*znc-server-default*'")
(defvar *znc-server-default-port* 12533 "Default port to use in `*znc-server-default*'")
;; Types
(defconst *znc-server-accounts-type* '((cons :tag "Account"
(symbol :tag "Network Slug" :value network-slug)
(group (string :tag "Username" :value "znc-username")
(string :tag "Password" :value "znc-password"))))
"A group describing an account belonging to a server")
(defconst *znc-server-type* `(group (string :tag "Host" :value ,*znc-server-default-host*)
(integer :tag "Port" :value ,*znc-server-default-port*)
(boolean :tag "SSL" :value nil)
(repeat :tag "Accounts on server" ,@*znc-server-accounts-type*))
"A group describing a ZNC server endpoint and the accounts on it")
;; Customizations
(defcustom znc-servers nil
"List of ZNC servers"
:tag "ZNC Servers"
:group 'znc
:type `(repeat ,*znc-server-type*))
(defcustom znc-erc-connector 'erc
"The ERC connection function, must be compatible with `erc'"
:group 'znc
:type 'symbol)
(defcustom znc-erc-ssl-connector 'erc-tls
"The ERC SSL connection function, must be compatible with `erc'"
:group 'znc
:type 'symbol)
(defcustom znc-detatch-on-kill t
"Detach from, rather than /part from channels when you a buffer is killed"
:group 'znc
:type 'boolean)
;; Interactive
;;;###autoload
(defun znc-erc (&optional network)
"Connect to a configured znc network"
(interactive)
(let* ((networks (znc-walk-all-servers :each 'znc-endpoint-slug-name))
(network (or (and network (format "%s" network))
(when networks
(znc-prompt-string-or-nil "Network" networks (car networks) t))))
(endpoint (when network
(znc-walk-all-servers :first t :pred (znc-walk-slugp (read network))))))
(if endpoint
(znc-erc-connect endpoint)
(message "Network %s not defined. Try M-x customize-group znc."
(symbol-name network)))))
(defun znc-discard (&optional network)
;; (interactive) ;;TODO: Abstract asking for anetwork, and interactive this
(let* ((buffer (znc-network-server-buffer network))
(proc (and buffer
(znc-network-server-process network)))
(pending (and buffer proc
(erc-with-all-buffers-of-server proc
(lambda () (not (equal (current-buffer) (erc-server-buffer))))
(current-buffer)))))
(if buffer
(loop for kidbuffer in pending
do (znc-kill-buffer-always kidbuffer)
initially (znc-kill-buffer-always buffer)
finally return `(buffer ,@pending))
(message "%s is unknown or not currently running"))))
;;;###autoload
(defun znc-all (&optional disconnect)
"Connect to all known networks"
(interactive "P")
(loop for network in (znc-walk-all-servers :each 'znc-endpoint-slug)
do
(message "Connecting to: %s" network)
(if disconnect
(znc-discard network)
(znc-erc network))
collecting network))
;; Advice
(defadvice erc-server-reconnect (after znc-erc-rename last nil activate)
"Maybe rename the buffer we create"
(let* ((wants-name (and (local-variable-p 'znc-buffer-name (erc-server-buffer))
(buffer-local-value 'znc-buffer-name (erc-server-buffer))))
(current (erc-server-buffer))
(returning ad-return-value))
(if wants-name
(progn
(ignore-errors (znc-kill-buffer-always wants-name))
(with-current-buffer returning
(znc-set-name wants-name)
(rename-buffer wants-name))
returning))))
(defadvice erc-kill-channel (around znc-maybe-dont-part first nil activate)
"Maybe don't let `erc-kill-channel' run"
(let ((is-znc (and (local-variable-p 'znc-buffer-name (erc-server-buffer))
(buffer-local-value 'znc-buffer-name (erc-server-buffer)))))
(if is-znc
(unless znc-detatch-on-kill ad-do-it)
ad-do-it)))
;; Hooks
(add-hook 'erc-kill-channel-hook (defun znc-kill-channel-hook ()
"Hook that handles ZNC-specific channel killing behavior"
(and (local-variable-p 'znc-buffer-name (erc-server-buffer))
znc-detatch-on-kill
(znc-detach-channel))))
;;; Traversal
(defun* znc-walk-all-servers (&key (each (lambda (&rest r) (mapcar 'identity r)))
(pred (lambda (&rest _) t))
(first nil))
"Walk every defined server and user pair calling `each' every time `pred' is non-nil
Both functions are called as: (apply f slug host port user pass)
`each' defaults to (mapcar 'identity ..)
`pred' is a truth function
`first' if non-nil, return the car of the result"
(funcall (if first 'car 'identity)
(loop for (host port ssl users) in znc-servers
appending (loop for (slug user pass) in users collecting
`(,slug ,host ,port ,ssl ,user ,pass)) into endpoints
finally return (loop for endpoint in endpoints
if (apply pred endpoint)
collect (apply each endpoint)))))
;;; Traversal helpers
(defun znc-walk-slugp (slug)
(lexical-let ((slug slug))
(lambda (s &rest _) (eq s slug))))
(defun znc-endpoint-slug-name (&rest args)
(symbol-name (apply 'znc-endpoint-slug args)))
(defun znc-endpoint-slug (s &rest _) s)
;;; Helper Macro(s)
(defmacro with-endpoint (endpoint &rest forms)
"Wraps the remainder in a binding in which
`slug' `host' `port' `ssl' `user' `pass' are bound
to the matching values for the endpoint"
(let ((sympoint (gensym "endpoint")))
`(let ((,sympoint ,endpoint))
(destructuring-bind (slug host port ssl user pass) ,sympoint
,@forms))))
;;; Helpers
(defun znc-network-buffer-name (network)
"Formats a buffer name for a given `network'"
(format "*irc-%s*" network))
(defun znc-network-has-buffer (network)
(and
(znc-walk-all-servers :first t :pred (znc-walk-slugp network))
(get-buffer (znc-network-buffer-name network))))
(defun znc-network-server-process (network)
(let ((buffer (znc-network-server-buffer network)))
(when buffer
(with-current-buffer buffer erc-server-process))))
(defun znc-network-server-buffer (network)
"Returns a server buffer for `network' or nil"
(let ((buffer (znc-network-has-buffer network)))
(when buffer
(with-current-buffer buffer
(erc-server-buffer)))))
(defun znc-kill-buffer-always (&optional buffer)
"Murderface a buffer, don't listen to nobody, son!"
(let ((buffer (or buffer (current-buffer)))
(kill-buffer-query-functions nil))
(kill-buffer buffer)))
(defun znc-detach-channel ()
(when (erc-server-process-alive)
(let ((tgt (erc-default-target)))
(erc-server-send (format "DETACH %s" tgt)
nil tgt))))
(defun znc-set-name (znc-name &optional buffer)
"Set the znc-buffer-name buffer local to znc-name in buffer or (current-buffer)"
(let ((buffer (get-buffer (or buffer (current-buffer)))))
(with-current-buffer buffer
(make-local-variable 'znc-buffer-name)
(setf znc-buffer-name znc-name))))
(defun znc-erc-connect (endpoint)
(let ((message-endpoint (append (butlast endpoint) '("***password***"))))
(message "Called with: %s" message-endpoint)
(with-endpoint endpoint
(message "Have endpoint: %s" message-endpoint)
(let* ((buffer (znc-network-buffer-name slug))
(erc-fun (if ssl znc-erc-ssl-connector znc-erc-connector))
(erc-args `(:server ,host :port ,port
:nick ,user :password ,(format "%s:%s" user pass)))
(erc-buffer (apply erc-fun erc-args)))
(when (get-buffer buffer)
(znc-kill-buffer-always buffer))
(znc-set-name buffer erc-buffer)
(with-current-buffer erc-buffer
(rename-buffer buffer))))))
(defun znc-prompt-string-or-nil (prompt &optional completions default require-match)
(let* ((string (completing-read (concat prompt ": ") completions nil require-match nil nil default))
(string (if (equal string "") nil string)))
string))
;;;;;;;;;;;;;;;;;;;
;; Provide! ;;;
(provide 'znc) ;;;
;;;;;;;;;;;;;;;;;;;
;;; znc.el ends here