1
0

zhanna.lisp 6.5 KB

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