chatikbot.lisp 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. (in-package #:chatikbot)
  2. ;; Load config file
  3. (alexandria:when-let (file (probe-file
  4. (merge-pathnames "config.lisp"
  5. (asdf:component-pathname
  6. (asdf:find-system '#:chatikbot)))))
  7. (load file))
  8. ;; Init database
  9. (db-init)
  10. (defvar *telegram-last-update* nil "Telegram last update_id")
  11. (defvar *admins* nil "Admins chat-ids")
  12. ;; getUpdates handling
  13. (defun process-updates ()
  14. (loop for update in (telegram-get-updates :offset (and *telegram-last-update*
  15. (1+ *telegram-last-update*))
  16. :timeout 300)
  17. do (setf *telegram-last-update*
  18. (max (or *telegram-last-update* 0)
  19. (aget "update_id" update)))
  20. do (handle-update update)))
  21. (defun handle-update (update)
  22. (log:info update)
  23. (loop for (key . value) in update
  24. unless (equal "update_id" key)
  25. do (run-update-hooks (key-to-hook-name key) value)))
  26. ;;
  27. (defun send-response (chat-id response &optional reply-id)
  28. (if (consp response)
  29. (if (keywordp (car response))
  30. (case (car response)
  31. (:text (telegram-send-message chat-id (cdr response) :reply-to reply-id))
  32. (:voice (telegram-send-voice chat-id (cdr response) :reply-to reply-id))
  33. (:sticker (telegram-send-sticker chat-id (cdr response) :reply-to reply-id)))
  34. (mapc #'(lambda (r) (send-response chat-id r reply-id)) response))
  35. (telegram-send-message chat-id response :reply-to reply-id)))
  36. (defun bot-send-message (chat-id text &key parse-mode disable-web-preview reply-to reply-markup)
  37. (handler-case (telegram-send-message chat-id text :parse-mode parse-mode
  38. :disable-web-preview disable-web-preview
  39. :reply-to reply-to
  40. :reply-markup reply-markup)
  41. (error (e)
  42. (log:error e))))
  43. (defun send-dont-understand (chat-id &optional text reply-id)
  44. (let ((resp (eliza text)))
  45. (log:info text resp)
  46. (when resp
  47. (send-response chat-id resp reply-id))))
  48. (defun handle-unknown-message (message)
  49. (let ((chat-id (aget "id" (aget "chat" message)))
  50. (text (aget "text" message)))
  51. (log:info "handle-unknown-message" message)
  52. (send-dont-understand chat-id (preprocess-input text))
  53. t))
  54. (add-update-hook :message 'handle-unknown-message t)
  55. (defun process-watchdog ()
  56. (ignore-errors
  57. (close
  58. (open (merge-pathnames ".watchdog"
  59. (asdf:component-pathname
  60. (asdf:find-system '#:chatikbot)))
  61. :direction :output
  62. :if-exists :supersede
  63. :if-does-not-exist :create))))
  64. (defvar *save-settings-lock* (bordeaux-threads:make-lock "save-settings-lock")
  65. "Lock for multithreading access to write settings file")
  66. (defun save-settings()
  67. (bordeaux-threads:with-lock-held (*save-settings-lock*)
  68. (with-open-file (s (merge-pathnames "settings.lisp"
  69. (asdf:component-pathname
  70. (asdf:find-system '#:chatikbot)))
  71. :direction :output
  72. :if-exists :supersede
  73. :if-does-not-exist :create)
  74. (write '(in-package #:chatikbot) :stream s)
  75. (write
  76. `(setf *chat-locations* ',*chat-locations*
  77. *akb-send-to* ',*akb-send-to*
  78. *akb-last-id* ,*akb-last-id*)
  79. :stream s)
  80. (values))))
  81. (defvar *schedules* '(process-latest-akb
  82. process-latest-checkins
  83. process-rates
  84. process-feeds
  85. process-walls
  86. process-watchdog) "Enabled schedules")
  87. (defun start ()
  88. ;; Clear prev threads
  89. (mapc #'trivial-timers:unschedule-timer (trivial-timers:list-all-timers))
  90. (let ((old-updates (find "process-updates"
  91. (bordeaux-threads:all-threads)
  92. :key #'bordeaux-threads:thread-name
  93. :test #'equal)))
  94. (when old-updates
  95. (bordeaux-threads:destroy-thread old-updates)))
  96. ;; Load settings file
  97. (alexandria:when-let (file (probe-file
  98. (merge-pathnames "settings.lisp"
  99. (asdf:component-pathname
  100. (asdf:find-system '#:chatikbot)))))
  101. (load file))
  102. ;; Start timers
  103. (dolist (func *schedules*)
  104. (clon:schedule-function func
  105. (clon:make-scheduler
  106. (clon:make-typed-cron-schedule :minute '* :hour '*)
  107. :allow-now-p t)
  108. :name func
  109. :thread t))
  110. ;; YIT
  111. (let ((last-yit-info))
  112. (clon:schedule-function
  113. #'(lambda() (let ((info (yit-info)))
  114. (when (not (equal info last-yit-info))
  115. (send-response (car *admins*) info)
  116. (setf last-yit-info info))))
  117. (clon:make-scheduler
  118. (clon:make-typed-cron-schedule :minute 0 :hour '*)
  119. :allow-now-p t)
  120. :name "YIT" :thread t))
  121. ;; Nalunch
  122. (clon:schedule-function
  123. #'process-nalunch (clon:make-scheduler (clon:make-typed-cron-schedule
  124. :minute '(member 0 15 30 45))
  125. :allow-now-p t)
  126. :name "Nalunch" :thread t)
  127. ;; Start getUpdates thread
  128. (bordeaux-threads:make-thread
  129. (lambda () (loop-with-error-backoff #'process-updates))
  130. :name "process-updates")
  131. ;; Notify admins
  132. (dolist (admin *admins*)
  133. (telegram-send-message admin (format nil "chatikbot started at ~A" (format-ts (local-time:now))))))