summaryrefslogtreecommitdiff
path: root/src/main.lisp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.lisp')
-rw-r--r--src/main.lisp354
1 files changed, 354 insertions, 0 deletions
diff --git a/src/main.lisp b/src/main.lisp
new file mode 100644
index 0000000..af88fe6
--- /dev/null
+++ b/src/main.lisp
@@ -0,0 +1,354 @@
1;; SPDX-License-Identifier: EUPL-1.2
2;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com>
3(defpackage :ukkoclot/main
4 (:nicknames :ukkoclot)
5 (:use :c2cl :ukkoclot/bot :ukkoclot/config :ukkoclot/inline-bots :ukkoclot/log :ukkoclot/tg-types)
6 (:import-from :anaphora :aand :awhen :it)
7 (:import-from :ukkoclot/bot :make-bot :bot-power-on)
8 (:import-from :ukkoclot/db :with-db)
9 (:import-from :ukkoclot/strings :escape-xml :is-tg-whitespace-str :starts-with :starts-with-ignore-case)
10 (:local-nicknames
11 (:jzon :com.inuoe.jzon))
12 (:export :main))
13(in-package :ukkoclot/main)
14
15(defvar *in-prod* t)
16
17(defmacro reporty ((evt) &body body)
18 `(if *in-prod*
19 (handler-case (progn ,@body)
20 (error (err) (report-error bot ,evt err)))
21 (progn ,@body)))
22
23(defun main ()
24 (unwind-protect
25 (let ((config (config-load #P"config.default.lisp")))
26 (config-merge config #P"config.lisp")
27 (log-info "Starting up ~A" (config-bot-name config))
28 (with-db (db (config-db-path config))
29 (let ((bot (make-bot config db)))
30 ;; TODO: Catch fatal errors & report them
31 (wrapped-main bot config))))
32 (log-info "We're done!")))
33
34(defun wrapped-main (bot config)
35 (send-message bot :chat-id (config-dev-group config) :text "Initializing...")
36 (set-my-name bot :name (config-bot-name config))
37 (let ((gup-offset 0))
38 (loop while (bot-power-on bot) do
39 (let ((updates (get-updates bot :timeout 60 :offset gup-offset)))
40 (loop for update across updates do
41 (unwind-protect
42 (progn
43 (awhen (update-message update)
44 (reporty (it)
45 (on-message bot it)))
46 (awhen (update-callback-query update)
47 (reporty (it)
48 (on-callback-query bot it))))
49 (setf gup-offset (1+ (update-update-id update)))))))
50 ;; One last getUpdates to make sure offset is stored on server
51 (get-updates bot :timeout 0 :limit 1 :offset gup-offset))
52 (send-message bot :chat-id (config-dev-group config) :text "Shutting down..."))
53
54(defun on-callback-query (bot cb)
55 (let ((data (callback-query-data cb)))
56 (cond ((and data
57 (starts-with data "bbl:")
58 (= (user-id (callback-query-from cb))
59 (config-owner (bot-config bot))))
60 (let ((bot-id (read-from-string data t nil :start 4)))
61 (blacklist-inline-bot bot bot-id))
62 (awhen (callback-query-message cb)
63 (delete-message bot
64 :chat-id (message-chat-id it)
65 :message-id (message-id it)))
66 (answer-callback-query bot
67 :callback-query-id (callback-query-id cb)
68 :text "OK"))
69 ((and data
70 (starts-with data "bwl:")
71 (= (user-id (callback-query-from cb))
72 (config-owner (bot-config bot))))
73 (let ((bot-id (read-from-string data t nil :start 4)))
74 (whitelist-inline-bot bot bot-id))
75 (awhen (callback-query-message cb)
76 (delete-message bot
77 :chat-id (message-chat-id it)
78 :message-id (message-id it)))
79 (answer-callback-query bot
80 :callback-query-id (callback-query-id cb)
81 :text "OK"))
82 (t
83 (log-info "Unrecognised callback query data: ~A" data)
84 (answer-callback-query bot
85 :callback-query-id (callback-query-id cb)
86 :text "Unallowed callback query, don't press the button again"
87 :show-alert t)))))
88
89
90(defun on-message (bot msg)
91 (block nil
92 (awhen (message-via-bot msg)
93 (unless (on-inline-bot bot msg it)
94 (return)))
95
96 (awhen (message-text msg)
97 (on-text-message bot msg it))
98
99 (awhen (message-new-chat-members msg)
100 (loop for new-chat-member across it do
101 (on-new-member bot msg new-chat-member)))))
102
103(defun on-new-member (bot msg new-member)
104 ;; TODO: Rule 11 no hating on cats on bot entry
105 ;; TODO: Rule 10 have fun and enjoy your time on user entry
106 (if (= (user-id new-member) (bot-id bot))
107 nil
108 (send-message bot
109 :chat-id (message-chat-id msg)
110 :text (concatenate 'string "Hello there, " (user-format-name new-member) "! Be on your bestest behaviour now!!")
111 :parse-mode "HTML"
112 :reply-parameters
113 (make-reply-parameters
114 :allow-sending-without-reply t
115 :message-id (message-id msg)
116 :chat-id (message-chat-id msg)))))
117
118(defun is-bad-text (text)
119 ;; TODO:
120 nil)
121
122(defun on-text-message (bot msg text)
123 (block nil
124 (when (is-bad-text text)
125 ;; TODO: Delete message, mute & warn user
126 ;; 0 current warns: 5 minute mute, +1 warn
127 ;; 1 current warn : 10 minute mute, +1 warn
128 ;; 2 current warns: 30 minute mute, +1 warn
129 ;; 3 current warns: 1 hour mute, +1 warn
130 ;; 4 current warns: 1 day mute, +1 warn
131 ;; 5 current warns: Ban
132 ;;
133 ;; warn gets removed after a month of no warns
134 (return))
135
136 (awhen (message-entities msg)
137 (loop for entity across it
138 when (and (eq (message-entity-type entity) :bot-command)
139 (= (message-entity-offset entity) 0))
140 do (on-text-command bot msg text (message-entity-extract entity text))))
141
142 (cond ((equal text ":3")
143 (send-message bot :chat-id (message-chat-id msg)
144 :text ">:3"
145 :reply-parameters (make-reply-parameters :message-id (message-id msg)
146 :chat-id (message-chat-id msg))))
147
148 ((equal text ">:3")
149 (send-message bot :chat-id (message-chat-id msg)
150 :text "<b>&gt;:3</b>"
151 :parse-mode "HTML"
152 :reply-parameters (make-reply-parameters
153 :message-id (message-id msg)
154 :chat-id (message-chat-id msg))))
155
156 ((starts-with-ignore-case text "big ")
157 (let ((the-text (subseq text 4)))
158 (unless (is-tg-whitespace-str the-text)
159 (send-message bot
160 :chat-id (message-chat-id msg)
161 :text (concatenate 'string
162 "<b>"
163 (escape-xml (string-upcase the-text))
164 "</b>")
165 :parse-mode "HTML"
166 :reply-parameters
167 (make-reply-parameters
168 :message-id (message-id msg)
169 :chat-id (message-chat-id msg))))))
170
171 ((string-equal text "dio cane")
172 (let ((reply-msg-id (message-id msg))
173 (reply-chat-id (message-chat-id msg)))
174 (awhen (message-reply-to-message msg)
175 (setf reply-msg-id (message-id it))
176 (setf reply-chat-id (message-chat-id it)))
177 (send-message bot
178 :chat-id (message-chat-id msg)
179 :text "porco dio"
180 :reply-parameters
181 (make-reply-parameters
182 :message-id reply-msg-id
183 :chat-id reply-chat-id))))
184
185 ((string-equal text "forgor")
186 (send-message bot
187 :chat-id (message-chat-id msg)
188 :text "💀"
189 :reply-parameters
190 (make-reply-parameters
191 :message-id (message-id msg)
192 :chat-id (message-chat-id msg))))
193
194 ((string-equal text "huh")
195 (send-message bot
196 :chat-id (message-chat-id msg)
197 :text "idgi"
198 :reply-parameters
199 (make-reply-parameters
200 :message-id (message-id msg)
201 :chat-id (message-chat-id msg))))
202
203 ((string= text "H")
204 (send-message bot
205 :chat-id (message-chat-id msg)
206 :text "<code>Randomly selected reminder that h &gt; H.</code>"
207 :parse-mode "HTML"
208 :reply-parameters
209 (make-reply-parameters
210 :message-id (message-id msg)
211 :chat-id (message-chat-id msg))))
212
213 ((string-equal text "porco dio")
214 (let ((reply-msg-id (message-id msg))
215 (reply-chat-id (message-chat-id msg)))
216 (awhen (message-reply-to-message msg)
217 (setf reply-msg-id (message-id it))
218 (setf reply-chat-id (message-chat-id it)))
219 (send-message bot
220 :chat-id (message-chat-id msg)
221 :text "dio cane"
222 :reply-parameters
223 (make-reply-parameters
224 :message-id reply-msg-id
225 :chat-id reply-chat-id))))
226
227 ((starts-with-ignore-case text "say ")
228 (let ((the-text (subseq text 4)))
229 (unless (is-tg-whitespace-str the-text)
230 (send-message bot
231 :chat-id (message-chat-id msg)
232 :text the-text
233 :reply-parameters
234 (make-reply-parameters
235 :message-id (message-id msg)
236 :chat-id (message-chat-id msg))))))
237
238 ((string-equal text "uwu")
239 (send-message bot
240 :chat-id (message-chat-id msg)
241 :text "OwO"
242 :reply-parameters
243 (make-reply-parameters
244 :message-id (message-id msg)
245 :chat-id (message-chat-id msg))))
246
247 ((string-equal text "waow")
248 (let ((reply-msg-id (message-id msg))
249 (reply-chat-id (message-chat-id msg)))
250 (awhen (message-reply-to-message msg)
251 (setf reply-msg-id (message-id it))
252 (setf reply-chat-id (message-chat-id it)))
253 (send-message bot
254 :chat-id (message-chat-id msg)
255 :text "BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED BASED"
256 :reply-parameters
257 (make-reply-parameters
258 :message-id reply-msg-id
259 :chat-id reply-chat-id))))
260
261 ((string-equal text "what")
262 (send-message bot
263 :chat-id (message-chat-id msg)
264 :text (with-output-to-string (s)
265 (if (char= (elt text 0) #\w)
266 (write-char #\g s)
267 (write-char #\G s))
268 (if (char= (elt text 1) #\h)
269 (write-string "ood " s)
270 (write-string "OOD " s))
271 (if (char= (elt text 2) #\a)
272 (write-string "gir" s)
273 (write-string "GIR" s))
274 (if (char= (elt text 3) #\t)
275 (write-char #\l s)
276 (write-char #\L s)))
277 :reply-parameters
278 (make-reply-parameters
279 :message-id (message-id msg)
280 :chat-id (message-chat-id msg))))
281 )))
282
283(defun simplify-cmd (bot cmd)
284 (let ((at-idx (position #\@ cmd)))
285 (if (null at-idx)
286 (subseq cmd 1)
287 (let ((username (subseq cmd (1+ at-idx)))
288 (my-username (bot-username bot)))
289 (if (equal username my-username)
290 (subseq cmd 1 at-idx)
291 nil)))))
292
293(defun on-text-command (bot msg text cmd)
294 (let ((simple-cmd (simplify-cmd bot cmd)))
295 (log-debug "text-command: ~A AKA ~A" cmd simple-cmd)
296 (cond ((equal simple-cmd "chatid")
297 (send-message bot :chat-id (message-chat-id msg)
298 :text (format nil "<code>~A</code>" (message-chat-id msg))
299 :parse-mode "HTML"
300 :reply-parameters (make-reply-parameters :message-id (message-id msg)
301 :chat-id (message-chat-id msg))))
302
303 ((equal simple-cmd "msginfo")
304 (aand (message-reply-to-message msg)
305 (send-message bot :chat-id (message-chat-id msg)
306 ;; TODO: Text needs lot more massaging
307 :text (jzon:stringify (arg-encode it))
308 :reply-parameters
309 (make-reply-parameters
310 :message-id (message-id msg)
311 :chat-id (message-chat-id msg)))))
312
313 ((equal simple-cmd "ping")
314 (let* ((start-time (get-internal-real-time))
315 (reply (send-message bot
316 :chat-id (message-chat-id msg)
317 :text "Pong!
318Send time: ..."
319 :reply-parameters
320 (make-reply-parameters
321 :message-id (message-id msg)
322 :chat-id (message-chat-id msg))))
323 (end-time (get-internal-real-time))
324 (time-elapsed (* (/ (- end-time start-time) internal-time-units-per-second)
325 1000)))
326 (edit-message-text bot
327 :chat-id (message-chat-id msg)
328 :message-id (message-id reply)
329 :text (format nil "Pong!~2%Send time: ~Gms" time-elapsed))))
330
331 ((and (equal simple-cmd "shutdown")
332 (message-from msg)
333 (= (user-id (message-from msg)) (config-owner (bot-config bot))))
334 (setf (bot-power-on bot) nil)
335 (send-message bot
336 :chat-id (message-chat-id msg)
337 :text "Initialising shutdown..."
338 :reply-parameters
339 (make-reply-parameters
340 :allow-sending-without-reply t
341 :message-id (message-id msg)
342 :chat-id (message-chat-id msg))))
343
344 )))
345
346(defun report-error (bot evt err)
347 (log-error "While handling ~A: ~A" evt err)
348 (let ((msg (format nil "<code>~A</code> while handling ~&<pre>~A</pre>"
349 (escape-xml (format nil "~A" err))
350 (escape-xml (format nil "~A" evt)))))
351 (send-message bot
352 :chat-id (config-dev-group (bot-config bot))
353 :text msg
354 :parse-mode "HTML")))