zhanna.lisp 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. (in-package #:chatikbot)
  2. (defsetting *zhanna-token-id* nil)
  3. (defsetting *zhanna-sheet-id* "1kLBodFUwcfbpdqe_d2d01MHij95NAVcKrmpjotYsUQk")
  4. (defun %zh-parse-values (rows)
  5. (labels ((non-empty (s)
  6. (unless (equal s "") s))
  7. (get-el (row idx)
  8. (when (> (length row) idx)
  9. (non-empty (elt row idx))))
  10. (get-item (row start)
  11. (cons (get-el row start) (get-el row (1+ start)))))
  12. (loop for start to 4 by 2
  13. append (loop for row in rows
  14. for item = (get-item row start)
  15. when (car item)
  16. collect (cons (subseq (car item) 0 5) (cdr item))))))
  17. (defparameter +zhanna-dows+ '("вс" "пн" "вт" "ср" "чт" "пт" "сб"))
  18. (defun zhanna-get-schedule (&rest days)
  19. (labels ((dow-range (dow)
  20. (format nil "'~A'!A3:F22" (elt +zhanna-dows+ dow))))
  21. (let* ((ranges (mapcar #'dow-range days))
  22. (resp (gsheets-get-sheet-values *zhanna-token-id* *zhanna-sheet-id* ranges)))
  23. (loop for vr in (aget "valueRanges" resp)
  24. collect (cons (subseq (aget "range" vr) 1 3)
  25. (%zh-parse-values (aget "values" vr)))))))
  26. (defun %zh-diff-schedule (old-s new-s)
  27. (let ((times (mapcar #'car (union old-s new-s :key #'car :test #'equal))))
  28. (loop for time in times
  29. for new = (aget time new-s)
  30. for old = (aget time old-s)
  31. unless (equal new old)
  32. collect (list (cons new old) time))))
  33. (defun %zh-format-diff (diff)
  34. (labels ((join (joined value)
  35. (destructuring-bind (change time) value
  36. (let ((existing (aget change joined)))
  37. (if existing
  38. (progn
  39. (setf (cdr existing) (append (cdr existing) (list time)))
  40. joined)
  41. (append joined (list value))))))
  42. (text-change (change)
  43. (destructuring-bind (new . old) change
  44. (cond
  45. ((null old) (format nil "🏄 ~A" new))
  46. ((null new) (format nil "💔 ~A" old))
  47. (:otherwise (format nil "~A 🔁 ~A" old new))))))
  48. (let ((joined (reduce #'join diff :initial-value (list))))
  49. (format nil "~{~A~^~%~}" (loop for (change . times) in joined
  50. collect (format nil "~A: ~{~A~^, ~}" (text-change change) times))))))
  51. (defsetting *zhanna-subscriptions* nil "chat-ids of schedule changes receivers")
  52. (defvar *zhanna-last-schedule* nil "Schedule from last cron run")
  53. (defcron process-zhanna ()
  54. (let ((schedule (zhanna-get-schedule 1 2 3 4 5 6 0)))
  55. (when *zhanna-last-schedule*
  56. (handler-case
  57. (let* ((day-changes (loop for dow in +zhanna-dows+
  58. for new-s = (aget dow schedule)
  59. for old-s = (aget dow *zhanna-last-schedule*)
  60. for diff = (%zh-diff-schedule old-s new-s)
  61. when diff
  62. collect (format nil "*~A:*~%~A" dow (%zh-format-diff diff))))
  63. (msg (format nil "~{~A~^~%~%~}" day-changes)))
  64. (when day-changes
  65. (log:info msg)
  66. (dolist (chat-id *zhanna-subscriptions*)
  67. (bot-send-message chat-id msg :parse-mode "markdown"))))
  68. (error (e) (log:error e))))
  69. (setf *zhanna-last-schedule* schedule)
  70. (values)))
  71. (def-message-cmd-handler zhanna-free-handler (:free :свободна :свободно)
  72. (let* ((dows (if args (remove nil (mapcar
  73. #'(lambda (a) (position a +zhanna-dows+ :test #'equal))
  74. args))
  75. (list (mod (1+ (local-time:timestamp-day-of-week (local-time:now))) 7))))
  76. (schedule (apply #'zhanna-get-schedule dows))
  77. (texts (loop for (dow . day-schedule) in schedule
  78. collect (format nil "*~A*~%~{~A~^, ~}" dow
  79. (mapcar #'car (remove nil day-schedule :key #'cdr :test-not #'eql))))))
  80. (bot-send-message chat-id (format nil "Жанна свободна:~%~{~A~^~%~}" texts) :parse-mode "markdown")))