macros.lisp 6.2 KB

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