(require 'erc)
(eval-when-compile
(require 'erc-networks)
(require 'cl))
(defgroup erc-log nil
"Logging facilities for ERC."
:group 'erc)
(defcustom erc-generate-log-file-name-function 'erc-generate-log-file-name-long
"*A function to generate a log filename.
The function must take five arguments: BUFFER, TARGET, NICK, SERVER and PORT.
BUFFER is the buffer to be saved,
TARGET is the name of the channel, or the target of the query,
NICK is the current nick,
SERVER and PORT are the parameters used to connect BUFFERs
`erc-server-process'."
:group 'erc-log
:type '(choice (const :tag "Long style" erc-generate-log-file-name-long)
(const :tag "Long, but with network name rather than server"
erc-generate-log-file-name-network)
(const :tag "Short" erc-generate-log-file-name-short)
(const :tag "With date" erc-generate-log-file-name-with-date)
(symbol :tag "Other function")))
(defcustom erc-truncate-buffer-on-save nil
"Truncate any ERC (channel, query, server) buffer when it is saved."
:group 'erc-log
:type 'boolean)
(defcustom erc-enable-logging t
"If non-nil, ERC will log IRC conversations.
This can either be a boolean value of nil or t, or a function.
If the value is a function, it will be called with one argument, the
name of the current ERC buffer. One possible function, which saves
all but server buffers is `erc-log-all-but-server-buffers'.
This variable is buffer local. Setting it via \\[customize] sets the
default value.
Log files are stored in `erc-log-channels-directory'."
:group 'erc-log
:type '(choice boolean
function))
(make-variable-buffer-local 'erc-enable-logging)
(defcustom erc-log-channels-directory "~/log"
"The directory to place log files for channels.
Leave blank to disable logging. If not nil, all the channel
buffers are logged in separate files in that directory. The
directory should not end with a trailing slash."
:group 'erc-log
:type '(choice directory
(const nil)))
(defcustom erc-log-insert-log-on-open nil
"*Insert log file contents into the buffer if a log file exists."
:group 'erc-log
:type 'boolean)
(defcustom erc-save-buffer-on-part t
"*Save the channel buffer content using `erc-save-buffer-in-logs' on PART.
If you set this to nil, you may want to enable both
`erc-log-write-after-send' and `erc-log-write-after-insert'."
:group 'erc-log
:type 'boolean)
(defcustom erc-save-queries-on-quit t
"*Save all query (also channel) buffers of the server on QUIT.
If you set this to nil, you may want to enable both
`erc-log-write-after-send' and `erc-log-write-after-insert'."
:group 'erc-log
:type 'boolean)
(defcustom erc-log-write-after-send nil
"*If non-nil, write to log file after every message you send.
If you set this to nil, you may want to enable both
`erc-save-buffer-on-part' and `erc-save-queries-on-quit'."
:group 'erc-log
:type 'boolean)
(defcustom erc-log-write-after-insert nil
"*If non-nil, write to log file when new text is added to a
logged ERC buffer.
If you set this to nil, you may want to enable both
`erc-save-buffer-on-part' and `erc-save-queries-on-quit'."
:group 'erc-log
:type 'boolean)
(defcustom erc-log-file-coding-system (if (featurep 'xemacs)
'binary
'emacs-mule)
"*The coding system ERC should use for writing log files.
This should ideally, be a \"catch-all\" coding system, like
`emacs-mule', or `iso-2022-7bit'."
:group 'erc-log)
(define-erc-module log nil
"Automatically logs things you receive on IRC into files.
Files are stored in `erc-log-channels-directory'; file name
format is defined through a formatting function on
`erc-generate-log-file-name-function'.
Since automatic logging is not always a Good Thing (especially if
people say things in different coding systems), you can turn logging
behaviour on and off with the variable `erc-enable-logging', which can
also be a predicate function. To only log when you are not set away, use:
\(setq erc-enable-logging
(lambda (buffer)
(with-current-buffer buffer
(null (erc-away-time)))))"
((when erc-log-write-after-insert
(add-hook 'erc-insert-post-hook 'erc-save-buffer-in-logs))
(when erc-log-write-after-send
(add-hook 'erc-send-post-hook 'erc-save-buffer-in-logs))
(add-hook 'erc-kill-buffer-hook 'erc-save-buffer-in-logs)
(add-hook 'erc-kill-channel-hook 'erc-save-buffer-in-logs)
(add-hook 'kill-emacs-hook 'erc-log-save-all-buffers)
(add-hook 'erc-quit-hook 'erc-conditional-save-queries)
(add-hook 'erc-part-hook 'erc-conditional-save-buffer)
(add-hook 'erc-connect-pre-hook 'erc-log-setup-logging 'append)
(dolist (buffer (erc-buffer-list))
(erc-log-setup-logging buffer)))
((remove-hook 'erc-insert-post-hook 'erc-save-buffer-in-logs)
(remove-hook 'erc-send-post-hook 'erc-save-buffer-in-logs)
(remove-hook 'erc-kill-buffer-hook 'erc-save-buffer-in-logs)
(remove-hook 'erc-kill-channel-hook 'erc-save-buffer-in-logs)
(remove-hook 'kill-emacs-hook 'erc-log-save-all-buffers)
(remove-hook 'erc-quit-hook 'erc-conditional-save-queries)
(remove-hook 'erc-part-hook 'erc-conditional-save-buffer)
(remove-hook 'erc-connect-pre-hook 'erc-log-setup-logging)
(dolist (buffer (erc-buffer-list))
(erc-log-disable-logging buffer))))
(define-key erc-mode-map "\C-c\C-l" 'erc-save-buffer-in-logs)
(defun erc-log-setup-logging (buffer)
"Setup the buffer-local logging variables in the current buffer.
This function is destined to be run from `erc-connect-pre-hook'.
The current buffer is given by BUFFER."
(when (erc-logging-enabled buffer)
(with-current-buffer buffer
(auto-save-mode -1)
(setq buffer-file-name nil)
(cond ((boundp 'write-file-functions)
(set (make-local-variable 'write-file-functions)
'(erc-save-buffer-in-logs)))
((boundp 'local-write-file-hooks)
(setq local-write-file-hooks '(erc-save-buffer-in-logs)))
(t
(set (make-local-variable 'write-file-hooks)
'(erc-save-buffer-in-logs))))
(when erc-log-insert-log-on-open
(ignore-errors (insert-file-contents (erc-current-logfile))
(move-marker erc-last-saved-position
(1- (point-max))))))))
(defun erc-log-disable-logging (buffer)
"Disable logging in BUFFER."
(when (erc-logging-enabled buffer)
(with-current-buffer buffer
(setq buffer-offer-save nil
erc-enable-logging nil))))
(defun erc-log-all-but-server-buffers (buffer)
"Returns t if logging should be enabled in BUFFER.
Returns nil iff `erc-server-buffer-p' returns t."
(save-excursion
(save-window-excursion
(set-buffer buffer)
(not (erc-server-buffer-p)))))
(defun erc-save-query-buffers (process)
"Save all buffers of the given PROCESS."
(erc-with-all-buffers-of-server process
nil
(erc-save-buffer-in-logs)))
(defun erc-conditional-save-buffer (buffer)
"Save Query BUFFER if `erc-save-queries-on-quit' is t."
(when erc-save-buffer-on-part
(erc-save-buffer-in-logs buffer)))
(defun erc-conditional-save-queries (process)
"Save Query buffers of PROCESS if `erc-save-queries-on-quit' is t."
(when erc-save-queries-on-quit
(erc-save-query-buffers process)))
(defun erc-log-save-all-buffers ()
(dolist (buffer (erc-buffer-list))
(erc-save-buffer-in-logs buffer)))
(defun erc-logging-enabled (&optional buffer)
"Return non-nil if logging is enabled for BUFFER.
If BUFFER is nil, the value of `current-buffer' is used.
Logging is enabled if `erc-log-channels-directory' is non-nil, the directory
is writeable (it will be created as necessary) and
`erc-enable-logging' returns a non-nil value."
(and erc-log-channels-directory
(erc-directory-writable-p erc-log-channels-directory)
(if (functionp erc-enable-logging)
(funcall erc-enable-logging (or buffer (current-buffer)))
erc-enable-logging)))
(defun erc-log-standardize-name (filename)
"Make FILENAME safe to use as the name of an ERC log.
This will not work with full paths, only names.
Any unsafe characters in the name are replaced with \"!\". The
filename is downcased."
(downcase (erc-replace-regexp-in-string
"[/\\]" "!" (convert-standard-filename filename))))
(defun erc-current-logfile (&optional buffer)
"Return the logfile to use for BUFFER.
If BUFFER is nil, the value of `current-buffer' is used.
This is determined by `erc-generate-log-file-name-function'.
The result is converted to lowercase, as IRC is case-insensitive"
(expand-file-name
(erc-log-standardize-name
(funcall erc-generate-log-file-name-function
(or buffer (current-buffer))
(or (buffer-name buffer) (erc-default-target))
(erc-current-nick)
erc-session-server erc-session-port))
erc-log-channels-directory))
(defun erc-generate-log-file-name-with-date (buffer &rest ignore)
"This function computes a short log file name.
The name of the log file is composed of BUFFER and the current date.
This function is a possible value for `erc-generate-log-file-name-function'."
(concat (buffer-name buffer) "-" (format-time-string "%Y-%m-%d") ".txt"))
(defun erc-generate-log-file-name-short (buffer &rest ignore)
"This function computes a short log file name.
In fact, it only uses the buffer name of the BUFFER argument, so
you can affect that using `rename-buffer' and the-like. This
function is a possible value for
`erc-generate-log-file-name-function'."
(concat (buffer-name buffer) ".txt"))
(defun erc-generate-log-file-name-long (buffer target nick server port)
"Generates a log-file name in the way ERC always did it.
This results in a file name of the form #channel!nick@server:port.txt.
This function is a possible value for `erc-generate-log-file-name-function'."
(let ((file (concat
(if target (concat target "!"))
nick "@" server ":" (cond ((stringp port) port)
((numberp port)
(number-to-string port))) ".txt")))
(convert-standard-filename file)))
(defun erc-generate-log-file-name-network (buffer target nick server port)
"Generates a log-file name using the network name rather than server name.
This results in a file name of the form #channel!nick@network.txt.
This function is a possible value for `erc-generate-log-file-name-function'."
(require 'erc-networks)
(let ((file (concat
(if target (concat target "!"))
nick "@"
(or (with-current-buffer buffer (erc-network-name)) server)
".txt")))
(convert-standard-filename file)))
(defun erc-save-buffer-in-logs (&optional buffer)
"Append BUFFER contents to the log file, if logging is enabled.
If BUFFER is not provided, current buffer is used.
Logging is enabled if `erc-logging-enabled' returns non-nil.
This is normally done on exit, to save the unsaved portion of the
buffer, since only the text that runs off the buffer limit is logged
automatically.
You can save every individual message by putting this function on
`erc-insert-post-hook'."
(interactive)
(or buffer (setq buffer (current-buffer)))
(when (erc-logging-enabled buffer)
(let ((file (erc-current-logfile buffer))
(coding-system-for-write erc-log-file-coding-system))
(save-excursion
(with-current-buffer buffer
(save-restriction
(widen)
(when (and (markerp erc-last-saved-position)
(> erc-insert-marker (1+ erc-last-saved-position)))
(write-region (1+ (marker-position erc-last-saved-position))
(marker-position erc-insert-marker)
file t 'nomessage)
(if (and erc-truncate-buffer-on-save (interactive-p))
(progn
(let ((inhibit-read-only t)) (erase-buffer))
(move-marker erc-last-saved-position (point-max))
(erc-display-prompt))
(move-marker erc-last-saved-position
(1- (marker-position erc-insert-marker)))))
(set-buffer-modified-p nil))))))
t)
(provide 'erc-log)