summaryrefslogtreecommitdiff
path: root/src/bot/impl.lisp
blob: 93e63f57033146a96c80302b93c0ead6ee079ffb (plain) (blame)
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
;; SPDX-License-Identifier: EUPL-1.2
;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com>
(defpackage :ukkoclot/bot/impl
  (:use :c2cl :iterate :ukkoclot/config)
  (:import-from :anaphora :aand :acond :it)
  (:import-from :cl+ssl)
  (:import-from :dex)
  (:import-from :log)
  (:import-from :ukkoclot/serializing :fixup-args :parse-value)
  (:import-from :ukkoclot/strings :lisp->snake-case)
  (:local-nicknames
   (:jzon :com.inuoe.jzon))
  (:export
   :bot :bot-p :make-bot :do-call

   :bot-config :bot-db :bot-base-uri :bot-power-on :bot-username% :bot-id%))
(in-package :ukkoclot/bot/impl)

(defstruct (bot (:constructor make-bot%))
  (config (error "No value given for config") :read-only t)
  (db (error "No value given for DB") :read-only t)
  (base-uri (error "No value given for base-uri") :read-only t)
  (power-on t :type boolean)
  (username% nil :type (or string null))
  (id% nil :type (or integer null)))

(defun make-bot (config db)
  (let ((base-uri (concatenate 'string
                               "https://api.telegram.org/bot"
                               (config-bot-token config) "/")))
    (make-bot% :config config :db db :base-uri base-uri)))

(defun req (uri method content)
  (let ((retrier (dex:retry-request 5 :interval 1)))
    (handler-case (dex:request uri :method method :content content)
      (dex:http-request-too-many-requests (e) (dex:ignore-and-continue e)) ; We deal with too many reqs manually
      (dex:http-request-failed (e) (funcall retrier e))
      (cl+ssl::ssl-error (e) (funcall retrier e)))))

(defun do-call% (bot method uri type args-encoded)
  (let ((body (req uri method args-encoded)))
    (let ((hash (jzon:parse body)))
      (acond
        ((gethash "ok" hash) (parse-value type (gethash "result" hash)))
        ((aand (gethash "parameters" hash)
               (gethash "retry_after" it))
         (log:info "Should sleep for ~A seconds" it)
         (sleep it)
         (log:info "Good morning!")
         (do-call% bot method uri type args-encoded))
        (t (error "TG error ~A: ~A ~:A"
                  (gethash "error_code" hash)
                  (gethash "description" hash)
                  (gethash "parameters" hash)))))))

(defun do-call (bot method path type args)
  (let ((uri (concatenate 'string (bot-base-uri bot) path))
        (args-encoded (fixup-args args)))
    (log:debug "~A .../~A ~S" method path args-encoded)
    (do-call% bot method uri type args-encoded)))