|  | 
|  | 1 | +;   Copyright (c) Rich Hickey. All rights reserved. | 
|  | 2 | +;   The use and distribution terms for this software are covered by the | 
|  | 3 | +;   Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) | 
|  | 4 | +;   which can be found in the file epl-v10.html at the root of this distribution. | 
|  | 5 | +;   By using this software in any fashion, you are agreeing to be bound by | 
|  | 6 | +;   the terms of this license. | 
|  | 7 | +;   You must not remove this notice, or any other, from this software. | 
|  | 8 | + | 
| 1 | 9 | (ns twitterbuzz.core | 
| 2 | 10 |   (:require [goog.net.Jsonp :as jsonp] | 
| 3 | 11 |             [goog.Timer :as timer] | 
| 4 | 12 |             [goog.events :as events] | 
| 5 | 13 |             [goog.dom :as dom])) | 
| 6 | 14 | 
 | 
|  | 15 | +(def state (atom {:max-id 1 | 
|  | 16 | +                  :graph {} | 
|  | 17 | +                  :new-tweets-listeners [] | 
|  | 18 | +                  :graph-update-listeners [] | 
|  | 19 | +                  :tweet-count 0})) | 
|  | 20 | + | 
|  | 21 | +(defn add-listener [k f] | 
|  | 22 | +  (swap! state (fn [old] (assoc old k (conj (k old) f))))) | 
|  | 23 | + | 
| 7 | 24 | (def twitter-uri (goog.Uri. "http://twitter.com/search.json")) | 
| 8 | 25 | 
 | 
| 9 | 26 | (defn retrieve [payload callback] | 
| 10 | 27 |   (.send (goog.net.Jsonp. twitter-uri) | 
| 11 | 28 |          payload | 
| 12 | 29 |          callback)) | 
| 13 | 30 | 
 | 
| 14 |  | -(def state (atom {:max-id 1 :functions [] :tweet-count 0})) | 
| 15 |  | - | 
| 16 | 31 | (defn send-tweets [fns tweets] | 
| 17 | 32 |   (when (seq fns) | 
| 18 | 33 |     (do ((first fns) tweets) | 
| 19 | 34 |         (recur (rest fns) tweets)))) | 
| 20 | 35 | 
 | 
