Browse code
Add postmodern store, make references more reliable
Ed L authored on 29/06/2019 22:42:31
Showing 9 changed files
Showing 9 changed files
- hhgbot-2.lisp
- montezuma-store.lisp
- postmodern-store.lisp
- postmodern.lisp
- slack-client.lisp
- slacker.asd
- slackmessage-1.sql
- slackmessage.sql
- start.lisp
... | ... |
@@ -7,6 +7,17 @@ |
7 | 7 |
|
8 | 8 |
(cl:in-package :hhgbot-2) |
9 | 9 |
|
10 |
+(defun normalize-ref (value) |
|
11 |
+ (when (or (alexandria:ends-with-subseq "co" value) |
|
12 |
+ (alexandria:ends-with-subseq "s.c" value)) |
|
13 |
+ (setf value (format nil "~a." value))) |
|
14 |
+ (when (alexandria:ends-with-subseq "sc" value) |
|
15 |
+ (setf value (format nil "~as.c." (subseq value 0 (- (length value) 2))))) |
|
16 |
+ (when (alexandria:ends-with-subseq "sc." value) |
|
17 |
+ (setf value (format nil "~as.c." (subseq value 0 (- (length value) 3))))) |
|
18 |
+ |
|
19 |
+ value) |
|
20 |
+ |
|
10 | 21 |
;; Special Variables |
11 | 22 |
|
12 | 23 |
(defvar *xxx* (make-synonym-stream '*standard-output*)) |
... | ... |
@@ -21,9 +32,10 @@ |
21 | 32 |
(%conversations :initform (make-hash-table :test 'equal)))) |
22 | 33 |
|
23 | 34 |
|
24 |
-(defclass hhgbot-event-pump (slacker:event-pump slacker.montezuma-store:montezuma-store logging-slackbot) |
|
35 |
+(defclass hhgbot-event-pump (slacker:event-pump #+(or)slacker.montezuma-store:montezuma-store logging-slackbot |
|
36 |
+ slacker.postmodern-store:postmodern-store) |
|
25 | 37 |
() |
26 |
- (:default-initargs :index-path "/tmp/slack-idx/")) |
|
38 |
+ #+(or)(:default-initargs :index-path "/tmp/slack-idx/")) |
|
27 | 39 |
|
28 | 40 |
(defun start-in-repl (&optional (start-bot t) (team-id :atomampd)) |
29 | 41 |
(ubiquitous:restore :hhgbot-augmented-assistant) |
... | ... |
@@ -44,7 +56,8 @@ |
44 | 56 |
(let ((value (read-line))) |
45 | 57 |
(setf (ubiquitous:value :api-token team-id) value |
46 | 58 |
slacker::*api-token* value))) |
47 |
- (values (slacker:coordinate-threads *queue-pair* 'hhgbot-event-pump) |
|
59 |
+ (values (slacker:coordinate-threads *queue-pair* 'hhgbot-event-pump |
|
60 |
+ '(:postgres-connection-spec ("edwlan" "edwlan" nil :unix))) |
|
48 | 61 |
slacker::*api-token*))) |
49 | 62 |
|
50 | 63 |
(defun ensure-unescaped (src) |
... | ... |
@@ -90,10 +103,13 @@ |
90 | 103 |
|
91 | 104 |
(define-command "ref>" (event-pump message channel &optional source ref &rest args) |
92 | 105 |
(declare (ignore args)) |
106 |
+ (when args |
|
107 |
+ (setf ref (format nil "~a~{~a~}" ref args))) |
|
108 |
+ |
|
93 | 109 |
(if (and source ref) |
94 | 110 |
(when-let ((source-h (gethash source *refs*))) |
95 | 111 |
(queue-message event-pump channel |
96 |
- (gethash ref source-h (concat "Can't find " source " " ref)) |
|
112 |
+ (gethash (normalize-ref ref) source-h (concat "Can't find " source " " ref)) |
|
97 | 113 |
:thread (keep-in-thread message))) |
98 | 114 |
(queue-message event-pump channel (concat "Must provide both a source and a reference. See ;sources") |
99 | 115 |
:thread (keep-in-thread message)))) |
... | ... |
@@ -130,27 +146,30 @@ |
130 | 146 |
(values-list args)) |
131 | 147 |
|
132 | 148 |
(define-message-command "arc" (event-pump message channel &rest args) |
133 |
- (let ((r (with-output-to-string (s) |
|
134 |
- (multiple-value-bind (results idx) |
|
135 |
- (slacker.montezuma-store:search-index *client* "message" (string-join args " ")) |
|
136 |
- (montezuma:each results |
|
137 |
- (lambda (h) |
|
138 |
- (format s "> ~a: ~a~%" |
|
139 |
- (local-time:format-timestring |
|
140 |
- nil |
|
141 |
- (local-time:unix-to-timestamp |
|
142 |
- (floor |
|
143 |
- (parse-number |
|
149 |
+ (if (equal (gethash "user" message) |
|
150 |
+ "U0CSPP3SB") |
|
151 |
+ (let ((r (with-output-to-string (s) |
|
152 |
+ (multiple-value-bind (results idx) |
|
153 |
+ (slacker.montezuma-store:search-index *client* "message" (string-join args " ")) |
|
154 |
+ (montezuma:each results |
|
155 |
+ (lambda (h) |
|
156 |
+ (format s "> ~a: ~a~%" |
|
157 |
+ (local-time:format-timestring |
|
158 |
+ nil |
|
159 |
+ (local-time:unix-to-timestamp |
|
160 |
+ (floor |
|
161 |
+ (parse-number |
|
162 |
+ (montezuma:document-value (montezuma:get-document idx (montezuma:doc h)) |
|
163 |
+ "ts"))) |
|
164 |
+ ) |
|
165 |
+ :format local-time:+rfc3339-format+) |
|
166 |
+ |
|
144 | 167 |
(montezuma:document-value (montezuma:get-document idx (montezuma:doc h)) |
145 |
- "ts"))) |
|
146 |
- ) |
|
147 |
- :format local-time:+rfc3339-format+) |
|
148 |
- |
|
149 |
- (montezuma:document-value (montezuma:get-document idx (montezuma:doc h)) |
|
150 |
- "text")))))))) |
|
151 |
- (if (= 0 (length r)) |
|
152 |
- (format nil "No results found for: `~a`" (string-join args " ")) |
|
153 |
- r))) |
|
168 |
+ "text")))))))) |
|
169 |
+ (if (= 0 (length r)) |
|
170 |
+ (format nil "No results found for: `~a`" (string-join args " ")) |
|
171 |
+ r)) |
|
172 |
+ (format nil "No results found for: `~a`" (string-join args " ")))) |
|
154 | 173 |
|
155 | 174 |
(defun extract-channel-info (channels) |
156 | 175 |
(funcall (data-lens:pick |
... | ... |
@@ -266,7 +285,9 @@ Return a string with the generated JSON output." |
266 | 285 |
(define-controller quote (params) |
267 | 286 |
(let* ((text (cdr (assoc :text params))) |
268 | 287 |
(ref (cdr (assoc :ref params)))) |
269 |
- (cons (cons text ref) (gethash ref (gethash text hhgbot-2::*refs*))))) |
|
288 |
+ (cons (cons text ref) |
|
289 |
+ (gethash (hhgbot-2::normalize-ref ref) |
|
290 |
+ (gethash text hhgbot-2::*refs*))))) |
|
270 | 291 |
|
271 | 292 |
(define-controller random-quote (params) |
272 | 293 |
(declare (ignore params)) |
... | ... |
@@ -281,15 +302,15 @@ Return a string with the generated JSON output." |
281 | 302 |
(define-view random-quote (model) |
282 | 303 |
(destructuring-bind ((book . ref) . text) model |
283 | 304 |
`(302 |
284 |
- ("Location" ,(format nil "http://hhgbot.edw.ai:5000/q/~a/~a" |
|
305 |
+ ("Location" ,(format nil "/q/~a/~a" |
|
285 | 306 |
book ref)) |
286 | 307 |
(,text)))) |
287 | 308 |
|
288 | 309 |
(define-spinneret-view quote (quote) |
289 | 310 |
(let ((title (format nil "Quote: ~a, ~a" (caar quote) (cdar quote))) |
290 |
- (permalink (format nil "http://hhgbot.edw.ai:5000/q/~a/~a" |
|
311 |
+ (permalink (format nil "/q/~a/~a" |
|
291 | 312 |
(caar quote) |
292 |
- (cdar quote)))) |
|
313 |
+ (hhgbot-2::normalize-ref (cdar quote))))) |
|
293 | 314 |
(:html |
294 | 315 |
(:head (:title title) |
295 | 316 |
(:link :href "https://fonts.googleapis.com/css?family=Lato:400&subset=latin,latin-ext" :rel "stylesheet" :type "text/css") |
... | ... |
@@ -2,30 +2,45 @@ |
2 | 2 |
(:use :cl :alexandria :serapeum :fw.lu) |
3 | 3 |
(:export |
4 | 4 |
#:montezuma-store |
5 |
- #:search-index)) |
|
5 |
+ #:search-index |
|
6 |
+ #:retire-open-indices)) |
|
6 | 7 |
(in-package :slacker.montezuma-store) |
7 | 8 |
|
8 | 9 |
(defclass montezuma-store () |
9 |
- ((%indexes :reader indexes :initform (make-hash-table :test #'equal)) |
|
10 |
- (%montezuma-index-path :reader index-path :initarg :index-path)) |
|
10 |
+ ((%indexes :reader indexes |
|
11 |
+ :initform (make-hash-table :test #'equal :synchronized t)) |
|
12 |
+ (%montezuma-index-path :reader index-path |
|
13 |
+ :initarg :index-path)) |
|
11 | 14 |
(:default-initargs :index-path nil)) |
12 | 15 |
|
16 |
+(defun retire-open-indices (client) |
|
17 |
+ (sb-ext:with-locked-hash-table ((indexes client)) |
|
18 |
+ (let ((old-indexes (indexes client))) |
|
19 |
+ (setf (slot-value client '%indexes) |
|
20 |
+ (make-hash-table :test #'equal :synchronized t)) |
|
21 |
+ (alexandria:maphash-values (lambda (v) (montezuma:close v)) |
|
22 |
+ old-indexes)))) |
|
23 |
+ |
|
13 | 24 |
(defun search-index (store index text) |
14 |
- (let* ((indexes (indexes store)) |
|
15 |
- (index (gethash index indexes))) |
|
16 |
- (values (montezuma:search index (format nil "!text:\";arc\" text:~a" text) :num-docs 3) |
|
17 |
- index))) |
|
25 |
+ (let* ((index (ensure-index-for-type store index))) |
|
26 |
+ (values |
|
27 |
+ (montezuma:search index |
|
28 |
+ (format nil "!text:\";arc\" text:~a" text) |
|
29 |
+ :num-docs 3) |
|
30 |
+ index))) |
|
18 | 31 |
|
19 | 32 |
(defun ensure-index-for-type (store type) |
20 |
- (ensure-gethash type (indexes store) |
|
21 |
- (if (index-path store) |
|
22 |
- (make-instance 'montezuma:index :path (ensure-directories-exist |
|
23 |
- (format nil "~a/~a/" |
|
24 |
- (index-path store) |
|
25 |
- type)) |
|
26 |
- :create-p nil |
|
27 |
- :create-if-missing-p t) |
|
28 |
- (make-instance 'montezuma:index)))) |
|
33 |
+ (sb-ext:with-locked-hash-table ((indexes store)) |
|
34 |
+ (ensure-gethash type (indexes store) |
|
35 |
+ (if (index-path store) |
|
36 |
+ (make-instance 'montezuma:index |
|
37 |
+ :path (ensure-directories-exist |
|
38 |
+ (format nil "~a/~a/" |
|
39 |
+ (index-path store) |
|
40 |
+ type)) |
|
41 |
+ :create-p nil |
|
42 |
+ :create-if-missing-p t) |
|
43 |
+ (make-instance 'montezuma:index))))) |
|
29 | 44 |
|
30 | 45 |
(defgeneric combine-child (parent key value) |
31 | 46 |
(:method (parent k child) |
... | ... |
@@ -51,15 +66,16 @@ |
51 | 66 |
(do-hash-table (k v hash-table new) |
52 | 67 |
(combine-child new k v)))) |
53 | 68 |
|
54 |
- |
|
55 | 69 |
(defgeneric store-message (store message) |
56 | 70 |
(:method ((store montezuma-store) message) |
57 | 71 |
(let* ((type (gethash "type" message)) |
58 | 72 |
(index (ensure-index-for-type store type))) |
59 |
- (montezuma:add-document-to-index index (flatten-hash-table message)) |
|
73 |
+ (montezuma:add-document-to-index index |
|
74 |
+ (flatten-hash-table message)) |
|
60 | 75 |
(montezuma:flush index)))) |
61 | 76 |
|
62 |
-(defmethod slacker:handle-message :before (type (event-pump montezuma-store) ts channel message) |
|
77 |
+(defmethod slacker:handle-message :before |
|
78 |
+ (type (event-pump montezuma-store) ts channel message) |
|
63 | 79 |
(declare (ignore type ts channel)) |
64 | 80 |
(store-message event-pump message) |
65 | 81 |
(values)) |
66 | 82 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,25 @@ |
1 |
+(defpackage :slacker.postmodern-store |
|
2 |
+ (:use :cl :alexandria :serapeum :fw.lu) |
|
3 |
+ (:export #:postmodern-store)) |
|
4 |
+(in-package :slacker.postmodern-store) |
|
5 |
+ |
|
6 |
+(defclass postmodern-store () |
|
7 |
+ ((%connection :reader connection) |
|
8 |
+ (%connection-spec :reader connection-spec :initarg :postgres-connection-spec))) |
|
9 |
+ |
|
10 |
+(defmethod shared-initialize :after ((instance postmodern-store) slot-names &key postgres-connection-spec) |
|
11 |
+ (setf (slot-value instance '%connection) |
|
12 |
+ (apply 'postmodern:connect postgres-connection-spec))) |
|
13 |
+ |
|
14 |
+(defun save-slack-message (value) |
|
15 |
+ (pomo:query "INSERT INTO slack_messages_raw (message) VALUES ($1) RETURNING id,slack_ts,message_text;" |
|
16 |
+ value :rows)) |
|
17 |
+ |
|
18 |
+(defmethod slacker:handle-message :before |
|
19 |
+ (type (event-pump postmodern-store) ts channel message) |
|
20 |
+ (declare (ignore type ts channel)) |
|
21 |
+ (let ((pomo:*database* (connection event-pump))) |
|
22 |
+ (save-slack-message |
|
23 |
+ (with-output-to-string (s) |
|
24 |
+ (yason:encode message s)))) |
|
25 |
+ (values)) |
0 | 26 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,8 @@ |
1 |
+(defpackage :slacker.postgres-store |
|
2 |
+ (:use :cl :alexandria :serapeum :fw.lu) |
|
3 |
+ (:export #:postgres-store)) |
|
4 |
+(in-package :slacker.postgres-store) |
|
5 |
+ |
|
6 |
+(defclass postgres-store () |
|
7 |
+ ((%connection-string :reader connection-string :initarg :postgres-connection-string)) |
|
8 |
+ (:default-initargs :index-path nil)) |
... | ... |
@@ -105,10 +105,11 @@ |
105 | 105 |
|
106 | 106 |
(bt:make-thread (op (fwoar.event-loop:run-loop event-pump)))) |
107 | 107 |
|
108 |
-(defun start-client (implementation &key (queue-pair (make-instance 'queue-pair)) modules) |
|
109 |
- (let* ((event-pump (make-instance implementation |
|
110 |
- :queue-pair queue-pair |
|
111 |
- :client-factory (op (make-client _))))) |
|
108 |
+(defun start-client (implementation &key (queue-pair (make-instance 'queue-pair)) modules impl-args) |
|
109 |
+ (let* ((event-pump (apply #'make-instance implementation |
|
110 |
+ :queue-pair queue-pair |
|
111 |
+ :client-factory (op (make-client _)) |
|
112 |
+ impl-args))) |
|
112 | 113 |
(setf (running event-pump) t) |
113 | 114 |
(values event-pump |
114 | 115 |
(bt:make-thread (lambda () |
... | ... |
@@ -186,10 +187,11 @@ |
186 | 187 |
(reply ))) |
187 | 188 |
do (sleep 0.01))) |
188 | 189 |
|
189 |
-(defun coordinate-threads (&optional queue-pair (implementation 'event-pump)) |
|
190 |
+(defun coordinate-threads (&optional queue-pair (implementation 'event-pump) args) |
|
190 | 191 |
(let* ((event-pump (start-client implementation |
191 | 192 |
:queue-pair queue-pair |
192 |
- :modules '((hhgbot-augmented-assistant::js-executor))))) |
|
193 |
+ :modules '((hhgbot-augmented-assistant::js-executor)) |
|
194 |
+ :impl-args args))) |
|
193 | 195 |
(bt:make-thread (lambda () (loop until (running event-pump) |
194 | 196 |
finally (event-loop event-pump))) |
195 | 197 |
:name "Event Loop") |
... | ... |
@@ -2,9 +2,11 @@ |
2 | 2 |
(in-package :asdf-user) |
3 | 3 |
|
4 | 4 |
(asdf:defsystem #:slacker |
5 |
- :description "Describe hhgbot here" |
|
6 |
- :author "Your Name <your.name@example.com>" |
|
7 |
- :license "Specify license here" |
|
5 |
+ :description #.(concatenate 'string |
|
6 |
+ "A Client for Slack's RTM API" |
|
7 |
+ " and wrappers for its REST API") |
|
8 |
+ :author "Ed L <edward@elangley.org>" |
|
9 |
+ :license "Apache/v2" |
|
8 | 10 |
:depends-on (#:alexandria |
9 | 11 |
#:blackbird |
10 | 12 |
#:chanl |
... | ... |
@@ -40,3 +42,12 @@ |
40 | 42 |
#:montezuma) |
41 | 43 |
:serial t |
42 | 44 |
:components ((:file "montezuma-store"))) |
45 |
+ |
|
46 |
+(defsystem :slacker/postmodern-store |
|
47 |
+ :description "" |
|
48 |
+ :author "Ed L <edward@elangley.org>" |
|
49 |
+ :license "MIT" |
|
50 |
+ :depends-on (#:slacker |
|
51 |
+ #:postmodern) |
|
52 |
+ :serial t |
|
53 |
+ :components ((:file "postmodern-store"))) |
43 | 54 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,20 @@ |
1 |
+BEGIN; |
|
2 |
+ ALTER TABLE slack_messages_raw |
|
3 |
+ ADD COLUMN slack_channel text, |
|
4 |
+ ADD COLUMN slack_user text; |
|
5 |
+ |
|
6 |
+ UPDATE slack_messages_raw SET slack_channel = message->>'channel'; |
|
7 |
+ UPDATE slack_messages_raw SET slack_user = message->>'user'; |
|
8 |
+ |
|
9 |
+ CREATE OR REPLACE FUNCTION insert_messages_slack_ts() RETURNS TRIGGER AS $$ |
|
10 |
+ BEGIN |
|
11 |
+ NEW.slack_ts := NEW.message->>'ts'; |
|
12 |
+ NEW.slack_channel := NEW.message->>'channel'; |
|
13 |
+ NEW.slack_user := NEW.message->>'user'; |
|
14 |
+ |
|
15 |
+ IF not NEW.message ? 'hidden' OR NEW.message->'hidden' <> to_jsonb(true) THEN |
|
16 |
+ NEW.message_text := NEW.message #>> '{text}'; |
|
17 |
+ END IF; |
|
18 |
+ RETURN NEW; |
|
19 |
+ END; |
|
20 |
+ $$ LANGUAGE PLPGSQL; |
0 | 21 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,28 @@ |
1 |
+BEGIN; |
|
2 |
+ |
|
3 |
+ CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; |
|
4 |
+ |
|
5 |
+ DROP TABLE IF EXISTS slack_messages_raw CASCADE; |
|
6 |
+ CREATE TABLE slack_messages_raw |
|
7 |
+ ( |
|
8 |
+ id UUID PRIMARY KEY DEFAULT uuid_generate_v1(), |
|
9 |
+ message JSONB, |
|
10 |
+ message_text text, |
|
11 |
+ slack_ts NUMERIC(32, 6) |
|
12 |
+ ); |
|
13 |
+ |
|
14 |
+ CREATE OR REPLACE FUNCTION insert_messages_slack_ts() RETURNS TRIGGER AS $$ |
|
15 |
+ BEGIN |
|
16 |
+ NEW.slack_ts := NEW.message->>'ts'; |
|
17 |
+ IF not NEW.message ? 'hidden' OR NEW.message->'hidden' <> to_jsonb(true) THEN |
|
18 |
+ NEW.message_text := NEW.message #>> '{text}'; |
|
19 |
+ END IF; |
|
20 |
+ RETURN NEW; |
|
21 |
+ END; |
|
22 |
+ $$ LANGUAGE PLPGSQL; |
|
23 |
+ |
|
24 |
+ CREATE TRIGGER trig_insert_messages_slack_ts |
|
25 |
+ BEFORE INSERT ON slack_messages_raw |
|
26 |
+ FOR EACH ROW EXECUTE PROCEDURE insert_messages_slack_ts(); |
|
27 |
+ |
|
28 |
+COMMIT; |
0 | 29 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,31 @@ |
1 |
+(in-package :cl-user) |
|
2 |
+(defpackage :foobar-baz (:use :cl)) |
|
3 |
+(in-package :foobar-baz) |
|
4 |
+ |
|
5 |
+(defmacro eval-always (() &body body) |
|
6 |
+ `(eval-when (:compile-toplevel :load-toplevel :execute) |
|
7 |
+ ,@body)) |
|
8 |
+ |
|
9 |
+ |
|
10 |
+(eval-always () |
|
11 |
+ (ql:quickload :slacker) |
|
12 |
+ (ql:quickload :slacker/montezuma-store) |
|
13 |
+ (ql:quickload :slacker/postmodern-store) |
|
14 |
+ (ql:quickload :data-lens) |
|
15 |
+ (ql:quickload :osicat) |
|
16 |
+ (ql:quickload :trivial-shell) |
|
17 |
+ (ql:quickload :clack) |
|
18 |
+ (ql:quickload :ningle) |
|
19 |
+ (ql:quickload :araneus)) |
|
20 |
+ |
|
21 |
+(eval-always () |
|
22 |
+ (load (compile-file "/home/edwlan/words_coprocess-work/words-coprocess.lisp"))) |
|
23 |
+ |
|
24 |
+(eval-always () |
|
25 |
+ (load (compile-file (asdf:system-relative-pathname :slacker "hhgbot-2.lisp")))) |
|
26 |
+ |
|
27 |
+#+(or)(eval-always () |
|
28 |
+ (load (compile-file "/home/edwlan/words_coprocess-work/words-coprocess.lisp"))) |
|
29 |
+ |
|
30 |
+ |
|
31 |
+ |