diff options
| author | Andrei <andreisva2023@gmail.com> | 2026-02-13 20:57:05 -0800 |
|---|---|---|
| committer | Andrei <andreisva2023@gmail.com> | 2026-02-13 20:57:05 -0800 |
| commit | 0fa9f69a97b9f695936b907424e4c3d1af6f7452 (patch) | |
| tree | 9a471d3ab005105e8bfcff73184f6beef5dbfa1c | |
Initial Commit
| -rw-r--r-- | .gitignore | 9 | ||||
| -rw-r--r-- | README.org | 5 | ||||
| -rw-r--r-- | nullbot.asd | 28 | ||||
| -rw-r--r-- | src/api.lisp | 122 | ||||
| -rw-r--r-- | src/main.lisp | 37 | ||||
| -rw-r--r-- | tests/main.lisp | 10 |
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)))) |
