|
| 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