macros.lisp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. (in-package :cl-user)
  2. (defpackage chatikbot.macros
  3. (:use :cl :chatikbot.utils :chatikbot.telegram :chatikbot.crypto)
  4. (:export :def-db-init
  5. :with-parsed-message
  6. :def-message-handler
  7. :def-message-cmd-handler
  8. :def-message-admin-cmd-handler
  9. :with-parsed-callback
  10. :def-callback-handler
  11. :def-callback-section-handler
  12. :def-oauth-handler
  13. :def-oauth-section-handler
  14. :with-random-state
  15. :with-chat-in-list
  16. :defcron))
  17. (in-package #:chatikbot.macros)
  18. (defmacro def-db-init (&body body)
  19. `(add-hook :db-init #'(lambda ()
  20. (handler-case (progn ,@body)
  21. (error (e) (log:error e)))
  22. (values))))
  23. (defmacro with-parsed-message (message &body body)
  24. `(let* ((*message* ,message)
  25. (*message-id* (agets *message* "message_id"))
  26. (*from* (agets *message* "from"))
  27. (*from-id* (agets *from* "id"))
  28. (*chat-id* (agets *message* "chat" "id"))
  29. (*text* (agets *message* "text")))
  30. ,@body))
  31. (defmacro def-message-handler (name (&optional prio) &body body)
  32. (alexandria:with-gensyms (g-message)
  33. `(progn
  34. (defun ,name (,g-message)
  35. (with-parsed-message ,g-message
  36. (handler-case (progn ,@body)
  37. (error (e)
  38. (log:error "~A" e)
  39. (bot-send-message (format nil "Ошибочка вышла~@[: ~A~]"
  40. (when (is-admin) e)))))))
  41. (when ,prio (setf (get ',name :prio) ,prio))
  42. (add-hook :update-message ',name))))
  43. (defmacro def-message-cmd-handler (name (&rest commands) &body body)
  44. `(def-message-handler ,name ()
  45. (when (and *text* (equal #\/ (char *text* 0)))
  46. (multiple-value-bind (*cmd* *args*) (parse-cmd *text*)
  47. (when (member *cmd* (list ,@commands))
  48. (log:info *cmd* *message-id* *chat-id* *from-id* *args*)
  49. ,@body
  50. t)))))
  51. (defmacro def-message-admin-cmd-handler (name (&rest commands) &body body)
  52. `(def-message-handler ,name ()
  53. (when (and (is-admin)
  54. *text* (equal #\/ (char *text* 0)))
  55. (multiple-value-bind (*cmd* *args*) (parse-cmd *text*)
  56. (when (member *cmd* (list ,@commands))
  57. (log:info *cmd* *message-id* *chat-id* *from-id* *args*)
  58. ,@body
  59. t)))))
  60. (defmacro with-chat-in-list (list-id &body body)
  61. `(if (member *chat-id* (chatikbot.db:lists-get ,list-id))
  62. (progn ,@body)
  63. (bot-send-message "А вот хуй!")))
  64. (defmacro with-parsed-callback (callback &body body)
  65. `(let* ((*callback* ,callback)
  66. (*query-id* (agets *callback* "id"))
  67. (*from* (agets *callback* "from"))
  68. (*raw-data* (agets *callback* "data"))
  69. (*source-message* (agets *callback* "message"))
  70. (*inline-message-id* (agets *callback* "inline_message_id"))
  71. (*from-id* (agets *from* "id"))
  72. (*source-chat-id* (agets *source-message* "chat" "id"))
  73. (*source-message-id* (agets *source-message* "message_id"))
  74. (*chat-id* *source-chat-id*))
  75. ,@body))
  76. (defmacro def-callback-handler (name (&optional prio) &body body)
  77. (alexandria:with-gensyms (g-callback)
  78. `(progn
  79. (defun ,name (,g-callback)
  80. (with-parsed-callback ,g-callback
  81. (handler-case (progn ,@body)
  82. (error (e)
  83. (log:error "~A" e)
  84. (bot-send-message (format nil "Ошибочка вышла~@[: ~A~]"
  85. (when (is-admin) e))
  86. :chat-id (or *source-chat-id* *from-id*))))))
  87. (when ,prio (setf (get ',name :prio) ,prio))
  88. (add-hook :update-callback-query ',name))))
  89. (defmacro def-callback-section-handler (name (&rest sections) &body body)
  90. `(def-callback-handler ,name ()
  91. (when *source-chat-id*
  92. (multiple-value-bind (*data* *section*) (decode-callback-data *source-chat-id* *raw-data*)
  93. (when (member *section* (list ,@sections))
  94. (log:info *query-id* *from-id* *source-chat-id* *source-message-id* *section* *data*)
  95. ,@body
  96. t)))))
  97. (defmacro def-oauth-handler (name (code error state &optional prio) &body body)
  98. `(progn
  99. (defun ,name (,code ,error ,state)
  100. (declare (ignorable ,code ,error ,state))
  101. (handler-case (progn ,@body)
  102. (error (e)
  103. (log:error "~A" e)
  104. (hunchentoot:redirect "/error"))))
  105. (when ,prio (setf (get ',name :prio) ,prio))
  106. (add-hook :oauth ',name)))
  107. (defmacro def-oauth-section-handler (name (&rest sections) &body body)
  108. `(def-oauth-handler ,name (*code* *error* *raw-state*)
  109. (multiple-value-bind (*state* *section*) (decode-oauth-state *raw-state*)
  110. (when (member *section* (list ,@sections))
  111. ,@body
  112. t))))
  113. ;; On CCL at least, new thread clones origin *random-state* thus
  114. ;; all cron threads generate same random values. To overcome this we'll
  115. ;; generate per-thread *random-state*
  116. (defvar *random-state-lock* (bt:make-recursive-lock
  117. "random state lock") "per-thread random state lock")
  118. (defvar *thread-random-state* (tg:make-weak-hash-table :weakness :key) "Per-thread *random-state* storage")
  119. (defun get-thread-random-state ()
  120. (bt:with-recursive-lock-held (*random-state-lock*)
  121. (let ((self (bt:current-thread)))
  122. (or (gethash self *thread-random-state*)
  123. (setf (gethash self *thread-random-state*)
  124. (make-random-state t))))))
  125. (defmacro with-random-state (&body body)
  126. `(let ((*random-state* (get-thread-random-state)))
  127. ,@body))
  128. ;; Schedule
  129. (defmacro defcron (name (&rest schedule) &body body)
  130. (let ((schedule (or schedule '(:minute '* :hour '*)))
  131. (scheduler (symbol-append name '-scheduler)))
  132. `(progn
  133. (defun ,name ()
  134. (with-random-state
  135. (unwind-protect
  136. (handler-case
  137. #+sbcl (sb-sys:with-interrupts ,@body)
  138. #-sbcl (progn ,@body)
  139. (error (e) (log:error "~A" e)))
  140. (dex:clear-connection-pool))))
  141. (defun ,scheduler ()
  142. (clon:schedule-function
  143. ',name (clon:make-scheduler
  144. (clon:make-typed-cron-schedule
  145. ,@schedule)
  146. :allow-now-p t)
  147. :name ',name
  148. :thread (bt:make-thread (lambda () (loop (sleep 1)))
  149. :name (format nil "Sleeper '~A'" (symbol-name ',name))))
  150. (values))
  151. (add-hook :starting ',scheduler))))