inline.lisp 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. (in-package :cl-user)
  2. (defpackage chatikbot.inline
  3. (:use :cl :chatikbot.utils :chatikbot.telegram :chatikbot.crypto
  4. :chatikbot.macros)
  5. (:export :get-inline-keyboard
  6. :inline-button))
  7. (in-package :chatikbot.inline)
  8. (defstruct button callback created)
  9. (defparameter +max-id+ 2000000 "Buttons store key range")
  10. (defparameter +max-tries+ 100 "Tries to find free store key")
  11. (defparameter +ttl+ (* 60 60 24 7) "Weekly TTL for buttons in store")
  12. (defvar *inline-buttons* (make-hash-table) "Hashtable storing button actions")
  13. (defvar *buttons-lock* (bt:make-recursive-lock "inline buttons lock"))
  14. (defun add-button (callback)
  15. (bt:with-recursive-lock-held (*buttons-lock*)
  16. (let ((id (loop for id = (random +max-id+) then (random +max-id+)
  17. for try from 1
  18. unless (gethash id *inline-buttons*)
  19. do (return id)
  20. when (> try +max-tries+)
  21. do (error "Can't get inline button id in ~A tries" try))))
  22. (setf (gethash id *inline-buttons*)
  23. (make-button :callback callback
  24. :created (get-universal-time)))
  25. id)))
  26. (defun prune-buttons ()
  27. (bt:with-recursive-lock-held (*buttons-lock*)
  28. (let ((now (get-universal-time)))
  29. (loop for id being the hash-keys in *inline-buttons* using (hash-value button)
  30. when (> (- now (button-created button))
  31. +ttl+)
  32. do (remhash id *inline-buttons*)))))
  33. (defcron process-prune-buttons ()
  34. (prune-buttons))
  35. (defmacro inline-button ((text) &body body)
  36. (alexandria:with-gensyms (g-callback)
  37. `(cons ,text
  38. (lambda (,g-callback)
  39. (with-parsed-callback ,g-callback
  40. (handler-case (progn ,@body)
  41. (error (e)
  42. (log:error "~A" e)
  43. (telegram-answer-callback-query
  44. *query-id*
  45. :text (format nil "Ошибочка вышла~@[: ~A~]"
  46. (when (is-admin) e))))))))))
  47. (defun get-inline-keyboard (buttons &optional (chat-id *chat-id*))
  48. (telegram-inline-keyboard-markup
  49. (loop for row in buttons
  50. when row
  51. collect (loop for (text . callback) in row
  52. when text
  53. collect (list
  54. :text text
  55. :callback-data
  56. (encode-callback-data
  57. chat-id :inline (add-button callback) +ttl+))))))
  58. (def-callback-section-handler cb-handle-inline (:inline)
  59. (let ((button (gethash (parse-integer *data*) *inline-buttons*)))
  60. (if button
  61. (funcall (button-callback button) *callback*)
  62. (progn
  63. (log:error "Can't find button id ~A" *data*)
  64. (telegram-answer-callback-query *query-id* :text "Ошибка")))))