|  | 36 | +(defn parse-mentions [tweet] | 
|  | 37 | +  (map second (re-seq (re-pattern "@(\\w*)") (:text tweet)))) | 
|  | 38 | + | 
|  | 39 | +(defn add-mentions | 
|  | 40 | +  "Add the user to the mentions map for each user she mentions." | 
|  | 41 | +  [graph user mentions] | 
|  | 42 | +  (reduce (fn [acc next-mention] | 
|  | 43 | +            (if-let [node (get graph next-mention)] | 
|  | 44 | +              (let [mentions-map (get node :mentions {})] | 
|  | 45 | +                (assoc-in acc [next-mention :mentions user] (inc (get mentions-map user 0)))) | 
|  | 46 | +              graph)) | 
|  | 47 | +          graph | 
|  | 48 | +          mentions)) | 
|  | 49 | + | 
|  | 50 | +(defn update-graph [graph tweet-maps] | 
|  | 51 | +  (reduce (fn [acc tweet] | 
|  | 52 | +            (let [user (:from_user tweet) | 
|  | 53 | +                  mentions (parse-mentions tweet)] | 
|  | 54 | +              (-> (if-let [existing-node (get acc user)] | 
|  | 55 | +                    (assoc acc user | 
|  | 56 | +                           (assoc existing-node :last-tweet (:text tweet))) | 
|  | 57 | +                    (assoc acc user | 
|  | 58 | +                           {:image-url (:profile_image_url tweet) | 
|  | 59 | +                            :last-tweet (:text tweet) | 
|  | 60 | +                            :mentions {}})) | 
|  | 61 | +                  (add-mentions user mentions)))) | 
|  | 62 | +          graph | 
|  | 63 | +          (map #(select-keys % [:text :from_user :profile_image_url]) tweet-maps))) | 
|  | 64 | + | 
|  | 65 | +(defn num-mentions [user] | 
|  | 66 | +  (reduce + (vals (:mentions user)))) | 
|  | 67 | + | 
|  | 68 | +(defn update-state [old-state max-id tweets] | 
|  | 69 | +  (-> old-state | 
|  | 70 | +      (assoc :max-id max-id) | 
|  | 71 | +      (update-in [:tweet-count] #(+ % (count tweets))) | 
|  | 72 | +      (assoc :graph (update-graph (:graph old-state) tweets)))) | 
|  | 73 | + | 
| 21 | 74 | (defn my-callback [json] | 
| 22 | 75 |   (let [result-map (js->clj json :keywordize-keys true) | 
| 23 | 76 |         new-max (:max_id result-map) | 
| 24 | 77 |         old-max (:max-id @state) ;; the filter won't work if you inline this | 
| 25 | 78 |         tweets (filter #(> (:id %) old-max) | 
| 26 | 79 |                        (:results result-map))] | 
| 27 |  | -    (do  (swap! state (fn [old] (-> old | 
| 28 |  | -                                   (assoc :max-id new-max) | 
| 29 |  | -                                   (assoc :tweet-count (+ (:tweet-count old) (count tweets))) | 
| 30 |  | -                                   ;; this doesn't work | 
| 31 |  | -                                   #_(update-in :tweet-count #(+ % (count tweets)))))) | 
| 32 |  | -         (send-tweets (:functions @state) tweets)))) | 
|  | 80 | +    (do  (swap! state update-state new-max tweets) | 
|  | 81 | +         (send-tweets (:new-tweets-listeners @state) tweets) | 
|  | 82 | +         (send-tweets (:graph-update-listeners @state) (:graph @state))))) | 
| 33 | 83 | 
 | 
| 34 | 84 | (defn register | 
| 35 |  | -  "Register fn to be called with new tweets." | 
| 36 |  | -  [f] | 
| 37 |  | -  (swap! state (fn [old] (assoc old :functions (conj (:functions old) f))))) | 
|  | 85 | +  "Register a function to be called when new data arrives specifying | 
|  | 86 | +  the event to receive updates for. Events can be :new-tweets or :graph-update." | 
|  | 87 | +  [event f] | 
|  | 88 | +  (cond (= event :new-tweets) (add-listener :new-tweets-listeners f) | 
|  | 89 | +        (= event :graph-update) (add-listener :graph-update-listeners f))) | 
| 38 | 90 | 
 | 
| 39 | 91 | (defn search-tag | 
| 40 | 92 |   "Get the current tag value from the page." | 
|  | 
| 54 | 106 |         (events/listen timer goog.Timer/TICK listener)))) | 
| 55 | 107 | 
 | 
| 56 | 108 | (poll) | 
|  | 109 | + | 
|  | 110 | +(comment | 
|  | 111 | + | 
|  | 112 | +  (parse-mentions {:text "What's up @sue and @larry"}) | 
|  | 113 | +   | 
|  | 114 | +  (add-mentions {} "jim" ["sue"]) | 
|  | 115 | +  (add-mentions {"sue" {}} "jim" ["sue"]) | 
|  | 116 | +   | 
|  | 117 | +  (def tweets [{:profile_image_url "url1" | 
|  | 118 | +                :from_user "jim" | 
|  | 119 | +                :text "I like cookies!"} | 
|  | 120 | +               {:profile_image_url "url2" | 
|  | 121 | +                :from_user "sue" | 
|  | 122 | +                :text "Me to @jim."} | 
|  | 123 | +               {:profile_image_url "url3" | 
|  | 124 | +                :from_user "bob" | 
|  | 125 | +                :text "You shouldn't eat so many cookies @sue"} | 
|  | 126 | +               {:profile_image_url "url4" | 
|  | 127 | +                :from_user "sam" | 
|  | 128 | +                :text "@bob that was a cruel thing to say to @sue."}]) | 
|  | 129 | +   | 
|  | 130 | +  (def graph (update-graph {} tweets)) | 
|  | 131 | +   | 
|  | 132 | +  (num-mentions (get graph "sue")) | 
|  | 133 | +  (num-mentions (get graph "bob")) | 
|  | 134 | +  (num-mentions (get graph "sam")) | 
|  | 135 | +   | 
|  | 136 | +  (take 1 (reverse (sort-by #(num-mentions (second %)) (seq graph)))) | 
|  | 137 | +   | 
|  | 138 | +  ) | 
|  | 139 | + | 
0 commit comments