tree checksum vpatch file split hunks
all signers:
antecedents:
press order: 
| logbot-genesis | 
patch: 
(0 . 0)(1 . 26)
  5 INSTALL
  6 
  7  * Install `ircbot`.
  8 
  9  * From the SBCL REPL:
 10      (ql:quickload :cl-irc)
 11      (ql:quickload :cl-postgres)
 12      (ql:quickload :postmodern)
 13 
 14  * Use V to press `logbot`
 15 
 16 mkdir -p ~/src/logbot
 17 cd ~/src/logbot
 18 
 19 mkdir .wot
 20 cd .wot && wget http://trinque.org/trinque.asc && cd ..
 21 
 22 v.pl init http://trinque.org/src/logbot
 23 v.pl press logbot-genesis logbot-genesis.vpatch
 24      
 25 ln -s ~/src/logbot/logbot-genesis ~/quicklisp/local-projects/logbot
 26 
 27  * Create a PostgreSQL database with UTF-8 encoding, then load logbot.sql
 28    into that database:
 29 
 30 psql -f logbot.sql mydb
-(0 . 0)(1 . 7)
 35 README
 36 
 37 `logbot` extends `ircbot` to provide an interface to a single IRC
 38 channel from PostgreSQL, writing all messages to a `log` table and
 39 reading messages to be sent from `outbox` in a specified database. By
 40 listening to pg_notify channels, services connected to PostgreSQL can
 41 react when new log lines are inserted.
