summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrei <andreisva2023@gmail.com>2026-02-13 20:57:05 -0800
committerAndrei <andreisva2023@gmail.com>2026-02-13 20:57:05 -0800
commit0fa9f69a97b9f695936b907424e4c3d1af6f7452 (patch)
tree9a471d3ab005105e8bfcff73184f6beef5dbfa1c
Initial Commit
-rw-r--r--.gitignore9
-rw-r--r--README.org5
-rw-r--r--nullbot.asd28
-rw-r--r--src/api.lisp122
-rw-r--r--src/main.lisp37
-rw-r--r--tests/main.lisp10
6 files changed, 211 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b9fa3c1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+*.abcl
+*.fasl
+*.dx32fsl
+*.dx64fsl
+*.lx32fsl
+*.lx64fsl
+*.x86f
+*~
+.#*
diff --git a/README.org b/README.org
new file mode 100644
index 0000000..b89e75e
--- /dev/null
+++ b/README.org
@@ -0,0 +1,5 @@
+* Matrix-Bot
+
+** Usage
+
+** Installation
diff --git a/nullbot.asd b/nullbot.asd
new file mode 100644
index 0000000..033d589
--- /dev/null
+++ b/nullbot.asd
@@ -0,0 +1,28 @@
+(defsystem "nullbot"
+ :version "0.0.1"
+ :author "Andrei Șova"
+ :license "MIT"
+ :depends-on (:com.inuoe.jzon
+ :dexador
+ :bordeaux-threads
+ :cl-hash-util
+ :quri
+ :flexi-streams
+ :split-sequence)
+ :components ((:module "src"
+ :components
+ ((:file "api")
+ (:file "main" :depends-on ("api")))))
+ :description "A bot for nullring on matrix"
+ :in-order-to ((test-op (test-op "nullbot/tests"))))
+
+(defsystem "nullbot/tests"
+ :author "Andrei Șova"
+ :license "MIT"
+ :depends-on (:nullbot
+ :rove)
+ :components ((:module "tests"
+ :components
+ ((:file "main"))))
+ :description "Test system for nullbot"
+ :perform (test-op (op c) (symbol-call :rove :run c)))
diff --git a/src/api.lisp b/src/api.lisp
new file mode 100644
index 0000000..fb4cc20
--- /dev/null
+++ b/src/api.lisp
@@ -0,0 +1,122 @@
+;; readers beware: this is currently a very barebones library
+
+(defpackage nullbot/matrix-api
+ (:use #:cl
+ #:cl-hash-util)
+ (:local-nicknames
+ (:jzon :com.inuoe.jzon)
+ (:fs :flexi-streams))
+ (:export
+ #:matrix-user
+ #:homeserver
+ #:name
+ #:listening
+ #:token
+ #:lock
+ #:matrix-bot
+ #:sendmsg
+ #:on-event
+ #:start
+ #:stop))
+(in-package #:nullbot/matrix-api)
+
+(defclass matrix-user ()
+ ((homeserver
+ :type string
+ :initarg :homeserver
+ :initform "matrix.org"
+ :reader homeserver)
+ (name
+ :type string
+ :initarg :name
+ :initform "matrix-user"
+ :reader name)
+ (listening
+ :type boolean
+ :initform nil
+ :accessor listening)
+ (token
+ :type string
+ :initarg :token
+ :initform ""
+ :reader token)
+ (lock
+ :type bt2:lock
+ :initform (bt2:make-lock)
+ :reader lock)))
+
+(defclass matrix-bot (matrix-user) ()
+ (:default-initargs :name "matrix-bot"))
+
+(defgeneric request (obj endpoint &rest rest)
+ (:method ((obj matrix-user) endpoint &rest rest &aux (headers))
+ (declare (type string endpoint))
+
+ (when (>= (length rest) 3) (setf headers (car (last rest))))
+ (bt2:with-lock-held ((lock obj))
+ (push `("Authorization" . ,(format nil "Bearer ~a" (token obj))) headers))
+ (jzon:parse (dexador:request (format nil "https://~a/_matrix/client/v3~a"
+ (homeserver obj) endpoint)
+ :headers headers
+ :method (car rest)
+ :content (jzon:stringify (cadr rest))
+ :verbose nil))))
+
+(defgeneric on-event (obj event room-id)
+ (:method ((obj matrix-user) event room-id)
+ (format t "Event Received: ~a~%" event)))
+
+(defun randint (start end)
+ (+ start (random (+ 1 (- end start)))))
+
+(defun rand-string (len &aux (arr (make-array len)))
+ (loop for i from 0 below len do
+ (setf (aref arr i) (randint 65 90)))
+ (fs:octets-to-string arr))
+
+(defgeneric sendmsg (obj room-id content)
+ (:method ((obj matrix-user) room-id content
+ &aux
+ (msg (make-hash-table :test #'equal))
+ (encoded-room-id (quri:url-encode room-id))
+ (unique-str (rand-string 20)))
+ (setf (gethash "msgtype" msg) "m.text")
+ (setf (gethash "body" msg) content)
+ (request obj (format nil "/rooms/~a/send/m.room.message/~a"
+ encoded-room-id
+ unique-str)
+ :put
+ msg
+ '(("Content-Type" . "application/json")))))
+
+(defgeneric get-events (obj rooms-join room-id)
+ (:method ((obj matrix-user) rooms-join room-id
+ &aux
+ (room-table (gethash room-id rooms-join))
+ (events
+ (hash-get room-table '("timeline" "events"))))
+ (when events
+ (loop for event across events do
+ (on-event obj event room-id)))))
+
+(defgeneric start (obj)
+ (:method-combination progn)
+ (:method ((obj matrix-user))
+ (setf (listening obj) t)
+ (bt2:make-thread (lambda (&aux
+ (since)
+ (sync-route "/sync?timeout=30000"))
+ (loop while (bt2:with-lock-held ((lock obj)) (listening obj)) do
+ (when since
+ (setf sync-route (format nil "/sync?timeout=30000&since=~a" since)))
+ (let* ((response (request obj sync-route :get))
+ (rooms-join (hash-get response '("rooms" "join"))))
+ (when rooms-join (loop for room-id being each hash-key of rooms-join
+ do (when since (get-events obj rooms-join room-id))))
+ (setf since (gethash "next_batch" response))))
+ (format t "Shutting down...~%"))
+ :name (format nil "~a Poll Thread" (name obj)))))
+
+(defgeneric stop (obj)
+ (:method ((obj matrix-user))
+ (bt2:with-lock-held ((lock obj)) (setf (listening obj) nil))))
diff --git a/src/main.lisp b/src/main.lisp
new file mode 100644
index 0000000..ee37ddc
--- /dev/null
+++ b/src/main.lisp
@@ -0,0 +1,37 @@
+(defpackage nullbot
+ (:use #:cl
+ #:cl-hash-util)
+ (:local-nicknames
+ (:jzon :com.inuoe.jzon)
+ (:mapi :nullbot/matrix-api)
+ (:sseq :split-sequence))
+ (:export
+ #:start))
+(in-package #:nullbot)
+
+(defclass nullbot (mapi:matrix-bot) ())
+(defparameter *bot* (make-instance 'nullbot
+ :token (uiop:getenv "NULLBOT_TOKEN")
+ :homeserver "matrix.nullring.xyz"))
+
+(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")))))
+
+(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))))
diff --git a/tests/main.lisp b/tests/main.lisp
new file mode 100644
index 0000000..84d23a9
--- /dev/null
+++ b/tests/main.lisp
@@ -0,0 +1,10 @@
+(defpackage matrix-bot/tests/main
+ (:use :cl
+ :matrix-bot
+ :rove))
+(in-package :matrix-bot/tests/main)
+
+;; NOTE: To run this test file, execute `(asdf:test-system :matrix-bot)' in your Lisp.
+(deftest test-target-1
+ (testing "should (= 1 1) to be true"
+ (ok (= 1 1))))