;; Created: 14 June 2021
;; Version: 1.0
;; Keywords: network
-;; Homepage: http://thelambdalab.xyz/erc
+;; Homepage: http://thelambdalab.xyz/lurk
;; Package-Requires: ((emacs "26"))
;; This file is not part of GNU Emacs.
(defcustom lurk-nick "plugd"
"Default nick.")
-(defcustom lurk-full-name "plugd"
- "Default full name.")
-(defcustom lurk-user-name "plugd"
- "Default user name.")
+
+(defcustom lurk-default-quit-msg "Bye"
+ "Default quit message when none supplied.")
(defcustom lurk-networks
'(("libera" "irc.libera.chat" 6697)
("freenode" "chat.freenode.net" 6697)
+ ("tilde" "tilde.chat" 6697)
+ ("mbr" "mbrserver.com" 6667 :notls)
("local" "localhost" 6697))
"IRC networks.")
(defcustom lurk-allow-ipv6 nil
"Set to non-nil to allow use of IPv6.")
+(defcustom lurk-show-joins nil
+ "Set to non-nil to be notified of joins, parts and quits.")
+
;;; Faces
;;
(defvar lurk-error-prefix
(propertize "!!!" 'face 'lurk-error))
-
(defvar lurk-prompt-string
(propertize "> " 'face 'lurk-prompt))
(clrhash lurk-contexts)
(setq lurk-current-context nil)
(lurk-start-process network)
- (lurk-send-msg (lurk-msg nil nil "USER" lurk-user-name 0 "*" lurk-full-name))
+ (lurk-send-msg (lurk-msg nil nil "USER" lurk-nick 0 "*" lurk-nick))
(lurk-send-msg (lurk-msg nil nil "NICK" lurk-nick))
(setq lurk-ping-timer (run-with-timer lurk-ping-period nil #'lurk-ping-function)))))
+(defun lurk-connected-p ()
+ (let ((proc (get-process "lurk")))
+ (and proc (eq (process-status proc) 'open))))
(defun lurk-send-msg (msg)
(let ((proc (get-process "lurk")))
nil))))
-;;; Contexts and users
+;;; Contexts
;;
-(defvar lurk-context-table
- '((channel lurk-display-channel-message)
- (nick lurk-display-private-message)
- (host lurk-diaplay-server-message)))
-
(defvar lurk-current-context nil)
(defvar lurk-contexts (make-hash-table :test #'equal))
(dolist (context (lurk-get-context-list))
(lurk-del-context-user context user)))
+(defun lurk-rename-user (old-nick new-nick)
+ (dolist (context (lurk-get-context-list))
+ (lurk-del-context-user context old-nick)
+ (lurk-add-context-users context (list new-nick))))
+
(defun lurk-get-context-type (name)
(cond
((string-prefix-p "#" name) 'channel)
(interactive)
(lurk-cycle-contexts t))
+
;;; Buffer
;;
(goto-char lurk-prompt-marker)
(let ((inhibit-read-only t)
(old-pos (marker-position lurk-prompt-marker))
- (adaptive-fill-regexp (rx (= 6 anychar))))
+ (adaptive-fill-regexp (rx (= 6 anychar)))
+ (fill-column 80))
(insert-before-markers
(propertize (concat (format-time-string "%H:%M") " ")
'face 'lurk-text
'read-only t)
(propertize (concat (apply #'concat strings) "\n")
'read-only t))
- (fill-region old-pos lurk-prompt-marker)))))
+ (fill-region old-pos lurk-prompt-marker nil t)))))
(defun lurk-render-prompt ()
(with-current-buffer "*lurk*"
(defun lurk-setup-buffer ()
(with-current-buffer (get-buffer-create "*lurk*")
+ (setq-local scroll-conservatively 1)
(if (markerp lurk-prompt-marker)
(set-marker lurk-prompt-marker (point-max))
(setq lurk-prompt-marker (point-max-marker)))
;;
(defun lurk-display-message (from to text)
- (let* ((to-type (lurk-get-context-type to))
- (display-fun (cadr (assoc to-type lurk-context-table))))
- (funcall display-fun from to text)))
-
-(defun lurk-display-channel-message (from to text)
+ (let ((context (if (eq 'channel (lurk-get-context-type to))
+ to
+ (if (equal to lurk-nick) from to))))
+ (lurk-display-string
+ (propertize
+ (pcase (lurk-get-context-type to)
+ ('channel (concat to " <" from "> " text))
+ ('nick (concat "[" from " -> " to "] " text))
+ (_
+ (error "Unsupported context type")))
+ 'face 'lurk-text
+ 'help-echo (concat "Context: " context)
+ 'context context))))
+
+(defun lurk-display-notice (context &rest notices)
(lurk-display-string
- (propertize (concat to
- " <" from "> "
- text)
- 'face 'lurk-text)))
-
-
-(defun lurk-display-action (channel-name nick action)
- (lurk-display-string (concat channel-name
- " * "
- (propertize (concat nick " " action)
- 'face 'lurk-text))))
-
-(defun lurk-display-private-message (from to text)
- (lurk-display-string
- (concat
- (propertize
- (concat "[" from " -> " to "] "
- text)
- 'face 'lurk-text))))
-
-
-(defun lurk-display-notice (&rest notices)
- (lurk-display-string lurk-notice-prefix " " (apply #'concat notices)))
+ (propertize
+ (concat lurk-notice-prefix " " (apply #'concat notices))
+ 'help-echo (concat "Context: " (or context "none"))
+ 'context context)))
(defun lurk-display-error (&rest messages)
- (lurk-display-string lurk-error-prefix " "
- (propertize (apply #'concat messages)
- 'face 'lurk-error)))
+ (lurk-display-string
+ (concat lurk-error-prefix " "
+ (propertize (apply #'concat messages)
+ 'face 'lurk-error))))
+
+(defun lurk-highlight-context (context)
+ (with-current-buffer "*lurk*"
+ (let* ((pos lurk-prompt-marker)
+ (nextpos (previous-single-property-change pos 'context))
+ (inhibit-read-only t))
+ (while (> pos nextpos)
+ (let ((thiscontext (get-text-property nextpos 'context)))
+ (if thiscontext
+ (if (equal context thiscontext)
+ (add-text-properties nextpos pos
+ '(face (foreground-color . "green")))
+ (add-text-properties nextpos pos
+ '(face (foreground-color . "blue"))))
+ (add-text-properties nextpos pos
+ '(face lurk-text)))
+ thiscontext
+ (setq pos nextpos)
+ (setq nextpos (previous-single-property-change pos 'context nil 1)))))))
;;; Message evaluation
;;
(defun lurk-eval-msg-string (string)
;; (lurk-display-string string)
(let* ((msg (lurk-string->msg string)))
+ ;; (message (pp msg))
(pcase (lurk-msg-cmd msg)
("PING"
(lurk-send-msg
- (lurk-msg nil nil "PONG" (lurk-msg-params msg)))
- (lurk-display-notice "ping-pong"))
+ (lurk-msg nil nil "PONG" (lurk-msg-params msg))))
+ ;; (lurk-display-notice nil "ping-pong (server initiated)"))
+
+ ("PONG")
+ ;; (lurk-display-notice nil "ping-pong (client initiated)"))
+
+ ("001"
+ (let* ((params (lurk-msg-params msg))
+ (nick (elt params 0))
+ (text (string-join (seq-drop params 1) " ")))
+ (setq lurk-nick nick)
+ (lurk-display-notice nil text)))
("353" ; NAMEREPLY
(let* ((params (lurk-msg-params msg))
(lurk-add-context-users channel names)))
("366" ; ENDOFNAMES
- (lurk-display-notice
- (lurk--as-string (length (lurk-get-context-users lurk-current-context)))
- " users in " lurk-current-context))
+ (let* ((params (lurk-msg-params msg))
+ (channel (elt params 1)))
+ (lurk-display-notice
+ channel
+ (lurk--as-string (length (lurk-get-context-users channel)))
+ " users in " channel)))
((rx (= 3 (any digit)))
- (lurk-display-notice (mapconcat 'identity (cdr (lurk-msg-params msg)) " ")))
+ (lurk-display-notice nil (mapconcat 'identity (cdr (lurk-msg-params msg)) " ")))
((and "JOIN"
(guard (equal lurk-nick (lurk-msg-src msg))))
- (let ((channel-name (car (lurk-msg-params msg))))
- (lurk-add-context channel-name)
- (setq lurk-current-context channel-name)
- (lurk-display-notice "Joining channel " channel-name)
+ (let ((channel (car (lurk-msg-params msg))))
+ (lurk-add-context channel)
+ (setq lurk-current-context channel)
+ (lurk-display-notice channel "Joining channel " channel)
(lurk-render-prompt)))
("JOIN"
- (let ((channel-name (car (lurk-msg-params msg)))
+ (let ((channel (car (lurk-msg-params msg)))
(nick (lurk-msg-src msg)))
- (lurk-add-context-users channel-name (list nick))
- (lurk-display-notice nick " joined channel " channel-name)))
+ (lurk-add-context-users channel (list nick))
+ (lurk-display-notice channel nick " joined channel " channel)))
((and "PART"
(guard (equal lurk-nick (lurk-msg-src msg))))
- (let ((channel-name (car (lurk-msg-params msg))))
- (lurk-display-notice "Left channel " channel-name)
- (lurk-del-context channel-name)
- (if (equal channel-name lurk-current-context)
+ (let ((channel (car (lurk-msg-params msg))))
+ (lurk-display-notice channel "Left channel " channel)
+ (lurk-del-context channel)
+ (if (equal channel lurk-current-context)
(setq lurk-current-context (lurk-get-next-context)))
(lurk-render-prompt)))
("PART"
- (let ((channel-name (car (lurk-msg-params msg)))
+ (let ((channel (car (lurk-msg-params msg)))
(nick (lurk-msg-src msg)))
- (lurk-del-context-user channel-name nick)
- (lurk-display-notice nick " left channel " channel-name)))
+ (lurk-del-context-user channel nick)
+ (lurk-display-notice channel nick " left channel " channel)))
("QUIT"
(let ((nick (lurk-msg-src msg))
(reason (mapconcat 'identity (lurk-msg-params msg) " ")))
(lurk-del-user nick)
- (lurk-display-notice nick " quit: " reason)))
+ (lurk-display-notice nil nick " quit: " reason)))
((and "NICK"
(guard (equal lurk-nick (lurk-msg-src msg))))
(setq lurk-nick (car (lurk-msg-params msg)))
- (lurk-display-notice "Set nick to " lurk-nick))
+ (lurk-display-notice nil "Set nick to " lurk-nick))
("NICK"
(let ((old-nick (lurk-msg-src msg))
(new-nick (car (lurk-msg-params msg))))
- (lurk-display-notice nick " is now known as " new-nick)
- (lurk-rename-user nick new-nick)))
+ (lurk-display-notice nil old-nick " is now known as " new-nick)
+ (lurk-rename-user old-nick new-nick)))
("NOTICE"
(let ((nick (lurk-msg-src msg))
((rx (: "\01VERSION "
(let version (* (not "\01")))
"\01"))
- (lurk-display-notice "CTCP version reply from " nick ": " version))
+ (lurk-display-notice nil "CTCP version reply from " nick ": " version))
(_
- (lurk-display-notice text)))))
+ (lurk-display-notice nil text)))))
("PRIVMSG"
(let* ((from (lurk-msg-src msg))
(list from (concat "\01VERSION "
version-string
"\01")))))
- (lurk-display-notice "CTCP version request received from " from))
+ (lurk-display-notice nil "CTCP version request received from " from))
((rx (let ping (: "\01PING " (* (not "\01")) "\01")))
(lurk-send-msg (lurk-msg nil nil "NOTICE" (list from ping)))
(if (string-prefix-p "/" string)
(pcase (substring string 1)
((rx (: "CONNECT " (let network (* not-newline))))
- (lurk-display-notice "Attempting to connect to " network "...")
+ (lurk-display-notice nil "Attempting to connect to " network "...")
(lurk-connect network))
((rx (: "TOPIC " (let new-topic (* not-newline))))
(concat "\01ACTION " action "\01"))))
(lurk-display-action lurk-nick action))
- ((rx (: "VERSION" " " (let nick (* (not whitespace)))))
+ ((rx (: "VERSION" " " (let nick (+ (not whitespace)))))
(lurk-send-msg (lurk-msg nil nil "PRIVMSG"
(list nick "\01VERSION\01")))
- (lurk-display-notice "CTCP version request sent to " nick))
+ (lurk-display-notice nil "CTCP version request sent to " nick))
((rx "PART" (opt (: " " (let channel (* not-newline)))))
(if (or lurk-current-context channel)
lurk-current-context)))
(lurk-display-error "No current channel to leave.")))
+ ((rx "QUIT" (opt (: " " (let quit-msg (* not-newline)))))
+ (lurk-send-msg (lurk-msg nil nil "QUIT"
+ (or quit-msg lurk-default-quit-msg))))
+
+ ((rx (: "NICK" (* whitespace) string-end))
+ (lurk-display-notice nil "Current nick: " lurk-nick))
+
+ ((rx (: "NICK" (+ whitespace) (let nick (+ (not whitespace)))))
+ (if (lurk-connected-p)
+ (lurk-send-msg (lurk-msg nil nil "NICK" nick))
+ (setq lurk-nick nick)
+ (lurk-display-notice nil "Set default nick to '" nick "'")))
+
((rx "MSG "
(let to (* (not whitespace)))
" "
"Enter current contents of line after prompt."
(interactive)
(with-current-buffer "*lurk*"
- (lurk-enter-string
- (buffer-substring lurk-input-marker (point-max)))
- (let ((inhibit-read-only t))
- (delete-region lurk-input-marker (point-max)))))
+ (let ((line (buffer-substring lurk-input-marker (point-max))))
+ (let ((inhibit-read-only t))
+ (delete-region lurk-input-marker (point-max)))
+ (lurk-enter-string line))))
;;; Mode