-(0 . 0)(1 . 14)
 46 USAGE
 47 
 48 (asdf:load-system :logbot)
 49 (defvar *bot*)
 50 (setf *bot*
 51     (logbot:make-logbot
 52     "chat.freenode.net" 6667 "nick" "password" "#channel"
 53     '("db-name" "db-user" "db-password" "db-host")))
 54 
 55 ; connect in separate thread, returning thread
 56 (logbot:ircbot-connect-thread *bot*)
 57 
 58 ; or connect using the current thread
 59 ; (logbot:ircbot-connect *bot*)
-(0 . 0)(1 . 13)
 64 ;;;; logbot.asd
 65 
 66 (asdf:defsystem #:logbot
 67   :description "logbot"
 68   :author "Michael Trinque <mike@trinque.org>"
 69   :license "http://trilema.com/2015/a-new-software-licensing-paradigm/"
 70   :depends-on (#:cl-irc
 71                #:cl-postgres
 72                #:ircbot
 73                #:postmodern)
 74   :components ((:file "package")
 75                (:file "logbot")))
 76 
-(0 . 0)(1 . 93)
 82 (in-package #:logbot)
 83 
 84 
 85 (defun get-and-purge-outbox-messages (db)
 86   (postmodern:with-connection db
 87     (postmodern:query
 88      "with deleted as (
 89           delete from outbox
 90           returning target, message, queued_at
 91       )
 92       select target,
 93              message
 94       from deleted
 95       order by queued_at"
 96      :rows)))
 97 
 98 (defun make-log-entry (db target message host source user)
 99   (postmodern:with-connection db
100     (postmodern:execute
101      "insert into log (target, message, host, source, \"user\")
102       values ($1, $2, $3, $4, $5)"
103      target
104      message
105      (if (string= "" host) :null host)
106      source
107      (if (null user) :null user))))
108 
109 
110 (defclass logbot (ircbot)
111   ((pg-thread :accessor logbot-pg-thread :initform nil)
112    (db :reader logbot-db :initarg :db)))
113 
114 (defmethod ircbot-connect :after ((bot logbot))
115   (let ((conn (ircbot-connection bot)))    
116     (add-hook conn 'irc-mode-message (lambda (message)
117                                        (logbot-check-mode bot message)))
118     (add-hook conn 'irc-privmsg-message (lambda (message)
119                                           (destructuring-bind (target message-text) (arguments message)
120                                             (make-log-entry (logbot-db bot)
121                                                             target
122                                                             message-text
123                                                             (host message)
124                                                             (source message)
125                                                             (user message)))))))
126 
127 (defmethod ircbot-send-message :after ((bot logbot) target message-text)
128   (let* ((b-connection (ircbot-connection bot))
129          (b-user (user b-connection)))
130     (make-log-entry (logbot-db bot)
131                     target
132                     message-text
133                     (hostname b-user)
134                     (nickname b-user)
135                     (username b-user))))
136 
137 (defmethod logbot-check-mode ((bot logbot) message)
138   (if (= 3 (length (arguments message)))
139       (destructuring-bind (channel mode nick) (arguments message)
140         (when (and (string= (host message) "services.")
141                    (string= channel (ircbot-channel bot))
142                    (or (string= mode "+o") (string= mode "+v"))
143                    (string= nick (ircbot-nick bot)))
144 
145           (when (null (logbot-pg-thread bot))
146             (logbot-start-pg-thread bot)
147             (logbot-send-outbox bot))))))
148 
149 (defmethod logbot-send-outbox ((bot logbot))
150   (loop
151      for (target message)
152      in (get-and-purge-outbox-messages (logbot-db bot))
153      do (ircbot-send-message bot target message)))
154 
155 (defmethod logbot-start-pg-thread ((bot logbot))
156   (setf (logbot-pg-thread bot)
157         (sb-thread:make-thread
158          (lambda ()
159            (postmodern:with-connection (logbot-db bot)
160              (postmodern:execute "listen outbox_new_message")
161              (loop
162                 (if (string= (cl-postgres:wait-for-notification postmodern:*database*)
163                              "outbox_new_message")
164                     (logbot-send-outbox bot)))))
165          :name "logbot-pg")))
166 
167 (defun make-logbot (server port nick password channel db)
168   (make-instance 'logbot
169                  :server server
170                  :port port
171                  :nick nick
172                  :password password
173                  :channel channel
174                  :db db))
-(0 . 0)(1 . 48)
179 set search_path = public;
180 
181 create extension if not exists plpgsql;
182 create extension if not exists pgcrypto;
183 create extension if not exists "uuid-ossp";
184 
185 create table log (
186     id uuid primary key default gen_random_uuid(),
187     target text not null,
188     message text not null,
189     host text,
190     source text not null,
191     "user" text,
192     received_at timestamp without time zone not null default (now() at time zone 'utc')
193 );
194 
195 create index log_received_at on log (received_at);
196 create index log_target on log (target);
197 create index log_source on log (source);
198 
199 create or replace function log_insert_notify () returns trigger as $$
200     begin
201         perform pg_notify('log_new_message', NEW.id::text);
202         return NEW;
203     end;
204 $$ language plpgsql;
205 
206 create trigger log_insert_notify_trigger
207 after insert on log
208 for each row execute procedure log_insert_notify ();
209     
210 create table outbox (
211     id serial primary key,
212     target text not null,
213     message text not null,
214     queued_at timestamp without time zone not null default (now() at time zone 'utc')
215 );
216 
217 create or replace function outbox_insert_notify () returns trigger as $$
218     begin
219         perform pg_notify('outbox_new_message', NEW.id::text);
220         return NEW;
221     end;
222 $$ language plpgsql;
223 
224 create trigger outbox_insert_notify_trigger
225 after insert on outbox
226 for each row execute procedure outbox_insert_notify ();
-(0 . 0)(1 . 20)
231 ;;;; package.lisp
232 
233 (defpackage :logbot
234   (:use :cl
235         :cl-irc
236         :ircbot)
237   (:export :make-logbot
238            :logbot
239            :ircbot-connect
240            :ircbot-connect-thread
241            :ircbot-disconnect
242            :ircbot-reconnect
243            :ircbot-connection
244            :ircbot-channel
245            :ircbot-send-message
246            :ircbot-server
247            :ircbot-port
248            :ircbot-nick
249            :ircbot-lag))
250