1
0

zhanna.lisp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  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 y from 0
  15. for item = (get-item row start)
  16. when (car item)
  17. collect (list (subseq (car item) 0 5) (cdr item) (cons start y))))))
  18. (defparameter +zhanna-dows+ '("вс" "пн" "вт" "ср" "чт" "пт" "сб"))
  19. (defun zhanna-get-schedule (&rest days)
  20. (labels ((dow-range (dow)
  21. (format nil "'~A'!A3:F30" (elt +zhanna-dows+ dow))))
  22. (let* ((ranges (mapcar #'dow-range days))
  23. (resp (gsheets-get-sheet-values *zhanna-token-id* *zhanna-sheet-id* ranges)))
  24. (loop for vr in (aget "valueRanges" resp)
  25. collect (cons (subseq (aget "range" vr) 1 3)
  26. (%zh-parse-values (aget "values" vr)))))))
  27. (defun zhanna-set-schedule (token-id name dow time &optional (count 1))
  28. (let* ((schedule (cdar (zhanna-get-schedule dow)))
  29. (start (member time schedule :key #'car :test #'equal))
  30. (positions (loop for (time name pos) in start
  31. while (null name)
  32. collect pos)))
  33. (cond
  34. ((null schedule) "Плохой день")
  35. ((null start) "Плохое время")
  36. ((< (length positions) count) (format nil "~[В ~A занято~:;Есть только ~:*~A с ~A~]"
  37. (length positions) time))
  38. (:otherwise
  39. (labels ((range-value (pos)
  40. (cons
  41. (format nil "'~A'!~A~A"
  42. (elt +zhanna-dows+ dow)
  43. (code-char (+ (car pos)
  44. (char-code #\B)))
  45. (+ 3 (cdr pos)))
  46. (list (list name)))))
  47. (let* ((resp (gsheets-put-sheet-values token-id *zhanna-sheet-id*
  48. (mapcar #'range-value (subseq positions 0 count))))
  49. (err (aget "error" resp)))
  50. (when (equal 403 (aget "code" err))
  51. "Нет сил писать")))))))
  52. (defun %zh-diff-schedule (old-s new-s)
  53. (let ((times (mapcar #'car (union old-s new-s :key #'car :test #'equal))))
  54. (loop for time in times
  55. for new = (car (aget time new-s))
  56. for old = (car (aget time old-s))
  57. unless (equal new old)
  58. collect (list (cons new old) time))))
  59. (defun %zh-format-diff (diff)
  60. (labels ((join (joined value)
  61. (destructuring-bind (change time) value
  62. (let ((existing (aget change joined)))
  63. (if existing
  64. (progn
  65. (setf (cdr existing) (append (cdr existing) (list time)))
  66. joined)
  67. (append joined (list value))))))
  68. (text-change (change)
  69. (destructuring-bind (new . old) change
  70. (cond
  71. ((null old) (format nil "🏄 ~A" new))
  72. ((null new) (format nil "💔 ~A" old))
  73. (:otherwise (format nil "~A 🔁 ~A" old new))))))
  74. (let ((joined (reduce #'join diff :initial-value (list))))
  75. (format nil "~{~A~^~%~}" (loop for (change . times) in joined
  76. collect (format nil "~A: ~{~A~^, ~}" (text-change change) times))))))
  77. (defsetting *zhanna-subscriptions* nil "chat-ids of schedule changes receivers")
  78. (defvar *zhanna-last-schedule* nil "Schedule from last cron run")
  79. (defcron process-zhanna ()
  80. (alexandria:when-let ((schedule (zhanna-get-schedule 1 2 3 4 5 6 0)))
  81. (when *zhanna-last-schedule*
  82. (handler-case
  83. (let* ((day-changes (loop for dow in +zhanna-dows+
  84. for new-s = (aget dow schedule)
  85. for old-s = (aget dow *zhanna-last-schedule*)
  86. for diff = (%zh-diff-schedule old-s new-s)
  87. when diff
  88. collect (format nil "*~A:*~%~A" dow (%zh-format-diff diff))))
  89. (msg (format nil "У Жанны сменились планы~%~{~A~^~%~}" day-changes)))
  90. (when day-changes
  91. (log:info msg)
  92. (dolist (chat-id *zhanna-subscriptions*)
  93. (bot-send-message chat-id msg :parse-mode "markdown"))))
  94. (error (e) (log:error e))))
  95. (setf *zhanna-last-schedule* schedule)
  96. (values)))
  97. (def-message-cmd-handler zhanna-free-handler (:free :свободна :свободно)
  98. (let* ((dows (if args (remove nil (mapcar
  99. #'(lambda (a) (position a +zhanna-dows+ :test #'equal))
  100. args))
  101. (list (mod (1+ (local-time:timestamp-day-of-week (local-time:now))) 7))))
  102. (schedule (apply #'zhanna-get-schedule dows))
  103. (texts (loop for (dow . day-schedule) in schedule
  104. collect (format nil "*~A*~%~{~A~^, ~}" dow
  105. (mapcar #'car (remove nil day-schedule :key #'second :test-not #'eql))))))
  106. (bot-send-message chat-id (format nil "Жанна свободна:~%~{~A~^~%~}" texts) :parse-mode "markdown")))
  107. (def-message-cmd-handler zhanna-set-handler (:set :записать)
  108. (if (null (gsheets-get-tokens from-id))
  109. (%gsheets-send-auth chat-id from-id)
  110. (handler-case
  111. (destructuring-bind (name day time &optional (sets "1")) args
  112. (let ((dow (position day +zhanna-dows+ :test #'equal))
  113. (count (parse-integer sets :junk-allowed t)))
  114. (cond
  115. ((null dow) (bot-send-message chat-id "День надо кратко: 'пн', 'вт', 'ср' и т.д"))
  116. ((null count) (bot-send-message chat-id "Число сетов - числом!"))
  117. (:otherwise
  118. (let ((err (zhanna-set-schedule from-id name dow time count)))
  119. (bot-send-message chat-id (or err "Записал!")))))))
  120. (error (e)
  121. (log:info e)
  122. (bot-send-message chat-id "Надо: /set <имя> <день> <время> [кол-во]")))))