foursquare.lisp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. (in-package #:chatikbot)
  2. (defparameter *fsq-checkins-url* "https://api.foursquare.com/v2/checkins/recent"
  3. "URL of recent checkins API")
  4. (defparameter *fsq-api-url* "https://api.foursquare.com/v2/~A"
  5. "Foursquare API URL")
  6. (defsetting *fsq-access-token* nil "Access token for a user under which the process is run")
  7. (defun %fsq-api-call (method &optional params)
  8. (let* ((resp
  9. (handler-case
  10. (bordeaux-threads:with-timeout (5)
  11. (json-request (format nil *fsq-api-url* method)
  12. :parameters (list*
  13. (cons "oauth_token" *fsq-access-token*)
  14. (cons "v" "20150811")
  15. params)))
  16. (bordeaux-threads:timeout () (error "Timeout"))))
  17. (meta (aget "meta" resp)))
  18. (when (not (= 200 (aget "code" meta)))
  19. (error (format nil "Foursquare API error, code ~A, errorType '~A', errorDetail '~A'"
  20. (aget "code" meta) (aget "errorType" meta) (aget "errorDetail" meta))))
  21. (aget "response" resp)))
  22. (defun fsq-fetch-checkins (&optional after-timestamp limit)
  23. (aget "recent"
  24. (%fsq-api-call "checkins/recent"
  25. (list (cons "afterTimestamp" (or after-timestamp "0"))
  26. (cons "limit" (or limit "20"))))))
  27. (defun fsq-fetch-friends (&optional offset)
  28. (list*
  29. (aget "user" (%fsq-api-call "users/self"))
  30. (aget "items"
  31. (aget "friends"
  32. (%fsq-api-call "users/self/friends"
  33. (list (cons "offset" (or offset "0"))))))))
  34. ;; Formatting
  35. (defun fsq-user-name (user)
  36. (when user
  37. (format nil "~@[~A~]~@[ ~A~]"
  38. (aget "firstName" user)
  39. (aget "lastName" user))))
  40. (defun fsq-format-checkin (checkin &optional with-dates)
  41. (when checkin
  42. (let ((user (aget "user" checkin))
  43. (venue (aget "venue" checkin)))
  44. (format nil "📍 ~@[~A~]~@[ ~A~]~@[ в ~A~]~@[ (~A)~]~@[ 📢 ~A~]~:[~; ~A~]"
  45. (aget "firstName" user) (aget "lastName" user)
  46. (aget "name" venue) (first (aget "formattedAddress" (aget "location" venue)))
  47. (aget "shout" checkin)
  48. with-dates
  49. (local-time:format-timestring
  50. nil
  51. (local-time:unix-to-timestamp (aget "createdAt" checkin))
  52. :format '(:year "-" (:month 2) "-" (:day 2) " " (:hour 2) ":" (:min 2)))))))
  53. ;; Database
  54. (def-db-init
  55. (db-execute "create table if not exists fsq_chat_users (chat_id, user_id)")
  56. (db-execute "create index if not exists fsq_chat_users_chat_idx on fsq_chat_users (chat_id)")
  57. (db-execute "create index if not exists fsq_chat_users_user_idx on fsq_chat_users (user_id)")
  58. (db-execute "create table if not exists fsq_seen (id, created_at)")
  59. (db-execute "create index if not exists fsq_seen_idx on fsq_seen (id)"))
  60. (defun db/fsq-get-chat-users (chat-id)
  61. (flatten (db-select "select user_id from fsq_chat_users where chat_id = ?" chat-id)))
  62. (defun db/fsq-get-user-chats (user-id)
  63. (flatten (db-select "select chat_id from fsq_chat_users where user_id = ?" user-id)))
  64. (defun db/fsq-set-chat-users (chat-id users)
  65. (db-transaction
  66. (db-execute "delete from fsq_chat_users where chat_id = ?" chat-id)
  67. (dolist (user-id users)
  68. (db-execute "insert into fsq_chat_users (chat_id, user_id) values (?, ?)" chat-id user-id))))
  69. (defun db/fsq-add-seen (id created-at)
  70. (db-execute "insert into fsq_seen (id, created_at) values (?, ?)" id created-at))
  71. (defun db/fsq-has-seen (id)
  72. (db-single "select id from fsq_seen where id = ? limit 1" id))
  73. (defun db/fsq-last-created ()
  74. (db-single "select created_at from fsq_seen order by created_at desc limit 1"))
  75. ;; Cron
  76. (defcron process-latest-checkins ()
  77. (let ((checkins (make-hash-table))
  78. (ts (princ-to-string (1+ (or (db/fsq-last-created) -1)))))
  79. (dolist (checkin (fsq-fetch-checkins ts))
  80. (let ((id (aget "id" checkin))
  81. (created-at (aget "createdAt" checkin))
  82. (user (aget "id" (aget "user" checkin))))
  83. (unless (db/fsq-has-seen id)
  84. (dolist (chat-id (db/fsq-get-user-chats user))
  85. (push (fsq-format-checkin checkin)
  86. (gethash chat-id checkins)))
  87. (db/fsq-add-seen id created-at))))
  88. (loop for chat-id being the hash-keys in checkins using (hash-value texts)
  89. do (log:info "Sending checkins" chat-id texts)
  90. (telegram-send-message chat-id (format nil "~{~A~^~%~}" texts)))))
  91. ;; Hooks
  92. (def-message-cmd-handler handle-cmd-post-checkins (:postcheckins)
  93. (let ((users (db/fsq-get-chat-users chat-id))
  94. (friends (fsq-fetch-friends)))
  95. (if (null args)
  96. (bot-send-message chat-id
  97. (if (null users)
  98. "Пока никого не палим"
  99. (format nil "Палим ~{~A~^, ~}"
  100. (loop for user in friends
  101. when (member (aget "id" user)
  102. users :test #'equal)
  103. collect (fsq-user-name user)))))
  104. (progn
  105. (dolist (user args)
  106. (let ((username (fsq-user-name
  107. (find user friends
  108. :test #'equal
  109. :key #'(lambda (f) (aget "id" f))))))
  110. (when username
  111. (if (member user users :test #'equal)
  112. (progn
  113. (setf users (remove user users :test #'equal))
  114. (bot-send-message chat-id
  115. (format nil "Больше не палим ~A" username)))
  116. (progn
  117. (push user users)
  118. (bot-send-message chat-id (format nil "Теперь палим ~A" username)))))))
  119. (db/fsq-set-chat-users chat-id users)))))
  120. (def-message-cmd-handler handle-cmd-friends (:friends)
  121. (let ((users (db/fsq-get-chat-users chat-id))
  122. (friends (fsq-fetch-friends)))
  123. (bot-send-message chat-id
  124. (format
  125. nil "~{~A: ~:[~;📍 ~]~A~^~%~}"
  126. (loop for user in friends
  127. append (list
  128. (aget "id" user)
  129. (member (aget "id" user) users :test #'equal)
  130. (fsq-user-name user)))))))
  131. (def-message-cmd-handler handle-cmd-checkins (:checkins)
  132. (let ((users (db/fsq-get-chat-users chat-id)))
  133. (when users
  134. (bot-send-message chat-id
  135. (format nil "~{~A~^~%~}"
  136. (loop for checkin in (fsq-fetch-checkins)
  137. if (member (aget "id" (aget "user" checkin)) users :test #'equal)
  138. collect (fsq-format-checkin checkin t)))))))