checkins.lisp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. (in-package :cl-user)
  2. (ql:quickload :address-formatting)
  3. (defpackage chatikbot.plugins.checkins
  4. (:use :cl :chatikbot.common :address-formatting))
  5. (in-package :chatikbot.plugins.checkins)
  6. (def-db-init
  7. (db-execute "create table if not exists checkins_chats (from_id, chat_id, primary key (from_id, chat_id)) without rowid")
  8. (db-execute "create table if not exists checkins_history (ts, from_id, from_user, lat, lon, place_id, place_name, place_icon, place_address)"))
  9. (defun db/get-chats (from-id)
  10. (mapcar #'car (db-select "select chat_id from checkins_chats where from_id = ?" from-id)))
  11. (defun db/add-chat (from-id chat-id)
  12. (db-execute "insert into checkins_chats (from_id, chat_id) values (?, ?)" from-id chat-id))
  13. (defun db/del-chat (from-id chat-id)
  14. (db-execute "delete from checkins_chats where from_id = ? and chat_id = ?" from-id chat-id))
  15. (defun db/add-checkin (from-id from-user lat lon place-id place-name place-icon place-address)
  16. (db-execute "insert into checkins_history (ts, from_id, from_user, lat, lon, place_id, place_name, place_icon, place_address) values (?, ?, ?, ?, ?, ?, ?, ?, ?)"
  17. (local-time:timestamp-to-unix (local-time:now))
  18. from-id from-user lat lon place-id place-name placeicon place-address))
  19. (defparameter +chat-checkins-window+ (* 60 60 24) "how long to look for checkins")
  20. (defun db/get-chat-checkins (chat-id)
  21. (db-select "with numbered as (select h.*, row_number() over (partition by h.from_id order by h.ts desc) as rn from checkins_history h inner join checkins_chats c on h.from_id = c.from_id where c.chat_id = ? and h.ts >= ? order by h.ts desc) select ts, from_id, from_user, lat, lon, place_id, place_name, place_icon, place_address from numbered where rn = 1"
  22. chat-id (- (local-time:timestamp-to-unix (local-time:now))
  23. +chat-checkins-window+)))
  24. (defun get-icon (res)
  25. (let ((cat (keyify (aget "category" res)))
  26. (type (keyify (aget "type" res)))
  27. (sport (keyify (aget "sport" res))))
  28. (case sport
  29. (:soccer "⚽️")
  30. (:tennis "🎾")
  31. (:basketball "🏀")
  32. (:baseball "⚾️")
  33. (:multi "🏆")
  34. (:swimming "🏊")
  35. (:golf "🏌️")
  36. (:equestrian "🏇")
  37. (:billiards "🎱")
  38. (:running "🏃")
  39. (:fitnes "🤸")
  40. (:athletics "🏋️")
  41. (:table-tennis "🏓")
  42. (:beachvolleyball "🏐")
  43. (:climbing "🧗")
  44. (:volleyball "🏐")
  45. (:skateboard "🛹")
  46. (:boules "🎳")
  47. (:american-football "🏈")
  48. (:motor "🏎")
  49. (:bowls "🎳")
  50. (:shooting "🎯")
  51. (:cricket "🏏")
  52. (:netball "🥍")
  53. (:horse-racing "🏇")
  54. (:cycling "🚴")
  55. (:motocross "🏍")
  56. (:rugby-union "🏉")
  57. (:skiing "⛷")
  58. (:gymnastics "🤸")
  59. (:karting "🏎")
  60. (:handball "🏉")
  61. (:badminton "🏸")
  62. (:ice-hockey "🏒")
  63. (:field-hockey "🏑")
  64. (t (case cat
  65. (:amenity (case type
  66. (:atm "🏧")
  67. (:bank "🏦")
  68. (:restaurant "🍽")
  69. (:cafe "☕️")
  70. (:fast-food "🌯")
  71. (:pharmacy "💊")
  72. (:kindergarten "🧸")
  73. (:bar "🍸")
  74. (:hospital "🏥")
  75. (:post-office "📯")
  76. (:pub "🍺")
  77. (:library "📚")
  78. ))
  79. (:leisure (case type
  80. (:pitch)))
  81. )))))
  82. (defvar *revgeo-format* "http://osmnames-search/r/~,6f/~,6f.js?count=10")
  83. (defun get-places (lat lon)
  84. (handler-case
  85. (loop for res in (agets (json-request (format nil *revgeo-format* lon lat))
  86. "results")
  87. collect (list (cons :id (agets res "id"))
  88. (cons :name (agets res "name"))
  89. (cons :icon (get-icon res))
  90. (cons :address (format-address res :one-line t))))
  91. (error (e)
  92. (log:error e)
  93. `(((:id . 0) (:name . "Не нашлось"))))))
  94. (defun add-place (name lat lon from-id)
  95. `((:name . ,name) (:id . ,from-id) (:lat . ,lat) (:lon . ,lon)))
  96. (defun get-name (from)
  97. (let ((username (agets from "username"))
  98. (first-name (agets from "first_name"))
  99. (last-name (agets from "last_name")))
  100. (if username
  101. (format nil "@~a" username)
  102. (format nil "~a ~a" first-name last-name))))
  103. (defun checkin-message (checkin)
  104. (destructuring-bind (ts from-id from-name lat lon place-id place-name place-icon place-address)
  105. checkin
  106. (declare (ignore from-id lat lon place-id))
  107. (format nil "~a ~@[~A~]~@[ в ~A~]~@[ (~A)~]~@[ @ ~a~]"
  108. (or place-icon "📍") from-name place-name place-address
  109. (when ts (format-ts (local-time:unix-to-timestamp ts))))))
  110. (defun checkin (from place lat lon)
  111. (let* ((from-id (agets from "id"))
  112. (from-name (get-name from))
  113. (place-id (agets place :id))
  114. (place-name (agets place :name))
  115. (place-icon (agets place :icon))
  116. (place-address (agets place :address)))
  117. (db/add-checkin from-id from-name lat lon place-id place-name place-icon place-address)
  118. (loop for chat-id in (db/get-chats from-id)
  119. do (telegram-send-venue chat-id lat lon
  120. (format nil "~a ~@[~A~]~@[ в ~A~]"
  121. (or place-icon "📍")
  122. from-name place-name)
  123. place-address)
  124. ;;do (bot-send-message message :chat-id chat-id :parse-mode "markdown")
  125. )
  126. (telegram-edit-message-reply-markup nil
  127. :chat-id *source-chat-id*
  128. :message-id *source-message-id*)))
  129. (defparameter +button-columns+ 2)
  130. (def-message-handler handle-location (10)
  131. (alexandria:when-let ((location (aget "location" *message*)))
  132. (alexandria:when-let ((chats (db/get-chats *from-id*))
  133. (lat (agets location "latitude"))
  134. (lon (agets location "longitude")))
  135. (log:info "handler-location" *from-id* location)
  136. (let ((places (get-places lat lon)))
  137. (log:info places)
  138. (bot-send-message
  139. (if places "Выбери место" "Не нашлось ничего :(")
  140. :reply-markup
  141. (get-inline-keyboard
  142. (append
  143. (let* ((buttons (loop for place in places
  144. for idx from 0
  145. collect (cons idx (let ((this-place place))
  146. (inline-button ((format nil "~@[~a ~]~a"
  147. (agets place :icon)
  148. (agets place :name)))
  149. (checkin *from* this-place lat lon))))))
  150. (groups (group-by buttons (lambda (p) (truncate (car p) +button-columns+)))))
  151. (loop for (i . pairs) in (reverse groups)
  152. collect (mapcar #'cdr pairs)))
  153. (list (list (inline-button ("+")
  154. (bot-send-message "Введи название")
  155. (let ((source-chat-id *source-chat-id*)
  156. (source-message-id *source-message-id*))
  157. (on-next-message
  158. (let ((*source-chat-id* source-chat-id)
  159. (*source-message-id* source-message-id))
  160. (checkin *from*
  161. (add-place *text* lat lon *from-id*)
  162. lat lon)))))))))))
  163. t)))
  164. (def-message-cmd-handler handler-checkins (:checkins)
  165. (let ((chats (db/get-chats *from-id*)))
  166. (if (member *chat-id* chats)
  167. (progn
  168. (db/del-chat *from-id* *chat-id*)
  169. (bot-send-message (format nil "Больше сюда не шлём чекины ~a" (get-name *from*))))
  170. (progn
  171. (db/add-chat *from-id* *chat-id*)
  172. (bot-send-message (format nil "Теперь шлём сюда чекины ~a" (get-name *from*)))))))
  173. (def-message-cmd-handler handler-checkin (:checkin)
  174. (if (equal "private" (agets *message* "chat" "type"))
  175. (bot-send-message "Отправь локейшн"
  176. :reply-markup (telegram-reply-keyboard-markup
  177. '(((:request-location t :text "Послать координаты")))
  178. :resize-keyboard t
  179. :one-time-keyboard t))
  180. (bot-send-message "Нужно в личку"
  181. :reply-markup (telegram-inline-keyboard-markup
  182. `(((:text "Перейти"
  183. :url ,(format nil "tg://user?id=~a" *bot-user-id*))))))))
  184. (def-message-cmd-handler handler-where (:where)
  185. (let ((checkins (db/get-chat-checkins *chat-id*)))
  186. (bot-send-message
  187. (if checkins
  188. (format nil "~{~a~^~%~}" (mapcar #'checkin-message checkins))
  189. "Никто не чекинился последнее время")
  190. :parse-mode "markdown")))