joggle 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,96 @@
1
+ require 'digest/md5'
2
+
3
+ module Joggle
4
+ module Store
5
+ module PStore
6
+ #
7
+ # Mixin that adds user store methods for PStore backend.
8
+ #
9
+ # Note: You're probably looking for Joggle::Store::PStore::All
10
+ #
11
+ module User
12
+ #
13
+ # Add user to store.
14
+ #
15
+ def add_user(key, row)
16
+ key = user_store_key(key)
17
+
18
+ @store.transaction do |s|
19
+ s[key] = row
20
+ end
21
+ end
22
+
23
+ #
24
+ # Update user's store entry.
25
+ #
26
+ def update_user(key, row)
27
+ key = user_store_key(key)
28
+
29
+ @store.transaction do |s|
30
+ row.each { |k, v| s[key][k] = v }
31
+ end
32
+ end
33
+
34
+ #
35
+ # Get information about given user.
36
+ #
37
+ def get_user(key)
38
+ key = user_store_key(key)
39
+
40
+ @store.transaction(true) do |s|
41
+ s[key]
42
+ end
43
+ end
44
+
45
+ #
46
+ # Is the given user ignored?
47
+ #
48
+ def ignored?(key)
49
+ get_user(key)['ignored']
50
+ end
51
+
52
+ #
53
+ # Does the given user exist?
54
+ #
55
+ def has_user?(key)
56
+ key = user_store_key(key)
57
+
58
+ @store.transaction(true) do |s|
59
+ s.root?(key)
60
+ end
61
+ end
62
+
63
+ #
64
+ # Iterate over all users in store.
65
+ #
66
+ def each_user(&block)
67
+ users = @store.transaction(true) do |s|
68
+ s.roots.inject([]) do |r, key|
69
+ (key =~ /^user-/) ? r << s[key] : r
70
+ end
71
+ end
72
+
73
+ users.each(&block)
74
+ end
75
+
76
+ #
77
+ # Delete given user from store.
78
+ #
79
+ def delete_user(key)
80
+ key = user_store_key(key)
81
+
82
+ @store.transaction do |s|
83
+ s.delete(key)
84
+ end
85
+ end
86
+
87
+ #
88
+ # Map user key to PStore root key.
89
+ #
90
+ def user_store_key(key)
91
+ 'user-' << Digest::MD5.hexdigest(key.gsub(/\/.*$/, ''))
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,186 @@
1
+ require 'net/http'
2
+ require 'time'
3
+ require 'joggle/pablotron/observable'
4
+
5
+ module Joggle
6
+ module Twitter
7
+ #
8
+ # Twitter engine object.
9
+ #
10
+ class Engine
11
+ include Joggle::Pablotron::Observable
12
+
13
+ DEFAULTS = {
14
+ # update interval, in minutes
15
+ 'twitter.engine.update_interval' => 5,
16
+ }
17
+
18
+ #
19
+ # Create new twitter engine.
20
+ #
21
+ def initialize(user_store, fetcher, opt = nil)
22
+ @opt = DEFAULTS.merge(opt || {})
23
+ @store = user_store
24
+ @fetcher = fetcher
25
+ end
26
+
27
+ #
28
+ # Is the given Jabber user ignored?
29
+ #
30
+ def ignored?(who)
31
+ if rec = @store.get_user(who)
32
+ rec['ignored']
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ #
39
+ # Is the given Jabber registered?
40
+ #
41
+ def registered?(who)
42
+ @store.has_user?(who)
43
+ end
44
+
45
+ #
46
+ # Bind the given Jabber user to the given Twitter username and
47
+ # password.
48
+ #
49
+ def register(who, user, pass)
50
+ store = @store
51
+
52
+ stoppable_action('twitter_engine_register_user', who, user, pass) do
53
+ store.add_user(who, {
54
+ 'who' => who.gsub(/\/.*$/, ''),
55
+ 'user' => user,
56
+ 'pass' => pass,
57
+ 'updated_at' => 0,
58
+ 'last_id' => 0,
59
+ 'ignored' => false,
60
+ 'sleep_bgn' => 0, # sleep start time, in hours
61
+ 'sleep_end' => 0, # sleep end time, in hours
62
+ })
63
+ end
64
+ end
65
+
66
+ #
67
+ # Forget registration for the given Jabber user.
68
+ #
69
+ def unregister(who)
70
+ store = @store
71
+
72
+ stoppable_action('twitter_engine_unregister_user', who) do
73
+ store.delete_user(who)
74
+ end
75
+ end
76
+
77
+ #
78
+ # Send a tweet as the given Jabber user.
79
+ #
80
+ def tweet(who, msg)
81
+ ret, store, fetcher = nil, @store, @fetcher
82
+
83
+ stoppable_action('twitter_engine_tweet', who, msg) do
84
+ if user = store.get_user(who)
85
+ ret = fetcher.tweet(user, msg)
86
+ end
87
+ end
88
+
89
+ # return result
90
+ ret
91
+ end
92
+
93
+ #
94
+ # List recent tweets for the given user.
95
+ #
96
+ def list(who, &block)
97
+ store, fetcher = @store, @fetcher
98
+
99
+ stoppable_action('twitter_engine_list', who) do
100
+ if user = store.get_user(who)
101
+ fetcher.get(user, true) do |id, who, time, msg|
102
+ block.call(id, who, time, msg)
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ #
109
+ # Update all users.
110
+ #
111
+ def update(&block)
112
+ store, updates = @store, []
113
+
114
+ # make list of updates
115
+ @store.each_user do |user|
116
+ updates << user if needs_update?(user)
117
+ end
118
+
119
+ # iterate over updates and do each one
120
+ updates.each do |user|
121
+ stoppable_action('twitter_engine_update', user) do
122
+ update_user(user) do |id, time, src, msg|
123
+ block.call(user['who'], id, time, src, msg)
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ #
132
+ # Update specific Jabber user.
133
+ #
134
+ def update_user(user, &block)
135
+ last_id = nil
136
+
137
+ @fetcher.get(user) do |id, who, time, msg|
138
+ last_id = id
139
+ block.call(id, who, time, msg)
140
+ end
141
+
142
+ @store.update_user(user['who'], {
143
+ 'last_id' => last_id,
144
+ 'updated_at' => Time.now.to_i,
145
+ })
146
+ end
147
+
148
+ #
149
+ # Does the given Jabber user need an update?
150
+ #
151
+ def needs_update?(user)
152
+ now, since = Time.now, Time.now - @opt['twitter.engine.update_interval']
153
+
154
+ # check update interval
155
+ if user['updated_at'].to_i < since.to_i
156
+ # check sleep interval
157
+ !is_sleeping?(user, now)
158
+ else
159
+ # updated within update_interval
160
+ false
161
+ end
162
+ end
163
+
164
+ #
165
+ # Is the given Jabber user asleep?
166
+ #
167
+ def is_sleeping?(user, time = Time.now)
168
+ # build sleep range
169
+ sleep = %w{bgn end}.map { |s| user["sleep_#{s}"].to_i }
170
+
171
+ # compare user sleep time
172
+ time.hour >= sleep.first && time.hour <= sleep.last
173
+ end
174
+
175
+ #
176
+ # Fire a stoppable event.
177
+ #
178
+ def stoppable_action(key, *args, &block)
179
+ if fire("before_#{key}", *args)
180
+ block.call
181
+ fire(key, *args)
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,123 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'openssl'
4
+ require 'json'
5
+ require 'joggle/pablotron/cache'
6
+
7
+ module Joggle
8
+ module Twitter
9
+ #
10
+ # Handler for Twitter HTTP requests.
11
+ #
12
+ class Fetcher
13
+ DEFAULTS = {
14
+ 'twitter.fetcher.url.timeline' => 'https://twitter.com/statuses/friends_timeline.json',
15
+ 'twitter.fetcher.url.tweet' => 'https://twitter.com/statuses/update.json',
16
+ }
17
+
18
+ #
19
+ # Create a new Twitter::Fetcher object.
20
+ #
21
+ def initialize(message_store, cache, opt = {})
22
+ @opt = DEFAULTS.merge(opt || {})
23
+ @store = message_store
24
+ @cache = cache
25
+ end
26
+
27
+ #
28
+ # Get Twitter updates for given user and pass them to the
29
+ # specified block.
30
+ #
31
+ def get(user, show_all = false, &block)
32
+ url, opt = url_for(user, 'timeline'), opt_for(user)
33
+
34
+ if data = @cache.get(url, opt)
35
+ JSON.parse(data).reverse.each do |row|
36
+ cached = @store.has_message?(row['id'])
37
+
38
+ if show_all || !cached
39
+ # cache message
40
+ @store.add_message(row['id'], row)
41
+
42
+ # send to parent
43
+ block.call(
44
+ row['id'],
45
+ Time.parse(row['created_at']),
46
+ row['user']['screen_name'],
47
+ row['text']
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ #
55
+ # Send a tweet. Use Joggle::Engine#tweet instead.
56
+ #
57
+ def tweet(user, msg)
58
+ # build URI and headers (opt)
59
+ url, opt = url_for(user, 'tweet'), opt_for(user)
60
+ uri = URI.parse(url)
61
+ ret = nil
62
+
63
+ # build post data
64
+ data = {
65
+ 'status' => msg,
66
+ }.map { |a|
67
+ a.map { |v| CGI.escape(v) }.join('=')
68
+ }.join('&')
69
+
70
+ # FIXME: add user-agent to headers
71
+ req = Net::HTTP.new(uri.host, uri.port)
72
+ req.use_ssl = (uri.scheme == 'https')
73
+
74
+ # start http request
75
+ req.start do |http|
76
+ # post request
77
+ r = http.post(uri.path, data, opt)
78
+
79
+ # check response
80
+ case r
81
+ when Net::HTTPSuccess
82
+ ret = JSON.parse(r.body)
83
+
84
+ # File.open('/tmp/foo.log', 'a') do |fh|
85
+ # fh.puts "r.body = #{r.body}"
86
+ # end
87
+
88
+ # check result
89
+ if ret && ret.key?('id')
90
+ @store.add_message(ret['id'], ret)
91
+ else
92
+ throw "got weird response from twitter"
93
+ end
94
+ else
95
+ throw r
96
+ end
97
+ end
98
+
99
+ # return result
100
+ ret
101
+ end
102
+
103
+ private
104
+
105
+ def url_for(user, key)
106
+ args = nil
107
+
108
+ if false && key == 'timeline'
109
+ if user['last_id'] && user['last_id'] > 0
110
+ args = { 'since_id' => user['last_id'] }
111
+ end
112
+ end
113
+
114
+ Joggle::Pablotron::Cache.urlify(@opt['twitter.fetcher.url.' + key], args)
115
+ end
116
+
117
+ def opt_for(user)
118
+ str = ["#{user['user']}:#{user['pass']}"].pack('m').strip
119
+ { 'Authorization' => 'Basic ' + str }
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,6 @@
1
+ module Joggle
2
+ #
3
+ # Joggle release version.
4
+ #
5
+ VERSION = '0.1.0'
6
+ end