diff options
35 files changed, 613 insertions, 309 deletions
diff --git a/LICENSES/LicenseRef-RandomGIFs.txt b/LICENSES/LicenseRef-RandomGIFs.txt new file mode 100644 index 0000000..408378d --- /dev/null +++ b/LICENSES/LicenseRef-RandomGIFs.txt | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | These are random gifs I've gathered on Telegram. I don't own them, and who the fuck knows who made them, the videos | ||
| 2 | could probably be sourced if needed. These are used under the implied "meme license": just don't make money off of it. | ||
| @@ -12,3 +12,9 @@ version = 1 | |||
| 12 | path = ["README.md"] | 12 | path = ["README.md"] |
| 13 | SPDX-License-Identifier = "EUPL-1.2" | 13 | SPDX-License-Identifier = "EUPL-1.2" |
| 14 | SPDX-FileCopyrightText = " 2025 Uko Kokņevičs <perkontevs@gmail.com>" | 14 | SPDX-FileCopyrightText = " 2025 Uko Kokņevičs <perkontevs@gmail.com>" |
| 15 | |||
| 16 | [[annotations]] | ||
| 17 | label = "Random \"gifs\"" | ||
| 18 | path = ["blob/do-not.mp4", "blob/rule-10.mp4", "blob/rule-11.mp4"] | ||
| 19 | SPDX-License-Identifier = "LicenseRef-RandomGIFs" | ||
| 20 | SPDX-FileCopyrightText = "Unknown" | ||
diff --git a/blob/do-not.mp4 b/blob/do-not.mp4 new file mode 100644 index 0000000..408e627 --- /dev/null +++ b/blob/do-not.mp4 | |||
| Binary files differ | |||
diff --git a/blob/rule-10.mp4 b/blob/rule-10.mp4 new file mode 100644 index 0000000..3dcc60e --- /dev/null +++ b/blob/rule-10.mp4 | |||
| Binary files differ | |||
diff --git a/blob/rule-11.mp4 b/blob/rule-11.mp4 new file mode 100644 index 0000000..ec90c87 --- /dev/null +++ b/blob/rule-11.mp4 | |||
| Binary files differ | |||
diff --git a/src/bot/advanced.lisp b/src/bot/advanced.lisp index a6ad9ba..241b04d 100644 --- a/src/bot/advanced.lisp +++ b/src/bot/advanced.lisp | |||
| @@ -2,7 +2,7 @@ | |||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> |
| 3 | (defpackage :ukkoclot/bot/advanced | 3 | (defpackage :ukkoclot/bot/advanced |
| 4 | (:use :c2cl :ukkoclot/bot/impl :ukkoclot/bot/methods :ukkoclot/tg-types) | 4 | (:use :c2cl :ukkoclot/bot/impl :ukkoclot/bot/methods :ukkoclot/tg-types) |
| 5 | (:export :bot-id :bot-username :reply-message)) | 5 | (:export :bot-id :bot-username :reply-animation :reply-message :try-delete-message)) |
| 6 | (in-package :ukkoclot/bot/advanced) | 6 | (in-package :ukkoclot/bot/advanced) |
| 7 | 7 | ||
| 8 | (defun bot-id (bot) | 8 | (defun bot-id (bot) |
| @@ -17,6 +17,20 @@ | |||
| 17 | (get-me bot) | 17 | (get-me bot) |
| 18 | (bot-username% bot)))) | 18 | (bot-username% bot)))) |
| 19 | 19 | ||
| 20 | ;; TODO: Some kind of caching for files? | ||
| 21 | (defun reply-animation (bot msg animation &key allow-sending-without-reply text parse-mode caption-above) | ||
| 22 | (send-animation bot | ||
| 23 | :chat-id (message-chat-id msg) | ||
| 24 | :animation animation | ||
| 25 | :caption text | ||
| 26 | :parse-mode parse-mode | ||
| 27 | :show-caption-above-media caption-above | ||
| 28 | :reply-parameters | ||
| 29 | (make-reply-parameters | ||
| 30 | :allow-sending-without-reply allow-sending-without-reply | ||
| 31 | :message-id (message-id msg) | ||
| 32 | :chat-id (message-chat-id msg)))) | ||
| 33 | |||
| 20 | (defun reply-message (bot msg text &key parse-mode allow-sending-without-reply) | 34 | (defun reply-message (bot msg text &key parse-mode allow-sending-without-reply) |
| 21 | (send-message bot | 35 | (send-message bot |
| 22 | :chat-id (message-chat-id msg) | 36 | :chat-id (message-chat-id msg) |
| @@ -27,3 +41,14 @@ | |||
| 27 | :allow-sending-without-reply allow-sending-without-reply | 41 | :allow-sending-without-reply allow-sending-without-reply |
| 28 | :message-id (message-id msg) | 42 | :message-id (message-id msg) |
| 29 | :chat-id (message-chat-id msg)))) | 43 | :chat-id (message-chat-id msg)))) |
| 44 | |||
| 45 | (defun try-delete-message (bot msg) | ||
| 46 | (handler-case | ||
| 47 | (delete-message bot | ||
| 48 | :chat-id (message-chat-id msg) | ||
| 49 | :message-id (message-id msg)) | ||
| 50 | (error () | ||
| 51 | (handler-case | ||
| 52 | (reply-animation bot msg #P"blob/do-not.mp4" | ||
| 53 | :allow-sending-without-reply nil) | ||
| 54 | (error () nil))))) | ||
diff --git a/src/bot/impl.lisp b/src/bot/impl.lisp index b57e2d3..57a9572 100644 --- a/src/bot/impl.lisp +++ b/src/bot/impl.lisp | |||
| @@ -8,41 +8,62 @@ | |||
| 8 | (:local-nicknames | 8 | (:local-nicknames |
| 9 | (:jzon :com.inuoe.jzon)) | 9 | (:jzon :com.inuoe.jzon)) |
| 10 | (:export | 10 | (:export |
| 11 | :arg-encode :bot :bot-p :make-bot :do-call | 11 | :bot :bot-p :make-bot :fixup-value :do-call :parse-value |
| 12 | 12 | ||
| 13 | :bot-config :bot-db :bot-base-uri :bot-power-on :bot-username% :bot-id%)) | 13 | :bot-config :bot-db :bot-base-uri :bot-power-on :bot-username% :bot-id%)) |
| 14 | (in-package :ukkoclot/bot/impl) | 14 | (in-package :ukkoclot/bot/impl) |
| 15 | 15 | ||
| 16 | (defgeneric will-arg-encode (object) | 16 | (defgeneric parse-value (type json) |
| 17 | (:documentation "Whether the OBJECT has any transformation done to it by arg-encode") | 17 | (:documentation "Parse value of TYPE from the parsed JSON") |
| 18 | (:method (obj) | 18 | (:method (type json) |
| 19 | nil) | 19 | (log-error "I don't know how to parse simple type ~A!" type) |
| 20 | (:method ((obj cons)) | 20 | (error "I don't know how to parse simple type ~A!" type)) |
| 21 | (or (will-arg-encode (car obj)) | 21 | (:method ((type (eql 'boolean)) json) |
| 22 | (will-arg-encode (cdr obj))))) | 22 | (check-type json boolean) |
| 23 | json) | ||
| 24 | (:method ((type (eql 'integer)) json) | ||
| 25 | (check-type json integer) | ||
| 26 | json) | ||
| 27 | (:method ((type (eql 'null)) json) | ||
| 28 | (check-type json null) | ||
| 29 | json) | ||
| 30 | (:method ((type (eql 'string)) json) | ||
| 31 | (check-type json string) | ||
| 32 | json)) | ||
| 23 | 33 | ||
| 24 | (defgeneric arg-encode (object) | 34 | (defun try-parse-value (type json) |
| 25 | (:documentation "Encode the OBJECT to a form that our HTTP code is happy about.") | 35 | (handler-case (values t (parse-value type json)) |
| 26 | (:method (obj) | 36 | (error () (values nil nil)))) |
| 27 | obj) | ||
| 28 | (:method ((obj cons)) | ||
| 29 | (if (not (will-arg-encode obj)) | ||
| 30 | obj | ||
| 31 | (cons (arg-encode (car obj)) | ||
| 32 | (arg-encode (cdr obj)))))) | ||
| 33 | 37 | ||
| 34 | (defgeneric fixup-arg (value) | 38 | (defmethod parse-value ((type cons) json) |
| 35 | (:documentation "Make sure Telegram & QURI & whatever like the arg") | 39 | (cond ((and (eq (car type) 'array) |
| 40 | (null (cddr type))) | ||
| 41 | (when json | ||
| 42 | (let ((element-type (cadr type))) | ||
| 43 | (iter (for element in-vector json) | ||
| 44 | (collect (parse-value element-type element) result-type vector))))) | ||
| 45 | ((eq (car type) 'or) | ||
| 46 | (iter (for el-type in (cdr type)) | ||
| 47 | (multiple-value-bind (success res) (try-parse-value el-type json) | ||
| 48 | (when success | ||
| 49 | (return res))) | ||
| 50 | (finally | ||
| 51 | (error "Failed to parse ~S as ~A!" json type)))) | ||
| 52 | (t | ||
| 53 | (error "I don't know how to parse complex type ~A!" type)))) | ||
| 54 | |||
| 55 | (defgeneric fixup-value (value) | ||
| 56 | (:documentation "Fixup top-level VALUE before passing it onto telegram") | ||
| 36 | (:method (value) | 57 | (:method (value) |
| 37 | (error "Unsupported argument type ~S (~S)" (class-name (class-of value)) value)) | 58 | (jzon:stringify value)) |
| 38 | (:method ((value null)) | 59 | (:method ((value null)) |
| 39 | value) | 60 | value) |
| 40 | (:method ((value number)) | 61 | (:method ((value number)) |
| 41 | value) | 62 | value) |
| 42 | (:method ((value string)) | 63 | (:method ((value pathname)) |
| 43 | value) | 64 | value) |
| 44 | (:method ((value hash-table)) | 65 | (:method ((value string)) |
| 45 | (jzon:stringify value))) | 66 | value)) |
| 46 | 67 | ||
| 47 | (defstruct (bot (:constructor make-bot%)) | 68 | (defstruct (bot (:constructor make-bot%)) |
| 48 | (config (error "No value given for config") :read-only t) | 69 | (config (error "No value given for config") :read-only t) |
| @@ -58,39 +79,35 @@ | |||
| 58 | (config-bot-token config) "/"))) | 79 | (config-bot-token config) "/"))) |
| 59 | (make-bot% :config config :db db :base-uri base-uri))) | 80 | (make-bot% :config config :db db :base-uri base-uri))) |
| 60 | 81 | ||
| 61 | (defun args-plist->alist (args-plist) | 82 | (defun fixup-args (args) |
| 62 | (iter (for (old-key value) on args-plist by #'cddr) | 83 | (iter (for (key . value) in args) |
| 63 | (collect | 84 | (collect |
| 64 | (let ((new-key (string-downcase (lisp->snake-case (symbol-name old-key))))) | 85 | (cons (string-downcase (lisp->snake-case (symbol-name key))) |
| 65 | (cons new-key value))))) | 86 | (fixup-value value))))) |
| 66 | |||
| 67 | (defun fixup-args (args-alist) | ||
| 68 | (iter (for (name . value) in args-alist) | ||
| 69 | (collecting (cons name (fixup-arg (arg-encode value)))))) | ||
| 70 | 87 | ||
| 71 | (defun req (uri method content) | 88 | (defun req (uri method content) |
| 72 | ;; We deal with this manually | 89 | ;; We deal with this manually |
| 73 | (handler-bind ((dex:http-request-too-many-requests #'dex:ignore-and-continue)) | 90 | (handler-bind ((dex:http-request-too-many-requests #'dex:ignore-and-continue)) |
| 74 | (dex:request uri :method method :content content))) | 91 | (dex:request uri :method method :content content))) |
| 75 | 92 | ||
| 76 | (defun do-call% (bot method uri mapfn args-encoded) | 93 | (defun do-call% (bot method uri type args-encoded) |
| 77 | (let ((body (req uri method args-encoded))) | 94 | (let ((body (req uri method args-encoded))) |
| 78 | (let ((hash (jzon:parse body))) | 95 | (let ((hash (jzon:parse body))) |
| 79 | (acond | 96 | (acond |
| 80 | ((gethash "ok" hash) (funcall mapfn (gethash "result" hash))) | 97 | ((gethash "ok" hash) (parse-value type (gethash "result" hash))) |
| 81 | ((aand (gethash "parameters" hash) | 98 | ((aand (gethash "parameters" hash) |
| 82 | (gethash "retry_after" it)) | 99 | (gethash "retry_after" it)) |
| 83 | (log-info "Should sleep for ~A seconds" it) | 100 | (log-info "Should sleep for ~A seconds" it) |
| 84 | (sleep it) | 101 | (sleep it) |
| 85 | (log-info "Good morning!") | 102 | (log-info "Good morning!") |
| 86 | (do-call% bot method uri mapfn args-encoded)) | 103 | (do-call% bot method uri type args-encoded)) |
| 87 | (t (error "TG error ~A: ~A ~:A" | 104 | (t (error "TG error ~A: ~A ~:A" |
| 88 | (gethash "error_code" hash) | 105 | (gethash "error_code" hash) |
| 89 | (gethash "description" hash) | 106 | (gethash "description" hash) |
| 90 | (gethash "parameters" hash))))))) | 107 | (gethash "parameters" hash))))))) |
| 91 | 108 | ||
| 92 | (defun do-call (bot method path mapfn args-plist) | 109 | (defun do-call (bot method path type args) |
| 93 | (let ((uri (concatenate 'string (bot-base-uri bot) path)) | 110 | (let ((uri (concatenate 'string (bot-base-uri bot) path)) |
| 94 | (args-encoded (fixup-args (args-plist->alist args-plist)))) | 111 | (args-encoded (fixup-args args))) |
| 95 | (log-debug "~A .../~A ~S" method path args-encoded) | 112 | (log-debug "~A .../~A ~S" method path args-encoded) |
| 96 | (do-call% bot method uri mapfn args-encoded))) | 113 | (do-call% bot method uri type args-encoded))) |
diff --git a/src/bot/methods.lisp b/src/bot/methods.lisp index 99b6411..7ccc4ad 100644 --- a/src/bot/methods.lisp +++ b/src/bot/methods.lisp | |||
| @@ -2,22 +2,22 @@ | |||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> |
| 3 | (defpackage :ukkoclot/bot/methods | 3 | (defpackage :ukkoclot/bot/methods |
| 4 | (:use :c2cl :ukkoclot/bot/impl :ukkoclot/tg-types :ukkoclot/tg-types/macros) | 4 | (:use :c2cl :ukkoclot/bot/impl :ukkoclot/tg-types :ukkoclot/tg-types/macros) |
| 5 | (:export :answer-callback-query :delete-message :edit-message-text :get-me :get-updates :send-message :set-my-name)) | 5 | (:export :answer-callback-query :delete-message :send-animation :edit-message-text :get-me :get-updates :send-message :set-my-name)) |
| 6 | (in-package :ukkoclot/bot/methods) | 6 | (in-package :ukkoclot/bot/methods) |
| 7 | 7 | ||
| 8 | (define-tg-method (answer-callback-query boolean "answerCallbackQuery" #'identity) | 8 | (define-tg-method (answer-callback-query boolean "answerCallbackQuery") |
| 9 | (callback-query-id string) | 9 | (callback-query-id string) |
| 10 | (text (or string null) nil) | 10 | (text (or string null) nil) |
| 11 | (show-alert boolean nil) | 11 | (show-alert boolean nil) |
| 12 | (url (or string null) nil) | 12 | (url (or string null) nil) |
| 13 | (cache-time (or integer null) nil)) | 13 | (cache-time (or integer null) nil)) |
| 14 | 14 | ||
| 15 | (define-tg-method (delete-message boolean "deleteMessage" #'identity) | 15 | (define-tg-method (delete-message boolean "deleteMessage") |
| 16 | (chat-id (or integer string)) | 16 | (chat-id (or integer string)) |
| 17 | (message-id integer)) | 17 | (message-id integer)) |
| 18 | 18 | ||
| 19 | ;; TODO: Add a way to simply specify :message msg :) | 19 | ;; TODO: Add a way to simply specify :message msg :) |
| 20 | (define-tg-method (edit-message-text message "editMessageText" #'hash->message) | 20 | (define-tg-method (edit-message-text message "editMessageText") |
| 21 | (business-connection-id (or string null) nil) | 21 | (business-connection-id (or string null) nil) |
| 22 | (chat-id (or integer string null) nil) | 22 | (chat-id (or integer string null) nil) |
| 23 | (message-id (or integer null) nil) | 23 | (message-id (or integer null) nil) |
| @@ -28,7 +28,7 @@ | |||
| 28 | (link-preview-options (or link-preview-options null) nil) | 28 | (link-preview-options (or link-preview-options null) nil) |
| 29 | (reply-markup (or inline-keyboard-markup null) nil)) | 29 | (reply-markup (or inline-keyboard-markup null) nil)) |
| 30 | 30 | ||
| 31 | (define-tg-method (get-me% user "getMe" #'hash->user :GET)) | 31 | (define-tg-method (get-me% user "getMe" :GET)) |
| 32 | 32 | ||
| 33 | (defun get-me (bot) | 33 | (defun get-me (bot) |
| 34 | (let ((res (get-me% bot))) | 34 | (let ((res (get-me% bot))) |
| @@ -36,16 +36,39 @@ | |||
| 36 | (setf (bot-username% bot) (user-username res)) | 36 | (setf (bot-username% bot) (user-username res)) |
| 37 | res)) | 37 | res)) |
| 38 | 38 | ||
| 39 | (define-tg-method (get-my-name bot-name "getMyName" #'hash->bot-name :GET) | 39 | (define-tg-method (get-my-name bot-name "getMyName" :GET) |
| 40 | (language-code (or string null) nil)) | 40 | (language-code (or string null) nil)) |
| 41 | 41 | ||
| 42 | (define-tg-method (get-updates (array update) "getUpdates" #'parse-update-array) | 42 | (define-tg-method (get-updates (array update) "getUpdates") |
| 43 | (offset (or integer null) nil) | 43 | (offset (or integer null) nil) |
| 44 | (limit (or integer null) nil) | 44 | (limit (or integer null) nil) |
| 45 | (timeout (or integer null) nil) | 45 | (timeout (or integer null) nil) |
| 46 | (allowed-updates (or string null) nil)) | 46 | (allowed-updates (or string null) nil)) |
| 47 | 47 | ||
| 48 | (define-tg-method (send-message message "sendMessage" #'hash->message) | 48 | (define-tg-method (send-animation message "sendAnimation") |
| 49 | (business-connection-id (or string null) nil) | ||
| 50 | (chat-id (or integer string)) | ||
| 51 | (message-thread-id (or integer null) nil) | ||
| 52 | (direct-messages-topic-id (or integer null) nil) | ||
| 53 | (animation (or pathname string)) | ||
| 54 | (duration (or integer null) nil) | ||
| 55 | (width (or integer null) nil) | ||
| 56 | (height (or integer null) nil) | ||
| 57 | (thumbnail (or pathname string null) nil) | ||
| 58 | (caption (or string null) nil) | ||
| 59 | (parse-mode (or string null) nil) | ||
| 60 | (caption-entities (or (array message-entity) null) nil) | ||
| 61 | (show-caption-above-media boolean nil) | ||
| 62 | (has-spoiler boolean nil) | ||
| 63 | (disable-notification boolean nil) | ||
| 64 | (protect-content boolean nil) | ||
| 65 | (allow-paid-broadcast boolean nil) | ||
| 66 | (message-effect-id (or string null) nil) | ||
| 67 | (suggested-post-parameters (or suggested-post-parameters null) nil) | ||
| 68 | (reply-parameters (or reply-parameters null) nil) | ||
| 69 | (reply-markup (or inline-keyboard-markup reply-keyboard-markup reply-keyboard-remove force-reply null) nil)) | ||
| 70 | |||
| 71 | (define-tg-method (send-message message "sendMessage") | ||
| 49 | (business-connection-id (or string null) nil) | 72 | (business-connection-id (or string null) nil) |
| 50 | (chat-id (or integer string)) | 73 | (chat-id (or integer string)) |
| 51 | (message-thread-id (or integer null) nil) | 74 | (message-thread-id (or integer null) nil) |
| @@ -58,12 +81,9 @@ | |||
| 58 | (protect-content (or boolean null) nil) | 81 | (protect-content (or boolean null) nil) |
| 59 | (message-effect-id (or string null) nil) | 82 | (message-effect-id (or string null) nil) |
| 60 | (reply-parameters (or reply-parameters null) nil) | 83 | (reply-parameters (or reply-parameters null) nil) |
| 61 | (reply-markup (or inline-keyboard-markup | 84 | (reply-markup (or inline-keyboard-markup reply-keyboard-markup reply-keyboard-remove force-reply null) nil)) |
| 62 | ;; TODO: reply-keyboard-markup | ||
| 63 | ;; TODO: reply-keyboard-remove | ||
| 64 | force-reply null) nil)) | ||
| 65 | 85 | ||
| 66 | (define-tg-method (set-my-name% boolean "setMyName" #'identity) | 86 | (define-tg-method (set-my-name% boolean "setMyName") |
| 67 | (name (or string null) nil) | 87 | (name (or string null) nil) |
| 68 | (language-code (or string null) nil)) | 88 | (language-code (or string null) nil)) |
| 69 | 89 | ||
diff --git a/src/inline-bots.lisp b/src/inline-bots.lisp index 5945084..eb20f21 100644 --- a/src/inline-bots.lisp +++ b/src/inline-bots.lisp | |||
| @@ -20,23 +20,20 @@ | |||
| 20 | (log-info "Deleting an unallowed inline bot message from ~A ~A" | 20 | (log-info "Deleting an unallowed inline bot message from ~A ~A" |
| 21 | (user-username via) | 21 | (user-username via) |
| 22 | (user-id via)) | 22 | (user-id via)) |
| 23 | (delete-message bot | 23 | (try-delete-message bot msg) |
| 24 | :chat-id (message-chat-id msg) | ||
| 25 | :message-id (message-id msg)) | ||
| 26 | (unless (eq ty :blacklisted) | 24 | (unless (eq ty :blacklisted) |
| 27 | ;; Not explicitly blacklisted, notify dev group | 25 | ;; Not explicitly blacklisted, notify dev group |
| 28 | (send-message bot | 26 | (let ((whitelist (make-inline-keyboard-button :text "Whitelist" |
| 29 | :chat-id (config-dev-group (bot-config bot)) | 27 | :callback-data (format nil "bwl:~A" (user-id via)))) |
| 30 | :text (format nil "Deleted a message sent via inline bot @~A <code>~A</code>" | 28 | (blacklist (make-inline-keyboard-button :text "Blacklist" |
| 31 | (user-username via) | 29 | :callback-data (format nil "bbl:~A" (user-id via))))) |
| 32 | (user-id via)) | 30 | (send-message bot |
| 33 | :parse-mode "HTML" | 31 | :chat-id (config-dev-group (bot-config bot)) |
| 34 | :reply-markup (make-inline-keyboard-markup | 32 | :text (format nil "Deleted a message sent via inline bot @~A <code>~A</code>" |
| 35 | :inline-keyboard | 33 | (user-username via) |
| 36 | #(#((make-inline-keyboard-button | 34 | (user-id via)) |
| 37 | :text "Whitelist" | 35 | :parse-mode "HTML" |
| 38 | :callback-data (format nil "bwl:~A" (user-id via))) | 36 | :reply-markup (make-inline-keyboard-markup |
| 39 | (make-inline-keyboard-button | 37 | :inline-keyboard |
| 40 | :text "Blacklist" | 38 | (make-array '(1 2) :initial-contents (list (list whitelist blacklist))))))) |
| 41 | :callback-data (format nil "bbl:~A" (user-id via)))))))) | ||
| 42 | nil)))) | 39 | nil)))) |
diff --git a/src/main.lisp b/src/main.lisp index 419bb67..6d83ff4 100644 --- a/src/main.lisp +++ b/src/main.lisp | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | (:nicknames :ukkoclot) | 4 | (:nicknames :ukkoclot) |
| 5 | (:use :c2cl :ukkoclot/bot :ukkoclot/config :ukkoclot/inline-bots :ukkoclot/log :ukkoclot/tg-types) | 5 | (:use :c2cl :ukkoclot/bot :ukkoclot/config :ukkoclot/inline-bots :ukkoclot/log :ukkoclot/tg-types) |
| 6 | (:import-from :anaphora :acond :awhen :it) | 6 | (:import-from :anaphora :acond :awhen :it) |
| 7 | (:import-from :ukkoclot/bot :make-bot :bot-power-on) | 7 | (:import-from :ukkoclot/bot :make-bot :bot-power-on :fixup-value) |
| 8 | (:import-from :ukkoclot/db :with-db) | 8 | (:import-from :ukkoclot/db :with-db) |
| 9 | (:import-from :ukkoclot/strings :escape-xml :is-tg-whitespace-str :starts-with :starts-with-ignore-case) | 9 | (:import-from :ukkoclot/strings :escape-xml :is-tg-whitespace-str :starts-with :starts-with-ignore-case) |
| 10 | (:local-nicknames | 10 | (:local-nicknames |
| @@ -32,7 +32,8 @@ | |||
| 32 | (log-info "We're done!"))) | 32 | (log-info "We're done!"))) |
| 33 | 33 | ||
| 34 | (defun wrapped-main (bot config) | 34 | (defun wrapped-main (bot config) |
| 35 | (send-message bot :chat-id (config-dev-group config) :text "Initializing...") | 35 | (when *in-prod* |
| 36 | (send-message bot :chat-id (config-dev-group config) :text "Initializing...")) | ||
| 36 | (set-my-name bot :name (config-bot-name config)) | 37 | (set-my-name bot :name (config-bot-name config)) |
| 37 | (let ((gup-offset 0)) | 38 | (let ((gup-offset 0)) |
| 38 | (loop while (bot-power-on bot) do | 39 | (loop while (bot-power-on bot) do |
| @@ -101,14 +102,17 @@ | |||
| 101 | (on-new-member bot msg new-chat-member))))) | 102 | (on-new-member bot msg new-chat-member))))) |
| 102 | 103 | ||
| 103 | (defun on-new-member (bot msg new-member) | 104 | (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 | 105 | ;; TODO: Rule 10 have fun and enjoy your time on user entry |
| 106 | (if (= (user-id new-member) (bot-id bot)) | 106 | (if (= (user-id new-member) (bot-id bot)) |
| 107 | nil | 107 | (reply-animation bot msg #P"blob/rule-11.mp4" |
| 108 | (reply-message bot msg | 108 | :allow-sending-without-reply t) |
| 109 | (concatenate 'string "Hello there, " (user-format-name new-member) "! Be on your bestest behaviour now!!") | 109 | (reply-animation bot msg #P"blob/rule-10.mp4" |
| 110 | :parse-mode "HTML" | 110 | :text (concatenate 'string "Hello there, " |
| 111 | :allow-sending-without-reply t))) | 111 | (user-format-name new-member) |
| 112 | "! Be on your bestest behaviour now!!") | ||
| 113 | :parse-mode "HTML" | ||
| 114 | :caption-above t | ||
| 115 | :allow-sending-without-reply t))) | ||
| 112 | 116 | ||
| 113 | (defun is-bad-text (text) | 117 | (defun is-bad-text (text) |
| 114 | ;; TODO: | 118 | ;; TODO: |
| @@ -130,7 +134,7 @@ | |||
| 130 | 134 | ||
| 131 | (awhen (message-entities msg) | 135 | (awhen (message-entities msg) |
| 132 | (loop for entity across it | 136 | (loop for entity across it |
| 133 | when (and (eq (message-entity-type entity) :bot-command) | 137 | when (and (equal (message-entity-type entity) "bot_command") |
| 134 | (= (message-entity-offset entity) 0)) | 138 | (= (message-entity-offset entity) 0)) |
| 135 | do (on-text-command bot msg text (message-entity-extract entity text)))) | 139 | do (on-text-command bot msg text (message-entity-extract entity text)))) |
| 136 | 140 | ||
| @@ -218,9 +222,7 @@ | |||
| 218 | 222 | ||
| 219 | ((and (equal simple-cmd "msginfo") | 223 | ((and (equal simple-cmd "msginfo") |
| 220 | (message-reply-to-message msg)) | 224 | (message-reply-to-message msg)) |
| 221 | (reply-message bot it | 225 | (reply-message bot it (fixup-value it))) |
| 222 | ;; TODO: Text needs lot more massaging lol | ||
| 223 | (jzon:stringify (arg-encode it)))) | ||
| 224 | 226 | ||
| 225 | ((equal simple-cmd "ping") | 227 | ((equal simple-cmd "ping") |
| 226 | (let* ((start-time (get-internal-real-time)) | 228 | (let* ((start-time (get-internal-real-time)) |
diff --git a/src/tg-types.lisp b/src/tg-types.lisp index 1243773..6a830b6 100644 --- a/src/tg-types.lisp +++ b/src/tg-types.lisp | |||
| @@ -6,13 +6,23 @@ | |||
| 6 | :ukkoclot/tg-types/bot-name | 6 | :ukkoclot/tg-types/bot-name |
| 7 | :ukkoclot/tg-types/callback-query | 7 | :ukkoclot/tg-types/callback-query |
| 8 | :ukkoclot/tg-types/chat | 8 | :ukkoclot/tg-types/chat |
| 9 | :ukkoclot/tg-types/chat-administrator-rights | ||
| 9 | :ukkoclot/tg-types/force-reply | 10 | :ukkoclot/tg-types/force-reply |
| 10 | :ukkoclot/tg-types/inline-keyboard-button | 11 | :ukkoclot/tg-types/inline-keyboard-button |
| 11 | :ukkoclot/tg-types/inline-keyboard-markup | 12 | :ukkoclot/tg-types/inline-keyboard-markup |
| 13 | :ukkoclot/tg-types/keyboard-button | ||
| 14 | :ukkoclot/tg-types/keyboard-button-poll-type | ||
| 15 | :ukkoclot/tg-types/keyboard-button-request-chat | ||
| 16 | :ukkoclot/tg-types/keyboard-button-request-users | ||
| 12 | :ukkoclot/tg-types/link-preview-options | 17 | :ukkoclot/tg-types/link-preview-options |
| 13 | :ukkoclot/tg-types/message | 18 | :ukkoclot/tg-types/message |
| 14 | :ukkoclot/tg-types/message-entity | 19 | :ukkoclot/tg-types/message-entity |
| 20 | :ukkoclot/tg-types/reply-keyboard-markup | ||
| 21 | :ukkoclot/tg-types/reply-keyboard-remove | ||
| 15 | :ukkoclot/tg-types/reply-parameters | 22 | :ukkoclot/tg-types/reply-parameters |
| 23 | :ukkoclot/tg-types/suggested-post-parameters | ||
| 24 | :ukkoclot/tg-types/suggested-post-price | ||
| 16 | :ukkoclot/tg-types/update | 25 | :ukkoclot/tg-types/update |
| 17 | :ukkoclot/tg-types/user | 26 | :ukkoclot/tg-types/user |
| 27 | :ukkoclot/tg-types/web-app-info | ||
| 18 | )) | 28 | )) |
diff --git a/src/tg-types/bot-name.lisp b/src/tg-types/bot-name.lisp index 385b91c..b42765c 100644 --- a/src/tg-types/bot-name.lisp +++ b/src/tg-types/bot-name.lisp | |||
| @@ -3,11 +3,11 @@ | |||
| 3 | (defpackage :ukkoclot/tg-types/bot-name | 3 | (defpackage :ukkoclot/tg-types/bot-name |
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | 4 | (:use :c2cl :ukkoclot/tg-types/macros) |
| 5 | (:export | 5 | (:export |
| 6 | bot-name bot-name-p | 6 | #:bot-name |
| 7 | 7 | #:make-bot-name | |
| 8 | hash->bot-name make-bot-name parse-bot-name-array | 8 | #:bot-name-p |
| 9 | 9 | #:copy-bot-name | |
| 10 | bot-name-name)) | 10 | #:bot-name-name)) |
| 11 | (in-package :ukkoclot/tg-types/bot-name) | 11 | (in-package :ukkoclot/tg-types/bot-name) |
| 12 | 12 | ||
| 13 | (define-tg-type bot-name | 13 | (define-tg-type bot-name |
diff --git a/src/tg-types/callback-query.lisp b/src/tg-types/callback-query.lisp index bb1b4e7..875ff25 100644 --- a/src/tg-types/callback-query.lisp +++ b/src/tg-types/callback-query.lisp | |||
| @@ -6,12 +6,17 @@ | |||
| 6 | :ukkoclot/tg-types/message | 6 | :ukkoclot/tg-types/message |
| 7 | :ukkoclot/tg-types/user) | 7 | :ukkoclot/tg-types/user) |
| 8 | (:export | 8 | (:export |
| 9 | callback-query callback-query-p | 9 | #:callback-query |
| 10 | 10 | #:make-callback-query | |
| 11 | hash->callback-query make-callback-query parse-callback-query-array | 11 | #:callback-query-p |
| 12 | 12 | #:copy-callback-query | |
| 13 | callback-query-id callback-query-from callback-query-message callback-query-inline-message-id | 13 | #:callback-query-id |
| 14 | callback-query-chat-instance callback-query-data callback-query-game-short-name)) | 14 | #:callback-query-from |
| 15 | #:callback-query-message | ||
| 16 | #:callback-query-inline-message-id | ||
| 17 | #:callback-query-chat-instance | ||
| 18 | #:callback-query-data | ||
| 19 | #:callback-query-game-short-name)) | ||
| 15 | (in-package :ukkoclot/tg-types/callback-query) | 20 | (in-package :ukkoclot/tg-types/callback-query) |
| 16 | 21 | ||
| 17 | (define-tg-type callback-query | 22 | (define-tg-type callback-query |
diff --git a/src/tg-types/chat-administrator-rights.lisp b/src/tg-types/chat-administrator-rights.lisp new file mode 100644 index 0000000..37c1ce0 --- /dev/null +++ b/src/tg-types/chat-administrator-rights.lisp | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/chat-administrator-rights | ||
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | ||
| 5 | (:export | ||
| 6 | #:chat-administrator-rights | ||
| 7 | #:make-chat-administrator-rights | ||
| 8 | #:chat-administrator-rights-p | ||
| 9 | #:copy-chat-administrator-rights | ||
| 10 | #:chat-administrator-rights-is-anonymous | ||
| 11 | #:chat-administrator-rights-can-manage-chat | ||
| 12 | #:chat-administrator-rights-can-delete-messages | ||
| 13 | #:chat-administrator-rights-can-manage-video-chats | ||
| 14 | #:chat-administrator-rights-can-restrict-members | ||
| 15 | #:chat-administrator-rights-can-promote-members | ||
| 16 | #:chat-administrator-rights-can-change-info | ||
| 17 | #:chat-administrator-rights-can-invite-users | ||
| 18 | #:chat-administrator-rights-can-post-stories | ||
| 19 | #:chat-administrator-rights-can-edit-stories | ||
| 20 | #:chat-administrator-rights-can-delete-stories | ||
| 21 | #:chat-administrator-rights-can-post-messages | ||
| 22 | #:chat-administrator-rights-can-edit-messages | ||
| 23 | #:chat-administrator-rights-can-pin-messages | ||
| 24 | #:chat-administrator-rights-can-manage-topics | ||
| 25 | #:chat-administrator-rights-can-manage-direct-messages)) | ||
| 26 | (in-package :ukkoclot/tg-types/chat-administrator-rights) | ||
| 27 | |||
| 28 | (define-tg-type chat-administrator-rights | ||
| 29 | (is-anonymous boolean) | ||
| 30 | (can-manage-chat boolean) | ||
| 31 | (can-delete-messages boolean) | ||
| 32 | (can-manage-video-chats boolean) | ||
| 33 | (can-restrict-members boolean) | ||
| 34 | (can-promote-members boolean) | ||
| 35 | (can-change-info boolean) | ||
| 36 | (can-invite-users boolean) | ||
| 37 | (can-post-stories boolean) | ||
| 38 | (can-edit-stories boolean) | ||
| 39 | (can-delete-stories boolean) | ||
| 40 | (can-post-messages boolean nil) | ||
| 41 | (can-edit-messages boolean nil) | ||
| 42 | (can-pin-messages boolean nil) | ||
| 43 | (can-manage-topics boolean nil) | ||
| 44 | (can-manage-direct-messages boolean nil)) | ||
diff --git a/src/tg-types/chat.lisp b/src/tg-types/chat.lisp index 4010f7b..3ad42ef 100644 --- a/src/tg-types/chat.lisp +++ b/src/tg-types/chat.lisp | |||
| @@ -1,28 +1,25 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | 1 | ;; SPDX-License-Identifier: EUPL-1.2 |
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> |
| 3 | (defpackage :ukkoclot/tg-types/chat | 3 | (defpackage :ukkoclot/tg-types/chat |
| 4 | (:use :c2cl :ukkoclot/tg-types/macros :ukkoclot/tg-types/parsers) | 4 | (:use :c2cl :ukkoclot/tg-types/macros) |
| 5 | (:export | 5 | (:export |
| 6 | chat | 6 | #:chat |
| 7 | make-chat | 7 | #:make-chat |
| 8 | chat-p | 8 | #:chat-p |
| 9 | copy-chat | 9 | #:copy-chat |
| 10 | chat-id | 10 | #:chat-id |
| 11 | chat-type | 11 | #:chat-type |
| 12 | chat-title | 12 | #:chat-title |
| 13 | chat-username | 13 | #:chat-username |
| 14 | chat-first-name | 14 | #:chat-first-name |
| 15 | chat-last-name | 15 | #:chat-last-name |
| 16 | chat-is-forum | 16 | #:chat-is-forum |
| 17 | chat-is-direct-messages | 17 | #:chat-is-direct-messages)) |
| 18 | |||
| 19 | hash->chat | ||
| 20 | parse-chat-array)) | ||
| 21 | (in-package :ukkoclot/tg-types/chat) | 18 | (in-package :ukkoclot/tg-types/chat) |
| 22 | 19 | ||
| 23 | (define-tg-type chat | 20 | (define-tg-type chat |
| 24 | (id integer) | 21 | (id integer) |
| 25 | (type keyword nil :parser tg-string->keyword) | 22 | (type string nil) ;TODO: member of keywords |
| 26 | (title (or string null) nil) | 23 | (title (or string null) nil) |
| 27 | (username (or string null) nil) | 24 | (username (or string null) nil) |
| 28 | (first-name (or string null) nil) | 25 | (first-name (or string null) nil) |
diff --git a/src/tg-types/force-reply.lisp b/src/tg-types/force-reply.lisp index ad9d2a0..5dc49fb 100644 --- a/src/tg-types/force-reply.lisp +++ b/src/tg-types/force-reply.lisp | |||
| @@ -3,16 +3,13 @@ | |||
| 3 | (defpackage :ukkoclot/tg-types/force-reply | 3 | (defpackage :ukkoclot/tg-types/force-reply |
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | 4 | (:use :c2cl :ukkoclot/tg-types/macros) |
| 5 | (:export | 5 | (:export |
| 6 | force-reply | 6 | #:force-reply |
| 7 | make-force-reply | 7 | #:make-force-reply |
| 8 | force-reply-p | 8 | #:force-reply-p |
| 9 | copy-force-reply | 9 | #:copy-force-reply |
| 10 | force-reply-force-reply | 10 | #:force-reply-force-reply |
| 11 | force-reply-input-field-placeholder | 11 | #:force-reply-input-field-placeholder |
| 12 | force-reply-selective | 12 | #:force-reply-selective)) |
| 13 | |||
| 14 | hash->force-reply | ||
| 15 | parse-force-reply-array)) | ||
| 16 | (in-package :ukkoclot/tg-types/force-reply) | 13 | (in-package :ukkoclot/tg-types/force-reply) |
| 17 | 14 | ||
| 18 | (define-tg-type force-reply | 15 | (define-tg-type force-reply |
diff --git a/src/tg-types/inline-keyboard-button.lisp b/src/tg-types/inline-keyboard-button.lisp index 3b76ade..e3b3533 100644 --- a/src/tg-types/inline-keyboard-button.lisp +++ b/src/tg-types/inline-keyboard-button.lisp | |||
| @@ -1,28 +1,26 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | 1 | ;; SPDX-License-Identifier: EUPL-1.2 |
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> |
| 3 | (defpackage :ukkoclot/tg-types/inline-keyboard-button | 3 | (defpackage :ukkoclot/tg-types/inline-keyboard-button |
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | 4 | (:use :c2cl :ukkoclot/tg-types/macros :ukkoclot/tg-types/web-app-info) |
| 5 | (:export | 5 | (:export |
| 6 | inline-keyboard-button | 6 | #:inline-keyboard-button |
| 7 | make-inline-keyboard-button | 7 | #:make-inline-keyboard-button |
| 8 | inline-keyboard-button-p | 8 | #:inline-keyboard-button-p |
| 9 | copy-inline-keyboard-button | 9 | #:copy-inline-keyboard-button |
| 10 | inline-keyboard-button-text | 10 | #:inline-keyboard-button-text |
| 11 | inline-keyboard-button-url | 11 | #:inline-keyboard-button-url |
| 12 | inline-keyboard-button-callback-data | 12 | #:inline-keyboard-button-callback-data |
| 13 | inline-keyboard-button-switch-inline-query | 13 | #:inline-keyboard-button-web-app |
| 14 | inline-keyboard-button-switch-inline-query-current-chat | 14 | #:inline-keyboard-button-switch-inline-query |
| 15 | inline-keyboard-button-pay | 15 | #:inline-keyboard-button-switch-inline-query-current-chat |
| 16 | 16 | #:inline-keyboard-button-pay)) | |
| 17 | hash->inline-keyboard-button | ||
| 18 | parse-inline-keyboard-button-array)) | ||
| 19 | (in-package :ukkoclot/tg-types/inline-keyboard-button) | 17 | (in-package :ukkoclot/tg-types/inline-keyboard-button) |
| 20 | 18 | ||
| 21 | (define-tg-type inline-keyboard-button | 19 | (define-tg-type inline-keyboard-button |
| 22 | (text string) | 20 | (text string) |
| 23 | (url (or string null) nil) | 21 | (url (or string null) nil) |
| 24 | (callback-data string) | 22 | (callback-data string) |
| 25 | ;; TODO: (web-app (or web-app-info null) nil) | 23 | (web-app (or web-app-info null) nil) |
| 26 | ;; TODO: (login-url (or login-url null) nil) | 24 | ;; TODO: (login-url (or login-url null) nil) |
| 27 | (switch-inline-query (or string null) nil) | 25 | (switch-inline-query (or string null) nil) |
| 28 | (switch-inline-query-current-chat (or string null) nil) | 26 | (switch-inline-query-current-chat (or string null) nil) |
diff --git a/src/tg-types/inline-keyboard-markup.lisp b/src/tg-types/inline-keyboard-markup.lisp index 1f17f6c..bb7b9c1 100644 --- a/src/tg-types/inline-keyboard-markup.lisp +++ b/src/tg-types/inline-keyboard-markup.lisp | |||
| @@ -3,14 +3,11 @@ | |||
| 3 | (defpackage :ukkoclot/tg-types/inline-keyboard-markup | 3 | (defpackage :ukkoclot/tg-types/inline-keyboard-markup |
| 4 | (:use :c2cl :ukkoclot/tg-types/inline-keyboard-button :ukkoclot/tg-types/macros) | 4 | (:use :c2cl :ukkoclot/tg-types/inline-keyboard-button :ukkoclot/tg-types/macros) |
| 5 | (:export | 5 | (:export |
| 6 | inline-keyboard-markup | 6 | #:inline-keyboard-markup |
| 7 | make-inline-keyboard-markup | 7 | #:make-inline-keyboard-markup |
| 8 | inline-keyboard-markup-p | 8 | #:inline-keyboard-markup-p |
| 9 | copy-inline-keyboard-markup | 9 | #:copy-inline-keyboard-markup |
| 10 | inline-keyboard-markup-inline-keyboard | 10 | #:inline-keyboard-markup-inline-keyboard)) |
| 11 | |||
| 12 | hash->inline-keyboard-markup | ||
| 13 | parse-inline-keyboard-markup-array)) | ||
| 14 | (in-package :ukkoclot/tg-types/inline-keyboard-markup) | 11 | (in-package :ukkoclot/tg-types/inline-keyboard-markup) |
| 15 | 12 | ||
| 16 | (define-tg-type inline-keyboard-markup | 13 | (define-tg-type inline-keyboard-markup |
diff --git a/src/tg-types/keyboard-button-poll-type.lisp b/src/tg-types/keyboard-button-poll-type.lisp new file mode 100644 index 0000000..fdd7b92 --- /dev/null +++ b/src/tg-types/keyboard-button-poll-type.lisp | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/keyboard-button-poll-type | ||
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | ||
| 5 | (:export | ||
| 6 | #:keyboard-button-poll-type | ||
| 7 | #:make-keyboard-button-poll-type | ||
| 8 | #:keyboard-button-poll-type-p | ||
| 9 | #:copy-keyboard-button-poll-type | ||
| 10 | #:keyboard-button-poll-type-type)) | ||
| 11 | (in-package :ukkoclot/tg-types/keyboard-button-poll-type) | ||
| 12 | |||
| 13 | (define-tg-type keyboard-button-poll-type | ||
| 14 | (type (or string null) nil)) ;(member "quiz" "regular") or null | ||
diff --git a/src/tg-types/keyboard-button-request-chat.lisp b/src/tg-types/keyboard-button-request-chat.lisp new file mode 100644 index 0000000..aab8512 --- /dev/null +++ b/src/tg-types/keyboard-button-request-chat.lisp | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/keyboard-button-request-chat | ||
| 4 | (:use :c2cl :ukkoclot/tg-types/chat-administrator-rights :ukkoclot/tg-types/macros) | ||
| 5 | (:export | ||
| 6 | #:keyboard-button-request-chat | ||
| 7 | #:make-keyboard-button-request-chat | ||
| 8 | #:keyboard-button-request-chat-p | ||
| 9 | #:copy-keyboard-button-request-chat | ||
| 10 | #:keyboard-button-request-chat-request-id | ||
| 11 | #:keyboard-button-request-chat-chat-is-channel | ||
| 12 | #:keyboard-button-request-chat-chat-is-created | ||
| 13 | #:keyboard-button-request-chat-user-administrator-rights | ||
| 14 | #:keyboard-button-request-chat-bot-administrator-rights | ||
| 15 | #:keyboard-button-request-chat-bot-is-member | ||
| 16 | #:keyboard-button-request-chat-request-title | ||
| 17 | #:keyboard-button-request-chat-request-username | ||
| 18 | #:keyboard-button-request-chat-request-photo)) | ||
| 19 | (in-package :ukkoclot/tg-types/keyboard-button-request-chat) | ||
| 20 | |||
| 21 | (define-tg-type keyboard-button-request-chat | ||
| 22 | (request-id integer) | ||
| 23 | (chat-is-channel boolean) | ||
| 24 | ;; TODO: (chat-is-forum ternary nil) | ||
| 25 | ;; TODO: (chat-has-username ternary nil) | ||
| 26 | (chat-is-created boolean nil) | ||
| 27 | (user-administrator-rights (or chat-administrator-rights null) nil) | ||
| 28 | (bot-administrator-rights (or chat-administrator-rights null) nil) | ||
| 29 | (bot-is-member boolean nil) | ||
| 30 | (request-title boolean nil) | ||
| 31 | (request-username boolean nil) | ||
| 32 | (request-photo boolean nil)) | ||
diff --git a/src/tg-types/keyboard-button-request-users.lisp b/src/tg-types/keyboard-button-request-users.lisp new file mode 100644 index 0000000..9b20c3d --- /dev/null +++ b/src/tg-types/keyboard-button-request-users.lisp | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/keyboard-button-request-users | ||
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | ||
| 5 | (:export | ||
| 6 | #:keyboard-button-request-users | ||
| 7 | #:make-keyboard-button-request-users | ||
| 8 | #:keyboard-button-request-users-p | ||
| 9 | #:copy-keyboard-button-request-users | ||
| 10 | #:keyboard-button-request-users-request-id | ||
| 11 | #:keyboard-button-request-users-max-quantity | ||
| 12 | #:keyboard-button-request-users-request-name | ||
| 13 | #:keyboard-button-request-users-request-username | ||
| 14 | #:keyboard-button-request-users-request-photo)) | ||
| 15 | (in-package :ukkoclot/tg-types/keyboard-button-request-users) | ||
| 16 | |||
| 17 | (define-tg-type keyboard-button-request-users | ||
| 18 | (request-id integer) | ||
| 19 | ;; TODO: (user-is-bot ternary nil) | ||
| 20 | ;; TODO: (user-is-premium ternary nil) | ||
| 21 | (max-quantity integer 1) | ||
| 22 | (request-name boolean nil) | ||
| 23 | (request-username boolean nil) | ||
| 24 | (request-photo boolean nil)) | ||
diff --git a/src/tg-types/keyboard-button.lisp b/src/tg-types/keyboard-button.lisp new file mode 100644 index 0000000..fac64e6 --- /dev/null +++ b/src/tg-types/keyboard-button.lisp | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/keyboard-button | ||
| 4 | (:use | ||
| 5 | :c2cl | ||
| 6 | :ukkoclot/tg-types/keyboard-button-poll-type | ||
| 7 | :ukkoclot/tg-types/keyboard-button-request-chat | ||
| 8 | :ukkoclot/tg-types/keyboard-button-request-users | ||
| 9 | :ukkoclot/tg-types/macros | ||
| 10 | :ukkoclot/tg-types/web-app-info) | ||
| 11 | (:export | ||
| 12 | #:keyboard-button | ||
| 13 | #:make-keyboard-button | ||
| 14 | #:keyboard-button-p | ||
| 15 | #:copy-keyboard-button | ||
| 16 | #:keyboard-button-text | ||
| 17 | #:keyboard-button-request-users | ||
| 18 | #:keyboard-button-request-chat | ||
| 19 | #:keyboard-button-request-contact | ||
| 20 | #:keyboard-button-request-location | ||
| 21 | #:keyboard-button-request-poll | ||
| 22 | #:keyboard-button-web-app)) | ||
| 23 | (in-package :ukkoclot/tg-types/keyboard-button) | ||
| 24 | |||
| 25 | (define-tg-type keyboard-button | ||
| 26 | (text string) | ||
| 27 | (request-users (or keyboard-button-request-users null) nil) | ||
| 28 | (request-chat (or keyboard-button-request-chat null) nil) | ||
| 29 | (request-contact boolean nil) | ||
| 30 | (request-location boolean nil) | ||
| 31 | (request-poll (or keyboard-button-poll-type null) nil) | ||
| 32 | (web-app (or web-app-info null) nil)) | ||
diff --git a/src/tg-types/link-preview-options.lisp b/src/tg-types/link-preview-options.lisp index 66b7d83..450a9ea 100644 --- a/src/tg-types/link-preview-options.lisp +++ b/src/tg-types/link-preview-options.lisp | |||
| @@ -3,18 +3,15 @@ | |||
| 3 | (defpackage :ukkoclot/tg-types/link-preview-options | 3 | (defpackage :ukkoclot/tg-types/link-preview-options |
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | 4 | (:use :c2cl :ukkoclot/tg-types/macros) |
| 5 | (:export | 5 | (:export |
| 6 | link-preview-options | 6 | #:link-preview-options |
| 7 | make-link-preview-options | 7 | #:make-link-preview-options |
| 8 | link-preview-options-p | 8 | #:link-preview-options-p |
| 9 | copy-link-preview-options | 9 | #:copy-link-preview-options |
| 10 | link-preview-options-is-disabled | 10 | #:link-preview-options-is-disabled |
| 11 | link-preview-options-url | 11 | #:link-preview-options-url |
| 12 | link-preview-options-prefer-small-media | 12 | #:link-preview-options-prefer-small-media |
| 13 | link-preview-options-prefer-large-media | 13 | #:link-preview-options-prefer-large-media |
| 14 | link-preview-options-show-above-text | 14 | #:link-preview-options-show-above-text)) |
| 15 | |||
| 16 | hash->link-preview-options | ||
| 17 | parse-link-preview-options-array)) | ||
| 18 | (in-package :ukkoclot/tg-types/link-preview-options) | 15 | (in-package :ukkoclot/tg-types/link-preview-options) |
| 19 | 16 | ||
| 20 | (define-tg-type link-preview-options | 17 | (define-tg-type link-preview-options |
diff --git a/src/tg-types/macros.lisp b/src/tg-types/macros.lisp index 668df17..a8a3d96 100644 --- a/src/tg-types/macros.lisp +++ b/src/tg-types/macros.lisp | |||
| @@ -2,26 +2,27 @@ | |||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> |
| 3 | (defpackage :ukkoclot/tg-types/macros | 3 | (defpackage :ukkoclot/tg-types/macros |
| 4 | (:use :c2cl) | 4 | (:use :c2cl) |
| 5 | (:import-from :ukkoclot/bot/impl :arg-encode :bot :do-call :will-arg-encode) | 5 | (:import-from :ukkoclot/bot/impl :bot :do-call :parse-value) |
| 6 | (:import-from :ukkoclot/hash-tables :gethash-lazy) | 6 | (:import-from :ukkoclot/hash-tables :gethash-lazy) |
| 7 | (:import-from :ukkoclot/strings :lisp->snake-case) | 7 | (:import-from :ukkoclot/strings :lisp->snake-case) |
| 8 | (:local-nicknames | ||
| 9 | (:jzon :com.inuoe.jzon)) | ||
| 8 | (:export :define-tg-method :define-tg-type)) | 10 | (:export :define-tg-method :define-tg-type)) |
| 9 | (in-package :ukkoclot/tg-types/macros) | 11 | (in-package :ukkoclot/tg-types/macros) |
| 10 | 12 | ||
| 11 | (eval-when (:compile-toplevel :load-toplevel :execute) | 13 | (eval-when (:compile-toplevel :load-toplevel :execute) |
| 12 | (defstruct (field (:constructor make-field%)) name type default skip-if-default (parser 'identity)) | 14 | (defstruct (field (:constructor make-field%)) name type default skip-if-default) |
| 13 | 15 | ||
| 14 | (defparameter +unique+ (gensym)) | 16 | (defparameter +unique+ (gensym)) |
| 15 | 17 | ||
| 16 | (defun make-field (name type &optional (default +unique+) &key (parser 'identity) (skip-if-default (not (eq default +unique+)))) | 18 | (defun make-field (name type &optional (default +unique+) &key (skip-if-default (not (eq default +unique+)))) |
| 17 | (let ((default (if (eq default +unique+) | 19 | (let ((default (if (eq default +unique+) |
| 18 | (list 'error (format nil "No value given for ~A" name)) | 20 | (list 'error (format nil "No value given for ~A" name)) |
| 19 | default))) | 21 | default))) |
| 20 | (make-field% :name name | 22 | (make-field% :name name |
| 21 | :type type | 23 | :type type |
| 22 | :default default | 24 | :default default |
| 23 | :skip-if-default skip-if-default | 25 | :skip-if-default skip-if-default))) |
| 24 | :parser parser))) | ||
| 25 | 26 | ||
| 26 | (defun parse-field-specs (field-specs) | 27 | (defun parse-field-specs (field-specs) |
| 27 | (loop for field-spec in field-specs | 28 | (loop for field-spec in field-specs |
| @@ -33,6 +34,11 @@ | |||
| 33 | (defun field-accessor (struc-name field) | 34 | (defun field-accessor (struc-name field) |
| 34 | (intern (concatenate 'string (symbol-name struc-name) "-" (symbol-name (field-name field))))) | 35 | (intern (concatenate 'string (symbol-name struc-name) "-" (symbol-name (field-name field))))) |
| 35 | 36 | ||
| 37 | (defun field->coerced-field-spec (field struc-name obj-name) | ||
| 38 | `(list ,(string-downcase (lisp->snake-case (symbol-name (field-name field)))) | ||
| 39 | (,(field-accessor struc-name field) ,obj-name) | ||
| 40 | ',(field-type field))) | ||
| 41 | |||
| 36 | (defun field->defun-spec (field) | 42 | (defun field->defun-spec (field) |
| 37 | (list (field-name field) (field-default field))) | 43 | (list (field-name field) (field-default field))) |
| 38 | 44 | ||
| @@ -58,10 +64,8 @@ | |||
| 58 | `(setf (gethash ,hash-key ,hash-table-sym) (,(field-accessor name field) ,struc))))) | 64 | `(setf (gethash ,hash-key ,hash-table-sym) (,(field-accessor name field) ,struc))))) |
| 59 | 65 | ||
| 60 | (defun field->let-gethash-spec (field hash-table-sym) | 66 | (defun field->let-gethash-spec (field hash-table-sym) |
| 61 | (list (field-name field) | 67 | `(,(field-name field) |
| 62 | (list 'funcall | 68 | (parse-value ',(field-type field) ,(field->gethash-spec field hash-table-sym)))) |
| 63 | (list 'function (field-parser field)) | ||
| 64 | (field->gethash-spec field hash-table-sym)))) | ||
| 65 | 69 | ||
| 66 | (defun field->make-spec (field) | 70 | (defun field->make-spec (field) |
| 67 | (list (intern (symbol-name (field-name field)) :keyword) | 71 | (list (intern (symbol-name (field-name field)) :keyword) |
| @@ -72,35 +76,40 @@ | |||
| 72 | 76 | ||
| 73 | ;; TODO: Automatically derive path from name | 77 | ;; TODO: Automatically derive path from name |
| 74 | ;; TODO: Automatically derive mapfn from type | 78 | ;; TODO: Automatically derive mapfn from type |
| 75 | ;; TODO: Skip values that are already their defaults | ||
| 76 | (defmacro define-tg-method ( | 79 | (defmacro define-tg-method ( |
| 77 | (name type path mapfn &optional (method :POST)) | 80 | (name type path &optional (method :POST)) |
| 78 | &body field-specs) | 81 | &body field-specs) |
| 79 | (let ((fields (parse-field-specs field-specs)) | 82 | (let* ((fields (parse-field-specs field-specs)) |
| 80 | (args-plist (gensym "ARGS-PLIST-")) | 83 | (revfields (reverse fields)) |
| 81 | (bot (gensym "BOT-"))) | 84 | (args (gensym "ARGS")) |
| 85 | (bot (gensym "BOT-"))) | ||
| 82 | `(progn | 86 | `(progn |
| 83 | (declaim (ftype (function (bot &key ,@(loop for field in fields | 87 | (declaim (ftype (function (bot &key ,@(loop for field in fields |
| 84 | collect (field->ftype-spec field))) | 88 | collect (field->ftype-spec field))) |
| 85 | ,type) | 89 | ,type) |
| 86 | ,name)) | 90 | ,name)) |
| 87 | (defun ,name (,bot &rest ,args-plist &key ,@(loop for field in fields collect (field->defun-spec field))) | 91 | (defun ,name (,bot &key ,@(loop for field in fields collect (field->defun-spec field))) |
| 88 | (declare ,@(loop for field in fields collect (list 'ignore (field-name field)))) | 92 | (let (,args) |
| 89 | (do-call ,bot ,method ,path ,mapfn ,args-plist))))) | 93 | ,@(loop for field in revfields |
| 94 | collecting | ||
| 95 | (if (field-skip-if-default field) | ||
| 96 | `(unless (equal ,(field-name field) ,(field-default field)) | ||
| 97 | (setf ,args (acons ',(field-name field) ,(field-name field) ,args))) | ||
| 98 | `(setf ,args (acons ',(field-name field) ,(field-name field) ,args)))) | ||
| 99 | (do-call ,bot ,method ,path ',type ,args)))))) | ||
| 90 | 100 | ||
| 91 | (defmacro define-tg-type (name &body field-specs) | 101 | (defmacro define-tg-type (name &body field-specs) |
| 92 | (let* ((fields (parse-field-specs field-specs)) | 102 | (let* ((fields (parse-field-specs field-specs)) |
| 93 | (revfields (reverse fields)) | 103 | (revfields (reverse fields)) |
| 94 | (make-name (intern (concatenate 'string "MAKE-" (symbol-name name)))) | 104 | (make-name (intern (concatenate 'string "MAKE-" (symbol-name name)))) |
| 95 | (hash->name (intern (concatenate 'string "HASH->" (symbol-name name)))) | ||
| 96 | (parse-name-array (intern (concatenate 'string "PARSE-" (symbol-name name) "-ARRAY"))) | ||
| 97 | (printer (gensym (concatenate 'string "PRINT-" (symbol-name name)))) | 105 | (printer (gensym (concatenate 'string "PRINT-" (symbol-name name)))) |
| 98 | (hash (gensym "HASH-")) | 106 | (hash (gensym "HASH-")) |
| 99 | (array (gensym "ARRAY-")) | ||
| 100 | (struc (gensym (symbol-name name))) | 107 | (struc (gensym (symbol-name name))) |
| 101 | (stream (gensym "STREAM")) | 108 | (stream (gensym "STREAM")) |
| 102 | (depth (gensym "DEPTH")) | 109 | (depth (gensym "DEPTH")) |
| 103 | (pprint-args (gensym "PPRINT-ARGS"))) | 110 | (pprint-args (gensym "PPRINT-ARGS")) |
| 111 | (res (gensym "RES")) | ||
| 112 | (type (gensym "TYPE"))) | ||
| 104 | `(progn | 113 | `(progn |
| 105 | (defstruct (,name (:print-function ,printer)) | 114 | (defstruct (,name (:print-function ,printer)) |
| 106 | ,@(loop for field in fields | 115 | ,@(loop for field in fields |
| @@ -116,19 +125,18 @@ | |||
| 116 | (setf ,pprint-args (list* ',(field-name field) value ,pprint-args)))) | 125 | (setf ,pprint-args (list* ',(field-name field) value ,pprint-args)))) |
| 117 | `(setf ,pprint-args (list* ',(field-name field) (,(field-accessor name field) ,struc) ,pprint-args)))) | 126 | `(setf ,pprint-args (list* ',(field-name field) (,(field-accessor name field) ,struc) ,pprint-args)))) |
| 118 | (format ,stream "~A~<[~;~@{~_~1I~W = ~W~^, ~}~;]~:>" ',name ,pprint-args))) | 127 | (format ,stream "~A~<[~;~@{~_~1I~W = ~W~^, ~}~;]~:>" ',name ,pprint-args))) |
| 119 | (defun ,hash->name (,hash) | 128 | (defmethod parse-value ((,type (eql ',name)) ,hash) |
| 120 | (when ,hash | 129 | (let ,(loop for field in fields |
| 121 | (let ,(loop for field in fields | 130 | collect (field->let-gethash-spec field hash)) |
| 122 | collect (field->let-gethash-spec field hash)) | 131 | (,make-name ,@(loop for field in fields |
| 123 | (,make-name ,@(loop for field in fields | 132 | append (field->make-spec field))))) |
| 124 | append (field->make-spec field)))))) | 133 | (defmethod jzon:coerced-fields ((,struc ,name)) |
| 125 | (defmethod arg-encode ((,struc ,name)) | 134 | (let (,res) |
| 126 | (let ((,hash (make-hash-table))) | 135 | ,@(loop for field in revfields |
| 127 | ,@(loop for field in fields | 136 | collecting |
| 128 | collect (field->sethash-spec field name struc hash)) | 137 | (if (field-skip-if-default field) |
| 129 | ,hash)) | 138 | `(let ((value (,(field-accessor name field) ,struc))) |
| 130 | (defmethod will-arg-encode ((,struc ,name)) | 139 | (unless (equal value ,(field-default field)) |
| 131 | t) | 140 | (setf ,res (cons ,(field->coerced-field-spec field name struc) ,res)))) |
| 132 | (defun ,parse-name-array (,array) | 141 | `(setf ,res (cons ,(field->coerced-field-spec field name struc) ,res)))) |
| 133 | (when ,array | 142 | ,res))))) |
| 134 | (map 'vector #',hash->name ,array)))))) | ||
diff --git a/src/tg-types/message-entity.lisp b/src/tg-types/message-entity.lisp index fcabcce..a605f23 100644 --- a/src/tg-types/message-entity.lisp +++ b/src/tg-types/message-entity.lisp | |||
| @@ -1,27 +1,25 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | 1 | ;; SPDX-License-Identifier: EUPL-1.2 |
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> |
| 3 | (defpackage :ukkoclot/tg-types/message-entity | 3 | (defpackage :ukkoclot/tg-types/message-entity |
| 4 | (:use :c2cl :iterate :ukkoclot/tg-types/macros :ukkoclot/tg-types/parsers :ukkoclot/tg-types/user) | 4 | (:use :c2cl :iterate :ukkoclot/tg-types/macros :ukkoclot/tg-types/user) |
| 5 | (:export | 5 | (:export |
| 6 | message-entity | 6 | #:message-entity |
| 7 | make-message-entity | 7 | #:make-message-entity |
| 8 | message-entity-p | 8 | #:message-entity-p |
| 9 | copy-message-entity | 9 | #:copy-message-entity |
| 10 | message-entity-type | 10 | #:message-entity-type |
| 11 | message-entity-offset | 11 | #:message-entity-offset |
| 12 | message-entity-length | 12 | #:message-entity-length |
| 13 | message-entity-url | 13 | #:message-entity-url |
| 14 | message-entity-user | 14 | #:message-entity-user |
| 15 | message-entity-language | 15 | #:message-entity-language |
| 16 | message-entity-custom-emoji-id | 16 | #:message-entity-custom-emoji-id |
| 17 | 17 | ||
| 18 | hash->message-entity | 18 | #:message-entity-extract)) |
| 19 | message-entity-extract | ||
| 20 | parse-message-entity-array)) | ||
| 21 | (in-package :ukkoclot/tg-types/message-entity) | 19 | (in-package :ukkoclot/tg-types/message-entity) |
| 22 | 20 | ||
| 23 | (define-tg-type message-entity | 21 | (define-tg-type message-entity |
| 24 | (type keyword nil :parser tg-string->keyword) | 22 | (type string nil) ;TODO: keywords |
| 25 | (offset integer) | 23 | (offset integer) |
| 26 | (length integer) | 24 | (length integer) |
| 27 | (url (or string null) nil) | 25 | (url (or string null) nil) |
diff --git a/src/tg-types/message.lisp b/src/tg-types/message.lisp index fee0734..028379f 100644 --- a/src/tg-types/message.lisp +++ b/src/tg-types/message.lisp | |||
| @@ -7,52 +7,50 @@ | |||
| 7 | :ukkoclot/tg-types/message-entity | 7 | :ukkoclot/tg-types/message-entity |
| 8 | :ukkoclot/tg-types/user) | 8 | :ukkoclot/tg-types/user) |
| 9 | (:export | 9 | (:export |
| 10 | message | 10 | #:message |
| 11 | make-message | 11 | #:make-message |
| 12 | message-p | 12 | #:message-p |
| 13 | copy-message | 13 | #:copy-message |
| 14 | message-message-id | 14 | #:message-message-id |
| 15 | message-message-thread-id | 15 | #:message-message-thread-id |
| 16 | message-from | 16 | #:message-from |
| 17 | message-sender-boost-count | 17 | #:message-sender-boost-count |
| 18 | message-sender-business-bot | 18 | #:message-sender-business-bot |
| 19 | message-date | 19 | #:message-date |
| 20 | message-business-connection-id | 20 | #:message-business-connection-id |
| 21 | message-chat | 21 | #:message-chat |
| 22 | message-is-topic-message | 22 | #:message-is-topic-message |
| 23 | message-is-automatic-forward | 23 | #:message-is-automatic-forward |
| 24 | message-reply-to-message | 24 | #:message-reply-to-message |
| 25 | message-reply-to-checklist-task-id | 25 | #:message-reply-to-checklist-task-id |
| 26 | message-via-bot | 26 | #:message-via-bot |
| 27 | message-edit-date | 27 | #:message-edit-date |
| 28 | message-has-protected-content | 28 | #:message-has-protected-content |
| 29 | message-is-from-offline | 29 | #:message-is-from-offline |
| 30 | message-is-paid-post | 30 | #:message-is-paid-post |
| 31 | message-media-group-id | 31 | #:message-media-group-id |
| 32 | message-author-signature | 32 | #:message-author-signature |
| 33 | message-paid-star-count | 33 | #:message-paid-star-count |
| 34 | message-text | 34 | #:message-text |
| 35 | message-entities | 35 | #:message-entities |
| 36 | message-effect-id | 36 | #:message-effect-id |
| 37 | message-caption | 37 | #:message-caption |
| 38 | message-show-caption-above-media | 38 | #:message-show-caption-above-media |
| 39 | message-has-media-spoiler | 39 | #:message-has-media-spoiler |
| 40 | message-new-chat-members | 40 | #:message-new-chat-members |
| 41 | message-new-chat-title | 41 | #:message-new-chat-title |
| 42 | message-delete-chat-photo | 42 | #:message-delete-chat-photo |
| 43 | message-group-chat-created | 43 | #:message-group-chat-created |
| 44 | message-supergroup-chat-created | 44 | #:message-supergroup-chat-created |
| 45 | message-channel-chat-created | 45 | #:message-channel-chat-created |
| 46 | message-migrate-to-chat-id | 46 | #:message-migrate-to-chat-id |
| 47 | message-migrate-from-chat-id | 47 | #:message-migrate-from-chat-id |
| 48 | message-pinned-message | 48 | #:message-pinned-message |
| 49 | message-connected-website | 49 | #:message-connected-website |
| 50 | 50 | ||
| 51 | hash->message | 51 | #:message-chat-id |
| 52 | message-id | 52 | #:message-thread-id |
| 53 | message-chat-id | 53 | #:message-id)) |
| 54 | message-thread-id | ||
| 55 | parse-message-array)) | ||
| 56 | (in-package :ukkoclot/tg-types/message) | 54 | (in-package :ukkoclot/tg-types/message) |
| 57 | 55 | ||
| 58 | ;; If this is a MaybeInaccessibleMessage date will be 0 if this is inaccessible | 56 | ;; If this is a MaybeInaccessibleMessage date will be 0 if this is inaccessible |
| @@ -60,22 +58,22 @@ | |||
| 60 | (message-id integer) | 58 | (message-id integer) |
| 61 | (message-thread-id (or integer null) nil) | 59 | (message-thread-id (or integer null) nil) |
| 62 | ;; (direct-messages-topic (or direct-messages-topic null) nil) | 60 | ;; (direct-messages-topic (or direct-messages-topic null) nil) |
| 63 | (from (or user null) nil :parser hash->user) | 61 | (from (or user null) nil) |
| 64 | ;; (sender-chat (or chat null) nil) | 62 | ;; (sender-chat (or chat null) nil) |
| 65 | (sender-boost-count (or integer null) nil) | 63 | (sender-boost-count (or integer null) nil) |
| 66 | (sender-business-bot (or user null) nil :parser hash->user) | 64 | (sender-business-bot (or user null) nil) |
| 67 | (date integer) | 65 | (date integer) |
| 68 | (business-connection-id (or string null) nil) | 66 | (business-connection-id (or string null) nil) |
| 69 | (chat chat nil :parser hash->chat) | 67 | (chat chat nil) |
| 70 | ;; (forward-origin (or message-origin null) nil) | 68 | ;; (forward-origin (or message-origin null) nil) |
| 71 | (is-topic-message boolean nil) | 69 | (is-topic-message boolean nil) |
| 72 | (is-automatic-forward boolean nil) | 70 | (is-automatic-forward boolean nil) |
| 73 | (reply-to-message (or message null) nil :parser hash->message) | 71 | (reply-to-message (or message null) nil) |
| 74 | ;; (external-reply (or external-reply-info null) nil) | 72 | ;; (external-reply (or external-reply-info null) nil) |
| 75 | ;; (quote (or text-quote null) nil) | 73 | ;; (quote (or text-quote null) nil) |
| 76 | ;; (reply-to-story (or story null) nil) | 74 | ;; (reply-to-story (or story null) nil) |
| 77 | (reply-to-checklist-task-id (or integer null) nil) | 75 | (reply-to-checklist-task-id (or integer null) nil) |
| 78 | (via-bot (or user null) nil :parser hash->user) | 76 | (via-bot (or user null) nil) |
| 79 | (edit-date (or integer null) nil) | 77 | (edit-date (or integer null) nil) |
| 80 | (has-protected-content boolean nil) | 78 | (has-protected-content boolean nil) |
| 81 | (is-from-offline boolean nil) | 79 | (is-from-offline boolean nil) |
| @@ -84,7 +82,7 @@ | |||
| 84 | (author-signature (or string null) nil) | 82 | (author-signature (or string null) nil) |
| 85 | (paid-star-count (or string null) nil) | 83 | (paid-star-count (or string null) nil) |
| 86 | (text (or string null) nil) | 84 | (text (or string null) nil) |
| 87 | (entities (or (array message-entity) null) nil :parser parse-message-entity-array) | 85 | (entities (or (array message-entity) null) nil) |
| 88 | ;; (link-preview-options (or link-preview-options null) nil) | 86 | ;; (link-preview-options (or link-preview-options null) nil) |
| 89 | ;; (suggested-post-info (or suggested-post-info null) nil) | 87 | ;; (suggested-post-info (or suggested-post-info null) nil) |
| 90 | (effect-id (or string null) nil) | 88 | (effect-id (or string null) nil) |
| @@ -108,7 +106,7 @@ | |||
| 108 | ;; (poll (or poll null) nil) | 106 | ;; (poll (or poll null) nil) |
| 109 | ;; (venue (or venue null) nil) | 107 | ;; (venue (or venue null) nil) |
| 110 | ;; (location (or location null) nil) | 108 | ;; (location (or location null) nil) |
| 111 | (new-chat-members (or (array user) null) nil :parser parse-user-array) | 109 | (new-chat-members (or (array user) null) nil) |
| 112 | ;; (left-chat-member (or user null) nil) | 110 | ;; (left-chat-member (or user null) nil) |
| 113 | (new-chat-title (or string null) nil) | 111 | (new-chat-title (or string null) nil) |
| 114 | ;; (new-chat-photo (or (array photo-size) null) nil) | 112 | ;; (new-chat-photo (or (array photo-size) null) nil) |
| @@ -119,7 +117,7 @@ | |||
| 119 | ;; (message-auto-delete-timer-changed (orp message-auto-delete-timer-changed null) nil) | 117 | ;; (message-auto-delete-timer-changed (orp message-auto-delete-timer-changed null) nil) |
| 120 | (migrate-to-chat-id (or integer null) nil) | 118 | (migrate-to-chat-id (or integer null) nil) |
| 121 | (migrate-from-chat-id (or integer null) nil) | 119 | (migrate-from-chat-id (or integer null) nil) |
| 122 | (pinned-message (or message null) nil :parser hash->message) | 120 | (pinned-message (or message null) nil) |
| 123 | ;; (invoice (or invoice null) nil) | 121 | ;; (invoice (or invoice null) nil) |
| 124 | ;; (successful-payment (or successful-payment null) nil) | 122 | ;; (successful-payment (or successful-payment null) nil) |
| 125 | ;; (refunded-payment (or refunded-payment null) nil) | 123 | ;; (refunded-payment (or refunded-payment null) nil) |
diff --git a/src/tg-types/parsers.lisp b/src/tg-types/parsers.lisp deleted file mode 100644 index 0b6c4ae..0000000 --- a/src/tg-types/parsers.lisp +++ /dev/null | |||
| @@ -1,9 +0,0 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/parsers | ||
| 4 | (:use :c2cl :ukkoclot/strings) | ||
| 5 | (:export tg-string->keyword)) | ||
| 6 | (in-package :ukkoclot/tg-types/parsers) | ||
| 7 | |||
| 8 | (defun tg-string->keyword (str) | ||
| 9 | (intern (string-upcase (snake->lisp-case str)) :keyword)) | ||
diff --git a/src/tg-types/reply-keyboard-markup.lisp b/src/tg-types/reply-keyboard-markup.lisp new file mode 100644 index 0000000..f4b5d45 --- /dev/null +++ b/src/tg-types/reply-keyboard-markup.lisp | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/reply-keyboard-markup | ||
| 4 | (:use :c2cl :ukkoclot/tg-types/keyboard-button :ukkoclot/tg-types/macros) | ||
| 5 | (:export | ||
| 6 | #:reply-keyboard-markup | ||
| 7 | #:make-reply-keyboard-markup | ||
| 8 | #:reply-keyboard-markup-p | ||
| 9 | #:copy-reply-keyboard-markup | ||
| 10 | #:reply-keyboard-markup-keyboard | ||
| 11 | #:reply-keyboard-markup-is-persistent | ||
| 12 | #:reply-keyboard-markup-resize-keyboard | ||
| 13 | #:reply-keyboard-markup-one-time-keyboard | ||
| 14 | #:reply-keyboard-markup-input-field-placeholder | ||
| 15 | #:reply-keyboard-markup-selective)) | ||
| 16 | (in-package :ukkoclot/tg-types/reply-keyboard-markup) | ||
| 17 | |||
| 18 | (define-tg-type reply-keyboard-markup | ||
| 19 | (keyboard (array (array keyboard-button))) | ||
| 20 | (is-persistent boolean nil) | ||
| 21 | (resize-keyboard boolean nil) | ||
| 22 | (one-time-keyboard boolean nil) | ||
| 23 | (input-field-placeholder (or string null) nil) | ||
| 24 | (selective boolean nil)) | ||
diff --git a/src/tg-types/reply-keyboard-remove.lisp b/src/tg-types/reply-keyboard-remove.lisp new file mode 100644 index 0000000..9925d82 --- /dev/null +++ b/src/tg-types/reply-keyboard-remove.lisp | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/reply-keyboard-remove | ||
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | ||
| 5 | (:export | ||
| 6 | #:reply-keyboard-remove | ||
| 7 | #:make-reply-keyboard-remove | ||
| 8 | #:reply-keyboard-remove-p | ||
| 9 | #:copy-reply-keyboard-remove | ||
| 10 | #:reply-keyboard-remove-remove-keyboard | ||
| 11 | #:reply-keyboard-remove-selective)) | ||
| 12 | (in-package :ukkoclot/tg-types/reply-keyboard-remove) | ||
| 13 | |||
| 14 | (define-tg-type reply-keyboard-remove | ||
| 15 | (remove-keyboard boolean t :skip-if-default nil) | ||
| 16 | (selective boolean nil)) | ||
diff --git a/src/tg-types/reply-parameters.lisp b/src/tg-types/reply-parameters.lisp index 29d21f7..ac38db7 100644 --- a/src/tg-types/reply-parameters.lisp +++ b/src/tg-types/reply-parameters.lisp | |||
| @@ -3,21 +3,18 @@ | |||
| 3 | (defpackage :ukkoclot/tg-types/reply-parameters | 3 | (defpackage :ukkoclot/tg-types/reply-parameters |
| 4 | (:use :c2cl :ukkoclot/tg-types/macros :ukkoclot/tg-types/message-entity) | 4 | (:use :c2cl :ukkoclot/tg-types/macros :ukkoclot/tg-types/message-entity) |
| 5 | (:export | 5 | (:export |
| 6 | reply-parameters | 6 | #:reply-parameters |
| 7 | make-reply-parameters | 7 | #:make-reply-parameters |
| 8 | reply-parameters-p | 8 | #:reply-parameters-p |
| 9 | copy-reply-parameters | 9 | #:copy-reply-parameters |
| 10 | reply-parameters-message-id | 10 | #:reply-parameters-message-id |
| 11 | reply-parameters-chat-id | 11 | #:reply-parameters-chat-id |
| 12 | reply-parameters-allow-sending-without-reply | 12 | #:reply-parameters-allow-sending-without-reply |
| 13 | reply-parameters-quote | 13 | #:reply-parameters-quote |
| 14 | reply-parameters-quote-parse-mode | 14 | #:reply-parameters-quote-parse-mode |
| 15 | reply-parameters-quote-entities | 15 | #:reply-parameters-quote-entities |
| 16 | reply-parameters-quote-position | 16 | #:reply-parameters-quote-position |
| 17 | reply-parameters-checklist-task-id | 17 | #:reply-parameters-checklist-task-id)) |
| 18 | |||
| 19 | hash->reply-parameters | ||
| 20 | parse-reply-parameters-array)) | ||
| 21 | (in-package :ukkoclot/tg-types/reply-parameters) | 18 | (in-package :ukkoclot/tg-types/reply-parameters) |
| 22 | 19 | ||
| 23 | (define-tg-type reply-parameters | 20 | (define-tg-type reply-parameters |
diff --git a/src/tg-types/suggested-post-parameters.lisp b/src/tg-types/suggested-post-parameters.lisp new file mode 100644 index 0000000..bf781fc --- /dev/null +++ b/src/tg-types/suggested-post-parameters.lisp | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/suggested-post-parameters | ||
| 4 | (:use :c2cl :ukkoclot/tg-types/macros :ukkoclot/tg-types/suggested-post-price) | ||
| 5 | (:export | ||
| 6 | #:suggested-post-parameters | ||
| 7 | #:make-suggested-post-parameters | ||
| 8 | #:suggested-post-parameters-p | ||
| 9 | #:copy-suggested-post-parameters | ||
| 10 | #:suggested-post-parameters-price | ||
| 11 | #:suggested-post-parameters-send-date)) | ||
| 12 | (in-package :ukkoclot/tg-types/suggested-post-parameters) | ||
| 13 | |||
| 14 | (define-tg-type suggested-post-parameters | ||
| 15 | (price (or suggested-post-price null) nil) | ||
| 16 | (send-date (or integer null) nil)) | ||
diff --git a/src/tg-types/suggested-post-price.lisp b/src/tg-types/suggested-post-price.lisp new file mode 100644 index 0000000..e5b6cb3 --- /dev/null +++ b/src/tg-types/suggested-post-price.lisp | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/suggested-post-price | ||
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | ||
| 5 | (:export | ||
| 6 | #:suggested-post-price | ||
| 7 | #:make-suggested-post-price | ||
| 8 | #:suggested-post-price-p | ||
| 9 | #:copy-suggested-post-price | ||
| 10 | #:suggested-post-price-currency | ||
| 11 | #:suggested-post-price-amount)) | ||
| 12 | (in-package :ukkoclot/tg-types/suggested-post-price) | ||
| 13 | |||
| 14 | (define-tg-type suggested-post-price | ||
| 15 | (currency string) ;(member "XTR" "TON") | ||
| 16 | (amount integer)) | ||
diff --git a/src/tg-types/update.lisp b/src/tg-types/update.lisp index 9043d54..c259553 100644 --- a/src/tg-types/update.lisp +++ b/src/tg-types/update.lisp | |||
| @@ -5,36 +5,35 @@ | |||
| 5 | :ukkoclot/tg-types/callback-query | 5 | :ukkoclot/tg-types/callback-query |
| 6 | :ukkoclot/tg-types/message) | 6 | :ukkoclot/tg-types/message) |
| 7 | (:export | 7 | (:export |
| 8 | update update-p | 8 | #:update |
| 9 | 9 | #:make-update | |
| 10 | hash->update make-update parse-update-array | 10 | #:update-p |
| 11 | 11 | #:copy-update | |
| 12 | update-update-id update-message update-edited-message update-channel-post update-edited-channel-post | 12 | #:update-update-id |
| 13 | ;; update-business-connection | 13 | #:update-message |
| 14 | update-business-message update-edited-business-message | 14 | #:update-edited-message |
| 15 | ;; update-deleted-business-messages update-message-reaction update-message-reaction-count update-inline-query | 15 | #:update-channel-post |
| 16 | ;; update-chosen-inline-result | 16 | #:update-edited-channel-post |
| 17 | update-callback-query | 17 | #:update-business-message |
| 18 | ;; update-shipping-query update-pre-checkout-query update-poll update-poll-answer update-my-chat-member | 18 | #:update-edited-business-message |
| 19 | ;; update-chat-member update-chat-join-request update-chat-boost update-removed-chat-boost | 19 | #:update-callback-query)) |
| 20 | )) | ||
| 21 | (in-package :ukkoclot/tg-types/update) | 20 | (in-package :ukkoclot/tg-types/update) |
| 22 | 21 | ||
| 23 | (define-tg-type update | 22 | (define-tg-type update |
| 24 | (update-id integer) | 23 | (update-id integer) |
| 25 | (message (or message null) nil :parser hash->message) | 24 | (message (or message null) nil) |
| 26 | (edited-message (or message null) nil :parser hash->message) | 25 | (edited-message (or message null) nil) |
| 27 | (channel-post (or message null) nil :parser hash->message) | 26 | (channel-post (or message null) nil) |
| 28 | (edited-channel-post (or message null) nil :parser hash->message) | 27 | (edited-channel-post (or message null) nil) |
| 29 | ;; (business-connection (or business-connection null) nil) | 28 | ;; (business-connection (or business-connection null) nil) |
| 30 | (business-message (or message null) nil :parser hash->message) | 29 | (business-message (or message null) nil) |
| 31 | (edited-business-message (or message null) nil :parser hash->message) | 30 | (edited-business-message (or message null) nil) |
| 32 | ;; (deleted-business-messages (or business-messages-deleted null) nil) | 31 | ;; (deleted-business-messages (or business-messages-deleted null) nil) |
| 33 | ;; (message-reaction (or message-reaction-updated null) nil) | 32 | ;; (message-reaction (or message-reaction-updated null) nil) |
| 34 | ;; (message-reaction-count (or message-reaction-count-updated null) nil) | 33 | ;; (message-reaction-count (or message-reaction-count-updated null) nil) |
| 35 | ;; (inline-query (or inline-query null) nil) | 34 | ;; (inline-query (or inline-query null) nil) |
| 36 | ;; (chosen-inline-result (or chosen-inline-result null) nil) | 35 | ;; (chosen-inline-result (or chosen-inline-result null) nil) |
| 37 | (callback-query (or callback-query null) nil :parser hash->callback-query) | 36 | (callback-query (or callback-query null) nil) |
| 38 | ;; (shipping-query (or shipping-query null) nil) | 37 | ;; (shipping-query (or shipping-query null) nil) |
| 39 | ;; (pre-checkout-query (or pre-checkout-query null) nil) | 38 | ;; (pre-checkout-query (or pre-checkout-query null) nil) |
| 40 | ;; (poll (or poll null) nil) | 39 | ;; (poll (or poll null) nil) |
diff --git a/src/tg-types/user.lisp b/src/tg-types/user.lisp index c5ed499..b5be417 100644 --- a/src/tg-types/user.lisp +++ b/src/tg-types/user.lisp | |||
| @@ -4,13 +4,24 @@ | |||
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | 4 | (:use :c2cl :ukkoclot/tg-types/macros) |
| 5 | (:import-from :ukkoclot/strings :escape-xml) | 5 | (:import-from :ukkoclot/strings :escape-xml) |
| 6 | (:export | 6 | (:export |
| 7 | user user-p | 7 | #:user |
| 8 | #:make-user | ||
| 9 | #:user-p | ||
| 10 | #:copy-user | ||
| 11 | #:user-id | ||
| 12 | #:user-is-bot | ||
| 13 | #:user-first-name | ||
| 14 | #:user-last-name | ||
| 15 | #:user-username | ||
| 16 | #:user-language-code | ||
| 17 | #:user-is-premium | ||
| 18 | #:user-added-to-attachment-menu | ||
| 19 | #:user-can-join-groups | ||
| 20 | #:user-can-read-all-group-messages | ||
| 21 | #:user-supports-inline-queries | ||
| 22 | #:user-can-connect-to-business | ||
| 8 | 23 | ||
| 9 | hash->user make-user parse-user-array user-format-name | 24 | #:user-format-name)) |
| 10 | |||
| 11 | user-id user-is-bot user-first-name user-last-name user-username user-language-code user-is-premium | ||
| 12 | user-added-to-attachment-menu user-can-join-groups user-can-read-all-group-messages user-supports-inline-queries | ||
| 13 | user-can-connect-to-business)) | ||
| 14 | (in-package :ukkoclot/tg-types/user) | 25 | (in-package :ukkoclot/tg-types/user) |
| 15 | 26 | ||
| 16 | (define-tg-type user | 27 | (define-tg-type user |
diff --git a/src/tg-types/web-app-info.lisp b/src/tg-types/web-app-info.lisp new file mode 100644 index 0000000..e239830 --- /dev/null +++ b/src/tg-types/web-app-info.lisp | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | ;; SPDX-License-Identifier: EUPL-1.2 | ||
| 2 | ;; SPDX-FileCopyrightText: 2025 Uko Kokņevičs <perkontevs@gmail.com> | ||
| 3 | (defpackage :ukkoclot/tg-types/web-app-info | ||
| 4 | (:use :c2cl :ukkoclot/tg-types/macros) | ||
| 5 | (:export | ||
| 6 | #:web-app-info | ||
| 7 | #:make-web-app-info | ||
| 8 | #:web-app-info-p | ||
| 9 | #:copy-web-app-info | ||
| 10 | #:web-app-info-url)) | ||
| 11 | (in-package :ukkoclot/tg-types/web-app-info) | ||
| 12 | |||
| 13 | (define-tg-type web-app-info | ||
| 14 | (url string)) | ||