Przeglądaj źródła

Twitter importer

Innocenty Enikeew 11 lat temu
rodzic
commit
8b0f469e71
6 zmienionych plików z 81 dodań i 14 usunięć
  1. 3 6
      src/foursquare.lisp
  2. 1 1
      src/locations.lisp
  3. 57 0
      src/twitter.lisp
  4. 7 2
      src/utils.lisp
  5. 7 3
      src/web.lisp
  6. 6 2
      timeliner.asd

+ 3 - 6
src/foursquare.lisp

@@ -2,10 +2,11 @@
 (defpackage #:timeliner.foursquare
 (defpackage #:timeliner.foursquare
   (:use :cl #:timeliner.utils :cl-mongo)
   (:use :cl #:timeliner.utils :cl-mongo)
   (:export
   (:export
+   #:*access-token*
    #:on-cron))
    #:on-cron))
 (in-package #:timeliner.foursquare)
 (in-package #:timeliner.foursquare)
 
 
-(defvar *access-token* "ZLLBUQAWZJLWURZNGQRYN5J5XYYFDHMOSMTTCQYXYTSZUYCE")
+(defvar *access-token*)
 
 
 (defun make-checkin-doc (checkin)
 (defun make-checkin-doc (checkin)
   (let* ((venue (gethash "venue" checkin))
   (let* ((venue (gethash "venue" checkin))
@@ -47,14 +48,10 @@
      for doc = (make-checkin-doc item)
      for doc = (make-checkin-doc item)
      when doc collect doc))
      when doc collect doc))
 
 
-(defun save-checkins (checkins)
-  (dolist (checkin checkins)
-    (db.insert "events" checkin)))
-
 (defun on-cron ()
 (defun on-cron ()
   (let* ((last-checkin (first (docs (db.sort "events" ($ "type" "checkin") :field "ts"
   (let* ((last-checkin (first (docs (db.sort "events" ($ "type" "checkin") :field "ts"
                                              :asc nil :limit 1))))
                                              :asc nil :limit 1))))
          (from (and last-checkin (ms->ts (cl-mongo::raw (get-element "ts" last-checkin)))))
          (from (and last-checkin (ms->ts (cl-mongo::raw (get-element "ts" last-checkin)))))
          (checkins (load-checkins from)))
          (checkins (load-checkins from)))
     (format t "Got ~A checkins from ~A~%" (length checkins) from)
     (format t "Got ~A checkins from ~A~%" (length checkins) from)
-    (save-checkins checkins)))
+    (save-events checkins)))

+ 1 - 1
src/locations.lisp

@@ -184,7 +184,7 @@
        for doc in docs
        for doc in docs
        for docMs = (get-element "timestampMs" doc)
        for docMs = (get-element "timestampMs" doc)
        when (<= docMs ms) do (setf less doc)
        when (<= docMs ms) do (setf less doc)
-       when (and (not greater) (>= docMs ms)) do (setf greater doc))
+       when (and (not greater) (> docMs ms)) do (setf greater doc))
     (when (and less greater)
     (when (and less greater)
       (geo:interpolate-between-points
       (geo:interpolate-between-points
        (doc->point less)
        (doc->point less)

+ 57 - 0
src/twitter.lisp

@@ -0,0 +1,57 @@
+(in-package :cl-user)
+(defpackage #:timeliner.twitter
+  (:use :cl #:timeliner.utils :cl-mongo)
+  (:export
+   #:*access-token*
+   #:on-cron))
+(in-package #:timeliner.twitter)
+
+(defvar *access-token*)
+
+(defun make-twitter-doc (twit)
+  (let* ((ts (local-time:universal-to-timestamp
+              (cl-date-time-parser:parse-date-time
+               (aget "created_at" twit)))))
+    (kv
+     (kv :ts ts)
+     (kv :type "twitter")
+     (kv :title (aget "text" twit))
+     (kv :language (aget "lang" twit))
+     (kv :twitter (kv
+                   (kv "id" (aget "id" twit))
+                   (kv "reply_to" (aget "in_reply_to_status_id" twit))
+                   (kv "retweeted" (aget "retweeted" twit))))
+     (kv :loc (timeliner.locations:point->doc
+               (timeliner.locations:find-location-at (ts->ms ts)))))))
+
+(defparameter *timeline-url* "https://api.twitter.com/1.1/statuses/user_timeline.json")
+
+(defun load-twits (since-id)
+  (loop
+     for max-id = nil then (1- (aget "id" (car (last data))))
+     for data = (yason:parse
+                 (flexi-streams:octets-to-string
+                  (cl-oauth:access-protected-resource
+                   (format nil "~A?~A"
+                           *timeline-url*
+                           (cl-oauth::alist->query-string
+                            (remove-if
+                             (complement #'cdr)
+                             (list
+                              (cons "count" 200)
+                              (cons "trim_user" 1)
+                              (cons "since_id" since-id)
+                              (cons "max_id" max-id)))
+                            :include-leading-ampersand nil))
+                   *access-token*))
+                 :object-as :alist)
+     while data
+     append (mapcar #'make-twitter-doc data)))
+
+(defun on-cron ()
+  (let* ((last-twit (first (docs (db.sort "events" ($ "type" "twitter") :field "ts"
+                                          :asc nil :limit 1))))
+         (since-id (if last-twit (get-element "twitter.id" last-twit) 1))
+         (twits (load-twits since-id)))
+    (format t "Got ~A twits from ~A~%" (length twits) since-id)
+    (save-events twits)))

+ 7 - 2
src/utils.lisp

@@ -11,7 +11,8 @@
    #:aget
    #:aget
    #:doc->plist
    #:doc->plist
    #:load-chrome-cookie-jar
    #:load-chrome-cookie-jar
-   #:today))
+   #:today
+   #:save-events))
 (in-package :timeliner.utils)
 (in-package :timeliner.utils)
 
 
 (defun ts->ms (ts)
 (defun ts->ms (ts)
@@ -29,6 +30,10 @@
     (set :sec 0)
     (set :sec 0)
     (set :nsec 0)))
     (set :nsec 0)))
 
 
+(defun save-events (events)
+  (dolist (e events)
+    (db.insert "events" e)))
+
 (defgeneric $between (a from to)
 (defgeneric $between (a from to)
   (:documentation "cl-mongo between query"))
   (:documentation "cl-mongo between query"))
 
 
@@ -48,7 +53,7 @@
            with))
            with))
 
 
 (defmacro aget (key alist)
 (defmacro aget (key alist)
-  `(cdr (assoc ,key ,alist :test #'string=)))
+  `(cdr (assoc ,key ,alist :test #'equal)))
 
 
 (defvar *decode-doc-to* :plist "Document object type")
 (defvar *decode-doc-to* :plist "Document object type")
 (defgeneric decode-element (element)
 (defgeneric decode-element (element)

+ 7 - 3
src/web.lisp

@@ -15,13 +15,16 @@
 (defvar *run-cron* t "Controls if starting a web should run cron tasks")
 (defvar *run-cron* t "Controls if starting a web should run cron tasks")
 (defvar *crons* (list
 (defvar *crons* (list
                  (list #'timeliner.locations:on-cron '(:minute 0 :hour *))
                  (list #'timeliner.locations:on-cron '(:minute 0 :hour *))
-                 (list #'timeliner.financisto:on-cron '(:minute 0 :hour 7))
-                 (list #'timeliner.foursquare:on-cron '(:minute 5 :hour *)))
+                 (list #'timeliner.twitter:on-cron '(:minute 1 :hour *))
+                 (list #'timeliner.foursquare:on-cron '(:minute 5 :hour *))
+                 (list #'timeliner.financisto:on-cron '(:minute 0 :hour 7)))
   "List of cron functions with their schedules")
   "List of cron functions with their schedules")
 (defvar *cron-timers* nil)
 (defvar *cron-timers* nil)
 
 
 (defmethod restas:initialize-module-instance :before ((module (eql #.*package*)) context)
 (defmethod restas:initialize-module-instance :before ((module (eql #.*package*)) context)
   (restas:with-context context
   (restas:with-context context
+    (alexandria:when-let (file (probe-file "config.lisp"))
+      (load file))
     (cl-mongo:mongo :host "10.8.0.6")
     (cl-mongo:mongo :host "10.8.0.6")
     (cl-mongo:db.use "timeline")
     (cl-mongo:db.use "timeline")
     (when *run-cron*
     (when *run-cron*
@@ -349,7 +352,8 @@
                             (case (! this model (get :type))
                             (case (! this model (get :type))
                               (:place "glyphicon glyphicon-map-marker")
                               (:place "glyphicon glyphicon-map-marker")
                               (:finance "glyphicon glyphicon-usd")
                               (:finance "glyphicon glyphicon-usd")
-                              (:checkin "glyphicon glyphicon-ok-circle")))
+                              (:checkin "glyphicon glyphicon-ok-circle")
+                              (:twitter "glyphicon glyphicon-pencil")))
                render (lambda ()
                render (lambda ()
                         (! this $el (attr (create
                         (! this $el (attr (create
                                            :href "#"
                                            :href "#"

+ 6 - 2
timeliner.asd

@@ -48,17 +48,21 @@ THE SOFTWARE.
                :css-lite
                :css-lite
                :sqlite
                :sqlite
                :gzip-stream
                :gzip-stream
-               :clon)
+               :clon
+               :cl-oauth
+               :cl-date-time-parser)
   :components ((:module "src"
   :components ((:module "src"
                 :components
                 :components
                 ((:file "utils")
                 ((:file "utils")
                  (:file "locations" :depends-on ("utils"))
                  (:file "locations" :depends-on ("utils"))
                  (:file "financisto" :depends-on ("utils"))
                  (:file "financisto" :depends-on ("utils"))
                  (:file "foursquare" :depends-on ("utils"))
                  (:file "foursquare" :depends-on ("utils"))
+                 (:file "twitter" :depends-on ("utils"))
                  (:file "web" :depends-on ("utils"
                  (:file "web" :depends-on ("utils"
                                            "locations"
                                            "locations"
                                            "financisto"
                                            "financisto"
-                                           "foursquare")))))
+                                           "foursquare"
+                                           "twitter")))))
   :description "Personal Timeline"
   :description "Personal Timeline"
   :long-description
   :long-description
   #.(with-open-file (stream (merge-pathnames
   #.(with-open-file (stream (merge-pathnames