blob: fb74901f4b979d0068133b641f93105aee0d2ec8 (
plain)
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
|
(defpackage nullbot
(:use #:cl
#:cl-hash-util)
(:local-nicknames
(:jzon :com.inuoe.jzon)
(:mapi :nullbot/matrix-api)
(:sseq :split-sequence)
(:dex :dexador))
(:export
#:start))
(in-package #:nullbot)
(defclass nullbot (mapi:matrix-bot) ())
(defparameter *bot* (make-instance 'nullbot
:token (uiop:getenv "NULLBOT_TOKEN")
:homeserver "matrix.nullring.xyz"))
(defparameter +feed-url+ "https://list.nullring.xyz/discussion/new.atom")
(defparameter +feed-room-id+ "!ShuXi5ohrPUtKHkrNO:matrix.nullring.xyz")
(defparameter +feed-cache-path+ #P"./nullbot_cache.sexp")
(defparameter +feed-sleep-minutes+ 1)
(defparameter +weather-vancouver+ )
(defparameter +prefix+ "$")
(defun get-temp (weather-station
&aux
(endpoint (format nil "https://api.weather.gc.ca/collections/swob-realtime/items?f=json&lang=en&url=C~A&sortby=-date_tm-value&limit=1&properties=date_tm-value,air_temp,air_temp-uom,air_temp-qa" weather-station))
(data (jzon:parse (dex:get endpoint))))
(hash-get (aref (gethash "features" data) 0) '("properties" "air_temp")))
(defun process-roommsg
(content room-id sender
&aux
(msgtype (gethash "msgtype" content))
(body (gethash "body" content))
(split-body (sseq:split-sequence #\Space body))
(command (car split-body)))
(format t "processing msg~%")
(when (and (> (length body) 0) (equal (aref (car split-body) 0) #\$))
(cond
((string= command "$help")
(mapi:sendmsg *bot* room-id "Unlike some other bots, I'm nice :3"))
((string= command "$weather")
(mapi:sendmsg *bot* room-id (format nil "It's ~a degrees in Vancouver~%It's ~a degrees in Victoria" (get-temp "YVR") (get-temp "YYJ")))))))
(defmethod mapi:on-event
((obj nullbot) event room-id
&aux
(msgtype (gethash "type" event))
(sender (gethash "sender" event)))
(cond
((string= msgtype "m.room.message")
(process-roommsg (gethash "content" event) room-id sender))))
(defun node-val (obj)
(car (xmls:node-children obj)))
(defun node-attr (obj name)
(second (assoc name (xmls:node-attrs obj) :test #'string=)))
;; TODO: make this into a generic f-n maybe and also make it not dumb
(defun get-node-by-name (obj name)
(check-type obj xmls:node)
(check-type name string)
(loop for child in (xmls:node-children obj)
when (and (xmls:node-p child) (string= name (xmls:node-name child)))
return child))
(defun send-entry (entry)
(mapi:sendmsg
*bot*
+feed-room-id+
(format nil "New message on mailing list!~%Title: ~a~%From: ~a~%Link: ~a~%"
(getf entry :title)
(getf entry :author)
(getf entry :link))))
(defun write-entries (entries)
(with-open-file (str +feed-cache-path+
:direction :output
:if-does-not-exist :create
:if-exists :supersede)
(format str "~s" entries)))
(defun feed-thread ()
(loop while (bt2:with-lock-held ((mapi:lock *bot*)) (mapi:listening *bot*)) do
(format t "Doing another poll~%")
(let* ((feed-str (dex:get +feed-url+))
(xmlobj (xmls:parse feed-str))
(entries (loop for entry in (xmls:node-children xmlobj)
when (string= (xmls:node-name entry) "entry")
collect `(:id ,(node-val (get-node-by-name entry "id"))
:title ,(node-val (get-node-by-name entry "title"))
:author ,(node-val (node-val (get-node-by-name entry "author")))
:link ,(node-attr (get-node-by-name entry "link") "href"))))
(cached-entries))
(if (uiop:file-exists-p +feed-cache-path+)
(setf cached-entries (read-from-string (uiop:read-file-string +feed-cache-path+)))
(write-entries entries))
(when cached-entries
(loop for entry in entries
when (not (find (getf entry :id)
cached-entries
:test #'string=
:key (lambda (e) (getf e :id))))
do (send-entry entry)))
;; update the cache with the new entries
(write-entries entries))
(sleep (* 60 +feed-sleep-minutes+))))
(defun start ()
(bt2:make-thread #'feed-thread :name "nullbot polling thread")
(mapi:start *bot*))
|