tjplurker 1.0.4 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/tjplurker.rb CHANGED
@@ -1,351 +1 @@
1
- # :title:TJPlurker API Documentation
2
- require 'logger'
3
- require 'json'
4
- require 'net/http'
5
-
6
- $tjpLog = Logger.new(STDOUT)
7
- $tjpLog.level = Logger::INFO
8
- $tjpLog.formatter = proc{ |severity, datetime, progname, msg|
9
- "#{severity} [#{datetime}] in #{progname} -- #{msg}\n"
10
- }
11
- # It's a wrapper of Plurk API.
12
- # Note that every methods in this class returns a JSON object
13
- # which was converted from Plurk server's response.
14
- # All mehtods will retry again when request timeout occurs.
15
- class TJPlurker
16
- # Plurk user ID.
17
- attr_reader :user
18
-
19
- # If +auto_login+ sets +true+, it will autologin while constructing.
20
- def initialize api_key, user, passwd, auto_login=true
21
- @user, @passwd, @api_key = user, passwd, api_key
22
- @cookie = nil
23
- @user_channel = nil
24
- @id_buffer = nil
25
- login if auto_login
26
- end
27
-
28
- # If no_data sets true, the successful login message was simplified as follow:
29
- #
30
- # {"success_text": "ok"}
31
- #
32
- # Otherwise, more details of user's profile were retured.
33
- def login no_data=true
34
- method = "/API/Users/login"
35
- attr = {:username=>@user, :password=>@passwd, :api_key=>@api_key}
36
- attr[:no_data] = '1' if no_data
37
- api(method, attr)
38
- end
39
-
40
- def plurk_add content, qualifier=':', limited_to=nil, no_comments=nil, lang='tr_ch'
41
- method = "/API/Timeline/plurkAdd"
42
- attr = {:api_key=>@api_key, :content=>content, :qualifier=>qualifier,:limited_to=>limited_to, :no_comments=>no_comments, :lang=>lang}
43
- api(method, attr)
44
- end
45
-
46
- def response_add plurk_id, content, qualifier=':'
47
- method = "/API/Responses/responseAdd"
48
- attr = {:api_key=>@api_key, :plurk_id=>plurk_id, :content=>content, :qualifier=>qualifier}
49
- api(method, attr)
50
- end
51
-
52
- def become_fan fan_id
53
- method = "/API/FriendsFans/becomeFan"
54
- attr = {:api_key=>@api_key, :fan_id=>fan_id}
55
- api(method, attr)
56
- end
57
-
58
- def add_all_as_friends
59
- method = "/API/Alerts/addAllAsFriends"
60
- attr = {:api_key=>@api_key}
61
- api(method, attr)
62
- end
63
-
64
- # This method is provided by TJPlurker.
65
- # +max+:: The maximum number of your request
66
- # +return+:: An array contains friends
67
- def get_friend_list user_id, max=nil
68
- list = []
69
- offset = 0;
70
- loop{
71
- set = get_friends_by_offset(user_id, 100, offset)
72
- size = set.size
73
- puts size
74
- break if size == 0
75
- list |= set
76
- break if max && list.size>max
77
- offset += size
78
- }
79
- return list
80
- end
81
-
82
- # This maximum limit is set 100 officially, this method is not that useful.
83
- # We suggest you use get_friend_list instead of get_friends_by_offset.
84
- def get_friends_by_offset user_id, limit=100, offset='0'
85
- method = "/API/FriendsFans/getFriendsByOffset"
86
- attr = {:api_key=>@api_key, :user_id=>user_id, :offset=>offset, :limit=>limit}
87
- api(method, attr)
88
- end
89
-
90
- def get_public_profile user_id
91
- method = "/API/Profile/getPublicProfile"
92
- attr = {:api_key=>@api_key, :user_id=>user_id}
93
- api(method, attr)
94
- end
95
-
96
- # Warning:: It has bugs in offecial Plurk API.
97
- def get_plurks limit=nil, offset=nil
98
- method = "/API/Polling/getPlurks"
99
- attr = {:api_key=>@api_key, :limit=>limit, :offset=>offset}
100
- api(method, attr)
101
- end
102
-
103
- def get_user_channel
104
- method = "/API/Realtime/getUserChannel"
105
- attr = {:api_key=>@api_key}
106
- api(method, attr)
107
- end
108
-
109
- def comet_channel
110
- #Assign new channel if @user_channel is nil
111
- @user_channel ||= get_user_channel
112
- begin
113
- res = Net::HTTP.get_response(URI.parse(@user_channel['comet_server']))
114
- json = JSON.parse(res.body.sub!(/CometChannel\.scriptCallback\((.*)\);/){|match| $1})
115
- rescue Timeout::Error => ex
116
- $tjpLog.warn(self.class){"Request timeout, retry."}
117
- retry
118
- rescue Errno::ECONNRESET => ex
119
- $tjpLog.warn(self.class){"Connection reset, retry."}
120
- retry
121
- rescue => ex
122
- $tjpLog.fatal(self.class){"Unknown error, skip."}
123
- puts ex.class, ex.message, ex.backtrace
124
- return
125
- end
126
- #Update the offset
127
- @user_channel['comet_server'].sub!(/[\d]+$/, json["new_offset"].to_s)
128
- if json['data']
129
- json['data'].each{|member|
130
- notification = NotificationData.new(member)
131
- return if notification.id == @id_buffer
132
- $tjpLog.info(self.class){"Notification: #{notification.user_id}: \"#{notification.content}\""}
133
- @id_buffer = notification.id
134
- yield(notification)
135
- }
136
- end
137
- json
138
- end
139
-
140
- private
141
- def api method, attr
142
- #Build POST Request
143
- req = Net::HTTP::Post.new(method)
144
- req.set_form_data(attr)
145
- req["Cookie"] = @cookie
146
- #Build GET Request
147
- #path = method + "?" + URI.encode_www_form(attr)
148
- #req2 = Net::HTTP::Get.new(path)
149
- #req2["Cookie"] = @cookie
150
-
151
- #Build HTTP connection
152
- http = Net::HTTP.new('www.plurk.com', 80)
153
- begin
154
- resp = http.start{
155
- $tjpLog.debug(self.class){"Request: #{method}, #{attr}"}
156
- http.request(req)
157
- }
158
- @cookie ||= resp['set-cookie']
159
- json = JSON.parse(resp.body)
160
- $tjpLog.debug(self.class){"Response: #{json}"}
161
- $tjpLog.error(self.class){json["error_text"]} if json.is_a?(Hash) && json["error_text"]
162
- rescue Timeout::Error => ex
163
- $tjpLog.warn(self.class){"Request timeout, retrying."}
164
- retry
165
- rescue Errno::ECONNRESET => ex
166
- $tjpLog.warn(self.class){"Connection reset, retry."}
167
- retry
168
- rescue JSON::ParserError => ex
169
- $tjpLog.error(self.class){"JSON parse error, request failed."}
170
- rescue => ex
171
- $tjpLog.fatal(self.class){"Unknown error, skip."}
172
- puts ex.class, ex.message, ex.backtrace
173
- return
174
- end
175
- return json
176
- end
177
-
178
- # Format the json from the comet server. See TJPlurker#comet_channel
179
- class NotificationData
180
- # Can be +new_plurk+ or +new_response+.
181
- attr_reader :type
182
- attr_reader :response_count
183
- attr_reader :plurk_id
184
- attr_reader :lang
185
- attr_reader :user_id
186
- attr_reader :qualifier
187
- attr_reader :content
188
- # Notification ID.
189
- attr_reader :id
190
- # Time of the listened post.
191
- attr_reader :posted
192
- attr_reader :owner_id
193
- # The original JSON object from server response.
194
- attr_reader :origin
195
-
196
- def initialize member
197
- @type = member['type']
198
- @response_count = member['response_count']
199
- @plurk_id = member['plurk_id']
200
-
201
- @lang = @type=="new_plurk" ? member['lang'] : member['response']['lang']
202
- @user_id = @type=="new_plurk" ? member['user_id'] : member['response']['user_id']
203
- @qualifier = @type=="new_plurk" ? member['qualifier'] : member['response']['qualifier']
204
- @content = @type=="new_plurk" ? member['content'] : member['response']['content']
205
- @id = @type=="new_plurk" ? member['id'] : member['response']['id']
206
- @posted = @type=="new_plurk" ? member['posted'] : member['response']['posted']
207
-
208
- @owner_id = @type=="new_plurk" ? member['owner_id'] : member['plurk']['owner_id']
209
-
210
- @origin = member
211
- end
212
- end
213
-
214
- class Robot
215
- def initialize *params
216
- case params.size
217
- when 1
218
- @tjplurker = params[0]
219
- when 3
220
- api_key, user, passwd = params
221
- @tjplurker = TJPlurker.new(api_key, user, passwd)
222
- when 4
223
- api_key, user, passwd, auto_login = params
224
- @tjplurker = TJPlurker.new(api_key, user, passwd, auto_login)
225
- end
226
- @services = []
227
- end
228
-
229
- # Starting a loop and invokes services when a notification was listened.
230
- def start
231
- $tjpLog.info(self.class){"Start robot."}
232
- Thread.new{
233
- loop{
234
- @tjplurker.add_all_as_friends
235
- sleep(60)
236
- }
237
- }
238
- loop{
239
- @tjplurker.comet_channel{|data|
240
- @services.each{|service|
241
- begin
242
- service.serve(@tjplurker, data)
243
- rescue => ex
244
- $tjpLog.error(self.class){"An error occured in #{service.class}, class: #{ex.class}, message: #{ex.message}, backtrace: #{ex.backtrace}"}
245
- end
246
- }
247
- }
248
- }
249
- end
250
-
251
- # +service+:: Can be instance TJPlurker::Service or class TJPlurker::Service or it's descendant classes.
252
- def add_service service
253
- if service.is_a? Service
254
- @services << service
255
- $tjpLog.info(self.class){"Add service: #{service.name}"}
256
- elsif service <= Service
257
- tmp = service.new
258
- @services << tmp
259
- $tjpLog.info(self.class){"Add service: #{tmp.name}"}
260
- else
261
- $tjpLog.warn(self.class){"Unrecognized service, ignored."}
262
- end
263
- end
264
- end
265
-
266
- class Service
267
- attr_reader :name
268
- def initialize name=self.class.to_s, &proc
269
- @name = name
270
- if block_given?
271
- @serve_proc = proc
272
- end
273
- end
274
-
275
- # Invoke when listened a new plurk.
276
- def decide tjplurker, data
277
- true
278
- end
279
-
280
- def action tjplurker, data
281
- if @serve_proc
282
- @serve_proc[tjplurker, data]
283
- else
284
- p data
285
- end
286
- end
287
-
288
- def serve tjplurker, data
289
- action(tjplurker, data) if decide(tjplurker, data)
290
- end
291
- end
292
-
293
- class ThreadService < Service
294
- # +lasting+:: How long in second will a thread lives. If one plurk just be responsed, then it will live another +lasting+ time.
295
- # +refresh+:: Interval that checks if thread should terminate.
296
- def initialize name=self.class.to_s, lasting=60, refresh=1, &proc
297
- super name
298
- @lasting = lasting
299
- @refresh = refresh
300
- @plurk_id_hash = Hash.new
301
- @mutex = Mutex.new
302
- end
303
-
304
- # Invoke after thread termination.
305
- # You can use this to tell users this service just terminate.
306
- def final tjplurker, data
307
-
308
- end
309
-
310
- def serve tjplurker, data
311
- if data.type=='new_plurk' && decide(tjplurker, data)
312
- @plurk_id_hash[data.plurk_id] = Thread.new(data.plurk_id){|plurk_id|
313
- $tjpLog.debug(self.class){"Create thread in plurk ##{plurk_id}"}
314
- Thread.current[:last_time] = Time.now.to_i
315
- loop{
316
- sleep(@refresh)
317
- $tjpLog.debug(self.class){"Plurk ##{plurk_id} remain #{Thread.current[:last_time]+@lasting-Time.now.to_i} seconds."}
318
- @mutex.lock
319
- if Time.now.to_i - Thread.current[:last_time] > @lasting
320
- @mutex.unlock
321
- break
322
- end
323
- @mutex.unlock
324
- }
325
- $tjpLog.debug(self.class){"Terminate thread in plurk ##{plurk_id}"}
326
- @mutex.lock
327
- @plurk_id_hash.delete(plurk_id)
328
- @mutex.unlock
329
- final(tjplurker, data)
330
- }
331
- else
332
- @mutex.lock
333
- if data.type=='new_response' && @plurk_id_hash.has_key?(data.plurk_id)
334
- @plurk_id_hash[data.plurk_id][:last_time] = Time.now.to_i
335
- action(tjplurker, data)
336
- end
337
- @mutex.unlock
338
- end
339
- end
340
- end
341
- end
342
-
343
- if __FILE__ == $0
344
- pk = Plurker.new("you api key", "your id", "your password")
345
- puts pk.login
346
- loop{
347
- pk.comet_channel{|date|
348
- puts data
349
- }
350
- }
351
- end
1
+ require 'tjplurker/core'
@@ -0,0 +1,296 @@
1
+ # :title:TJPlurker API Documentation
2
+ # encoding: utf-8
3
+ require 'oauth'
4
+ require 'json'
5
+ require 'logger'
6
+ require 'open-uri'
7
+
8
+ $tjpLog = Logger.new(STDOUT)
9
+ $tjpLog.level = Logger::INFO
10
+ $tjpLog.formatter = proc{ |severity, datetime, progname, msg|
11
+ "#{severity} [#{datetime}] in #{progname} -- #{msg}\n"
12
+ }
13
+
14
+ module TJP
15
+ # It's core class of TJPlurker based on {Plurk API 2.0}[http://www.plurk.com/API].
16
+ # Note that every API wrapped methods in this class returns a JSON object
17
+ # All mehtods will retry again when request timeout occurs.
18
+ class TJPlurker
19
+ # It will automatically authorize if +access_token+ and +access_token_secret+ are both given.
20
+ # If you do not have access token and secret, leave the parameters blank,
21
+ # and you will be asked to enter verification code to get access token and secret.
22
+ # key:: consumer key
23
+ # secret:: consumer secret
24
+ # return:: OAuth::AccessToken
25
+ def initialize(key, secret, token=nil, token_secret=nil)
26
+ @key, @secret, @token, @token_secret = key, secret, token, token_secret
27
+ @consumer = OAuth::Consumer.new(@key, @secret, {
28
+ :site => 'http://www.plurk.com',
29
+ :scheme => :header,
30
+ :http_method => :post,
31
+ :request_token_path => '/OAuth/request_token',
32
+ :access_token_path => '/OAuth/access_token',
33
+ :authorize_path => '/OAuth/authorize'
34
+ })
35
+ end
36
+
37
+ # return:: authorize URL
38
+ def get_authorize_url
39
+ @request_token = @consumer.get_request_token
40
+ return @request_token.authorize_url
41
+ end
42
+
43
+ # Change or get access token and secret.
44
+ # If parameters are not both given, user will be asked to enter verfication code,
45
+ # otherwise, replace the access token and secret.
46
+ # return:: access token and access token secret in Array
47
+ def authorize(access_token=nil, access_token_secret=nil)
48
+ if access_token && access_token_secret
49
+ #@access_token = OAuth::AccessToken.new(@consumer, access_token, access_token_secret)
50
+ @token, @token_secret = access_token, access_token_secret
51
+ else
52
+ puts "Authorize URL: #{get_authorize_url}"
53
+ print "Please Enter your verification code: "
54
+ verify = gets.chomp!
55
+ access_token = @request_token.get_access_token :oauth_verifier=>verify
56
+ @token, @token_secret = access_token.token, access_token.secret
57
+ end
58
+ $tjpLog.info(self.class){"Access Token: #{@token}, Access Token Secret: #{@token_secret}"}
59
+ return @token, @token_secret
60
+ end
61
+
62
+ # url:: plurk APP url in String
63
+ # body:: options in Hash
64
+ # return:: result in JSON
65
+ def api(url, body=nil, headers=nil)
66
+ # clone consumer for supporting thread
67
+ access_token = OAuth::AccessToken.new(@consumer.clone, @token, @token_secret)
68
+ resp = access_token.post(url, body, headers).body
69
+ json = JSON.parse(resp)
70
+ $tjpLog.debug(self.class){"Response: #{json}"}
71
+ $tjpLog.error(self.class){json["error_text"]} if json.is_a?(Hash) && json["error_text"]
72
+ return json
73
+ end
74
+
75
+
76
+ #:section: Offical API wrapping
77
+
78
+
79
+ # qualifier:: The Plurk's qualifier, must be in English. Can be following:
80
+ # loves, likes, shares, gives, hates, wants, has, will, asks, wishes, was,
81
+ # feels, thinks, says, is, :, freestyle, hopes, needs, wonders
82
+ # limited_to:: Limit the plurk only to some users (also known as private plurking).
83
+ # Should can be a JSON list of friend ids in String formatting or Ruby Array, e.g.
84
+ # \[3,4,66,34] or "\[3,4,66,34]" will only be plurked to these user ids.
85
+ # If it's [0] then the Plurk is privatley posted to the poster's friends.
86
+ # no_comments:: If set to 1 or "1", then responses are disabled for this plurk.
87
+ # If set to 2 or "2", then only friends can respond to this plurk.
88
+ # lang:: The plurk's language. Can be following:
89
+ # 'en': 'English', 'pt_BR': 'Português', 'cn': '中文 (中国)', 'ca': 'Català', 'el': 'Ελληνικά'
90
+ # 'dk': 'Dansk', 'de': 'Deutsch', 'es': 'Español', 'sv': 'Svenska', 'nb': 'Norsk bokmål'
91
+ # 'hi': 'Hindi', 'ro': 'Română', 'hr': 'Hrvatski', 'fr': 'Français', 'ru': 'Pусский'
92
+ # 'it': 'Italiano', 'ja': '日本語', 'he': 'עברית', 'hu': 'Magyar', 'ne': 'Nederlands', 'th': 'ไทย'
93
+ # 'ta_fp': 'Filipino', 'in': 'Bahasa Indonesia', 'pl': 'Polski', 'ar': 'العربية', 'fi': 'Finnish'
94
+ # 'tr_ch': '中文 (繁體中文)', 'tr': 'Türkçe', 'ga': 'Gaeilge', 'sk': 'Slovenský'
95
+ # 'uk': 'українська', 'fa': 'فارسی
96
+ def plurk_add content, qualifier=':', limited_to=nil, no_comments=nil, lang='tr_ch'
97
+ method = "/APP/Timeline/plurkAdd"
98
+ limited_to = limited_to.to_s if limited_to.is_a? Array
99
+ attr = {:content=>content, :qualifier=>qualifier, :limited_to=>limited_to, :no_comments=>no_comments, :lang=>lang}
100
+ api(method, attr)
101
+ end
102
+
103
+ # Adds a responses to +plurk_id+. Language is inherited from the plurk.
104
+ def response_add plurk_id, content, qualifier=':'
105
+ method = "/APP/Responses/responseAdd"
106
+ attr = {:plurk_id=>plurk_id, :content=>content, :qualifier=>qualifier}
107
+ api(method, attr)
108
+ end
109
+
110
+ # Accept all friendship requests as friends.
111
+ def add_all_as_friends
112
+ method = "/APP/Alerts/addAllAsFriends"
113
+ api(method)
114
+ end
115
+
116
+ # Become fan of +fan_id+. To stop being a fan of someone, use set_following
117
+ def become_fan fan_id
118
+ method = "/APP/FriendsFans/becomeFan"
119
+ attr = {:fan_id=>fan_id}
120
+ api(method, attr)
121
+ end
122
+
123
+ # Returns user_id's friend list in chucks of 100 friends at a time.
124
+ # limit:: The max number of friends to be returned.
125
+ # offset:: Can be Numeric or String, e.g. 10, "20", 30, "40" etc.
126
+ # return:: An Array
127
+ def get_friends_by_offset user_id, limit=100, offset=0
128
+ method = "/APP/FriendsFans/getFriendsByOffset"
129
+ attr = {:user_id=>user_id, :offset=>offset, :limit=>limit}
130
+ api(method, attr)
131
+ end
132
+
133
+ # Fetches public information such as a user's public plurks and basic information.
134
+ # Fetches also if the current user is following the user, are friends with or is a fan.
135
+ def get_public_profile user_id
136
+ method = "/APP/Profile/getPublicProfile"
137
+ attr = {:user_id=>user_id}
138
+ api(method, attr)
139
+ end
140
+
141
+ # Get plurks from user's timeline
142
+ # Warning:: It has bugs in offecial Plurk API.
143
+ # offset:: Return plurks newer than offset, formatted as 2009-6-20T21:55:34.
144
+ # limit:: The max number of plurks to be returned (default: 20)
145
+ # filter:: Can be only_user, only_responded, only_private or only_favorite
146
+ def get_plurks offset=nil, limit=nil, filter=nil
147
+ method = "/APP/Timeline/getPlurks"
148
+ attr = {:limit=>limit, :offset=>offset}
149
+ api(method, attr)
150
+ end
151
+
152
+ # Get instant notifications when there are new plurks and responses on a user's timeline.
153
+ # Generally you don't need call this because comet_channel will automatically do that for you.
154
+ # For details, see http://www.plurk.com/API#realtime
155
+ # return:: channel name and comet server in Hash
156
+ def get_user_channel
157
+ method = "/APP/Realtime/getUserChannel"
158
+ api(method)
159
+ end
160
+
161
+ # It will do GET requests to user's comet server to get new notification data.
162
+ # The request will sleep for about 50 seconds before returning a response if there is
163
+ # no new data added to your channel.
164
+ # It will automatically get comet server URL from /APP/Realtime/getUserChannel
165
+ # if there is no comet server yet, and renew +offset+ each time it was called.
166
+ # You don't need to worry about URL and +offset+.
167
+ # For details, see http://www.plurk.com/API#realtime
168
+ # return:: plurk notification in Hash
169
+ def comet_channel
170
+ #Assign new channel if @user_channel is nil
171
+ @user_channel ||= get_user_channel
172
+ begin
173
+ res = Net::HTTP.get_response(URI.parse(@user_channel['comet_server']))
174
+ json = JSON.parse(res.body.sub!(/CometChannel\.scriptCallback\((.*)\);/){|match| $1})
175
+ rescue Timeout::Error => ex
176
+ $tjpLog.warn(self.class){"Request timeout, retry."}
177
+ retry
178
+ rescue Errno::ECONNRESET => ex
179
+ $tjpLog.warn(self.class){"Connection reset, retry."}
180
+ retry
181
+ rescue => ex
182
+ $tjpLog.fatal(self.class){"Unknown error, skip."}
183
+ puts ex.class, ex.message, ex.backtrace
184
+ return
185
+ end
186
+ #Update the offset
187
+ @user_channel['comet_server'].sub!(/[\d]+$/, json["new_offset"].to_s)
188
+ if json['data']
189
+ json['data'].each{|member|
190
+ notification = NotificationData.new(member)
191
+ # Prevent duplicate notification
192
+ return if notification.id == @id_buffer
193
+ $tjpLog.info(self.class){"Notification: #{notification.user_id}: \"#{notification.content}\""}
194
+ @id_buffer = notification.id
195
+ yield(notification)
196
+ }
197
+ end
198
+ json
199
+ end
200
+
201
+ # :section: TJPlurker provided
202
+ # Those methos were all provided by TJPlukrer instead of Plurk
203
+
204
+ # Improved from get_friends_by_offset for getting all friends from a user
205
+ # return:: An Array
206
+ def get_friends user_id, max=nil
207
+ list = []
208
+ offset = 0;
209
+ loop{
210
+ set = get_friends_by_offset(user_id, 100, offset)
211
+ size = set.size
212
+ break if size == 0
213
+ list |= set
214
+ break if max && list.size>max
215
+ offset += size
216
+ }
217
+ return list
218
+ end
219
+
220
+ # Used to follow a lot of users.
221
+ # level:: e.g. 1 for following user's all friends, 2 for following user's all friends and all friends of all friends.
222
+ def super_become_fan fan_id, level=1
223
+ return if level<=0
224
+ become_fan(fan_id)
225
+ list = []
226
+ offset = 0;
227
+ loop{
228
+ set = get_friends_by_offset(fan_id, 100, offset)
229
+ size = set.size
230
+ break if size == 0
231
+ tg = ThreadGroup.new
232
+ set.each{|i|
233
+ tg.add Thread.new{
234
+ wait_sec = 1
235
+ until become_fan(i["uid"]).key?("success_text")
236
+ $tjpLog.info(self.class){"Trying to become #{i["uid"]}'s fan in #{wait_sec} seconds"}
237
+ sleep(wait_sec)
238
+ wait_sec *= 2
239
+ end
240
+ $tjpLog.info(self.class){"Become #{i["uid"]}'s fan successfully"}
241
+ }
242
+ }
243
+ tg.list.each{|i| i.join }
244
+ list |= set
245
+ offset += size
246
+ }
247
+ list.each{|i|
248
+ super_become_fan(i["uid"], level-1)
249
+ }
250
+ end
251
+ end
252
+
253
+ # Format plurk notification. See TJPlurker#comet_channel
254
+ class NotificationData
255
+ # Can be +new_plurk+ or +new_response+.
256
+ attr_reader :type
257
+ attr_reader :response_count
258
+ attr_reader :plurk_id
259
+ attr_reader :lang
260
+ attr_reader :user_id
261
+ attr_reader :qualifier
262
+ attr_reader :content
263
+ # Notification ID.
264
+ attr_reader :id
265
+ # Time of the listened post.
266
+ attr_reader :posted
267
+ attr_reader :owner_id
268
+ # The original JSON object from server response.
269
+ attr_reader :origin
270
+
271
+ def initialize member
272
+ @type = member['type']
273
+ @response_count = member['response_count']
274
+ @plurk_id = member['plurk_id']
275
+
276
+ @lang = @type=="new_plurk" ? member['lang'] : member['response']['lang']
277
+ @user_id = @type=="new_plurk" ? member['user_id'] : member['response']['user_id']
278
+ @qualifier = @type=="new_plurk" ? member['qualifier'] : member['response']['qualifier']
279
+ @content = @type=="new_plurk" ? member['content'] : member['response']['content']
280
+ @id = @type=="new_plurk" ? member['id'] : member['response']['id']
281
+ @posted = @type=="new_plurk" ? member['posted'] : member['response']['posted']
282
+
283
+ @owner_id = @type=="new_plurk" ? member['owner_id'] : member['plurk']['owner_id']
284
+
285
+ @origin = member
286
+ end
287
+ end
288
+
289
+ module_function
290
+ # Convert user name into user_id, e.g. Given "tonytonyjan", return "5874158"
291
+ def get_uid user_name
292
+ open("http://www.plurk.com/#{user_name}").read =~ /"user_id": (\d*)/
293
+ raise "User \"#{user_name}\" not found." unless $1
294
+ return $1
295
+ end
296
+ end
@@ -0,0 +1,489 @@
1
+ require 'logger'
2
+ require 'json'
3
+ require 'oauth'
4
+ require 'net/http'
5
+
6
+ $tjpLog = Logger.new(STDOUT)
7
+ $tjpLog.level = Logger::INFO
8
+ $tjpLog.formatter = proc{ |severity, datetime, progname, msg|
9
+ "#{severity} [#{datetime}] in #{progname} -- #{msg}\n"
10
+ }
11
+
12
+ # == 注意
13
+ # 這是舊的函式庫,如果你想學習如何使用最新的TJPlurker,建議你應該參考 {此頁}[link:GETSTART.html]
14
+ #
15
+ # 如果你堅持享用舊版本,你應該在程式中如下引用以支援舊式的寫法:
16
+ # require "tjplurker/legacy"
17
+ # include Legacy
18
+ # 而不是
19
+ # require "tjplurker"
20
+ # include TJP
21
+ module Legacy
22
+ # == 注意
23
+ # 這是舊的函式庫,如果你想學習如何使用最新的TJPlurker,建議你應該參考 {此頁}[link:GETSTART.html]
24
+ #
25
+ # 如果你堅持享用舊版本,你應該在程式中如下引用以支援舊式的寫法:
26
+ # require "tjplurker/legacy"
27
+ # include Legacy
28
+ # 而不是
29
+ # require "tjplurker"
30
+ # include TJP
31
+ #
32
+ # 這是TJPlurker的核心類別,大部分的情況下此類別的方法都會回傳一個由Plurk Server產生的JSON物件。
33
+ # 所有的方法會在timeout發生的時候重新呼叫
34
+ #
35
+ # It's core class of TJPlurker.
36
+ # Note that every methods in this class returns a JSON object
37
+ # which was converted from Plurk server's response.
38
+ # All mehtods will retry again when request timeout occurs.
39
+ class TJPlurker
40
+ # 噗浪帳號
41
+ # Plurk user ID.
42
+ attr_reader :user
43
+
44
+ =begin
45
+ [auto_login] 是否在建構時自動登入
46
+ If +auto_login+ sets +true+, it will autologin while constructing.
47
+ =end
48
+ def initialize api_key, user, passwd, auto_login=true
49
+ @user, @passwd, @api_key = user, passwd, api_key
50
+ @cookie = nil
51
+ @user_channel = nil
52
+ @id_buffer = nil
53
+ login if auto_login
54
+ end
55
+
56
+
57
+ =begin
58
+ :section: Offical API wrapping
59
+ 對Plurk官方API做包裹
60
+
61
+ Plurk API wrapping
62
+ =end
63
+
64
+ =begin
65
+ 登入
66
+ [no_data] 是否將登入成功後的訊息簡化成:
67
+ {"success_text": "ok"}
68
+ 否則將回傳使用者的詳細資料
69
+
70
+ If no_data sets true, the successful login message was simplified as follow:
71
+ {"success_text": "ok"}
72
+ Otherwise, more details of user's profile were retured.
73
+ =end
74
+ def login no_data=true
75
+ method = "/API/Users/login"
76
+ attr = {:username=>@user, :password=>@passwd, :api_key=>@api_key}
77
+ attr[:no_data] = '1' if no_data
78
+ api(method, attr)
79
+ end
80
+
81
+ =begin
82
+ 發噗
83
+
84
+ 範例:
85
+ require 'tjplurker'
86
+ tjp = TJPlurker.new("API Key", "帳號", "密碼")
87
+ tjp.plurk_add("Hello TJPlurker")
88
+ =end
89
+ def plurk_add content, qualifier=':', limited_to=nil, no_comments=nil, lang='tr_ch'
90
+ method = "/API/Timeline/plurkAdd"
91
+ attr = {:api_key=>@api_key, :content=>content, :qualifier=>qualifier,:limited_to=>limited_to, :no_comments=>no_comments, :lang=>lang}
92
+ api(method, attr)
93
+ end
94
+
95
+ =begin
96
+ 回噗
97
+ [qualifier] 可以是如下的字串:
98
+
99
+ Can be the following string:
100
+ loves, likes, shares, gives, hates, wants, has, will,
101
+ asks, wishes, was, feels, thinks, says, is, :, freestyle, hopes, needs, wonders
102
+ 範例:
103
+ require 'tjplurker'
104
+ tjp = TJPlurker.new("API Key", "帳號", "密碼")
105
+ json_obj = tjp.plurk_add("Hello TJPlurker")
106
+ tjp.response_add(json_obj["plurk_id"], "This is a reply.", "says")
107
+ =end
108
+ def response_add plurk_id, content, qualifier=':'
109
+ method = "/API/Responses/responseAdd"
110
+ attr = {:api_key=>@api_key, :plurk_id=>plurk_id, :content=>content, :qualifier=>qualifier}
111
+ api(method, attr)
112
+ end
113
+
114
+ =begin
115
+ 成為粉絲
116
+ [fan_id] 你想追蹤的人的ID
117
+
118
+ The ID of the user you want to become fan of
119
+ =end
120
+ def become_fan fan_id
121
+ method = "/API/FriendsFans/becomeFan"
122
+ attr = {:api_key=>@api_key, :fan_id=>fan_id}
123
+ api(method, attr)
124
+ end
125
+
126
+ =begin
127
+ 接受所有的好友邀請
128
+
129
+ Accept all friendship requests as friends.
130
+ Successful return:
131
+ {"success_text": "ok"}
132
+ =end
133
+ def add_all_as_friends
134
+ method = "/API/Alerts/addAllAsFriends"
135
+ attr = {:api_key=>@api_key}
136
+ api(method, attr)
137
+ end
138
+
139
+ =begin
140
+ 取得使用者的好友名單
141
+
142
+ Returns user_id's friend list
143
+ [limit] The max number of friends to be returned (default 10).
144
+ [Successful return] 好友名單陣列
145
+
146
+ Returns a list of JSON objects users, e.g.
147
+ [{"id": 3, "nick_name": "alvin", ...}, ...]
148
+ [warnning] +limit+的最大值被官方設定為100,這個方法沒有很好用,建議你用tjp_get_friends取代
149
+ This maximum of +limit+ is set 100 officially, this method is not that useful.
150
+ We suggest you use get_friend_list instead of get_friends_by_offset.
151
+ =end
152
+ def get_friends_by_offset user_id, limit=100, offset='0'
153
+ method = "/API/FriendsFans/getFriendsByOffset"
154
+ attr = {:api_key=>@api_key, :user_id=>user_id, :offset=>offset, :limit=>limit}
155
+ api(method, attr)
156
+ end
157
+
158
+ =begin
159
+ Fetches public information such as a user's public plurks and basic information.
160
+ Fetches also if the current user is following the user, are friends with or is a fan.
161
+ [Successful return] Returns a JSON object with a lot of information
162
+ that can be used to construct a user's public profile and timeline.
163
+ [Error returns] HTTP 400 BAD REQUEST with {"error_text": "Invalid user_id"} as body
164
+ HTTP 400 BAD REQUEST with {"error_text": "User not found"} as body
165
+ =end
166
+ def get_public_profile user_id
167
+ method = "/API/Profile/getPublicProfile"
168
+ attr = {:api_key=>@api_key, :user_id=>user_id}
169
+ api(method, attr)
170
+ end
171
+
172
+ # Warning:: It has bugs in offecial Plurk API.
173
+ def get_plurks limit=nil, offset=nil
174
+ method = "/API/Polling/getPlurks"
175
+ attr = {:api_key=>@api_key, :limit=>limit, :offset=>offset}
176
+ api(method, attr)
177
+ end
178
+
179
+ def get_user_channel
180
+ method = "/API/Realtime/getUserChannel"
181
+ attr = {:api_key=>@api_key}
182
+ api(method, attr)
183
+ end
184
+
185
+ def comet_channel
186
+ #Assign new channel if @user_channel is nil
187
+ @user_channel ||= get_user_channel
188
+ begin
189
+ res = Net::HTTP.get_response(URI.parse(@user_channel['comet_server']))
190
+ json = JSON.parse(res.body.sub!(/CometChannel\.scriptCallback\((.*)\);/){|match| $1})
191
+ rescue Timeout::Error => ex
192
+ $tjpLog.warn(self.class){"Request timeout, retry."}
193
+ retry
194
+ rescue Errno::ECONNRESET => ex
195
+ $tjpLog.warn(self.class){"Connection reset, retry."}
196
+ retry
197
+ rescue => ex
198
+ $tjpLog.fatal(self.class){"Unknown error, skip."}
199
+ puts ex.class, ex.message, ex.backtrace
200
+ return
201
+ end
202
+ #Update the offset
203
+ @user_channel['comet_server'].sub!(/[\d]+$/, json["new_offset"].to_s)
204
+ if json['data']
205
+ json['data'].each{|member|
206
+ notification = NotificationData.new(member)
207
+ return if notification.id == @id_buffer
208
+ $tjpLog.info(self.class){"Notification: #{notification.user_id}: \"#{notification.content}\""}
209
+ @id_buffer = notification.id
210
+ yield(notification)
211
+ }
212
+ end
213
+ json
214
+ end
215
+
216
+ =begin
217
+ :section: TJPlurker provided
218
+ 由TJPlurker提供的工具,非官方有的方法
219
+
220
+ Those methos were all provided by TJPlukrer instead of plurk
221
+ =end
222
+
223
+ =begin
224
+ 這個方法游TJPlurker提供的非官方功能,改良自get_friends_by_offset,用於取得使用者所有的好友
225
+
226
+ This method is provided by TJPlurker. Improved from get_friends_by_offset for getting all friends of a user
227
+ [max] 取得多少好友,nil則無限制
228
+
229
+ The maximum number of your request. Set +nil+ for unlimited.
230
+ [return] 陣列
231
+
232
+ An array contains friends
233
+ 範例:
234
+ require 'tjplurker'
235
+ tjp = TJPlurker.new("API Key", "帳號", "密碼")
236
+ friend_list = tjp.tjp_get_friends("5874158")
237
+ friend_list.each{|friend| puts friend["uid"]}
238
+ =end
239
+ def tjp_get_friends user_id, max=nil
240
+ list = []
241
+ offset = 0;
242
+ loop{
243
+ set = get_friends_by_offset(user_id, 100, offset)
244
+ size = set.size
245
+ break if size == 0
246
+ list |= set
247
+ break if max && list.size>max
248
+ offset += size
249
+ }
250
+ return list
251
+ end
252
+
253
+ =begin
254
+ 用於追蹤大量使用者的河道
255
+ [level] 深度,如等於1會追蹤使用者的所有好友,等於2則會追蹤使用者所有的好友以及好友的好友
256
+ =end
257
+ def tjp_super_become_fan fan_id, level=1
258
+ return if level<=0
259
+ become_fan(fan_id)
260
+ list = []
261
+ offset = 0;
262
+ loop{
263
+ set = get_friends_by_offset(fan_id, 100, offset)
264
+ size = set.size
265
+ break if size == 0
266
+ tg = ThreadGroup.new
267
+ set.each{|i|
268
+ tg.add Thread.new{
269
+ wait_sec = 1
270
+ until become_fan(i["uid"]).key?("success_text")
271
+ $tjpLog.info(self.class){"Trying to become #{i["uid"]}'s fan in #{wait_sec} seconds"}
272
+ sleep(wait_sec)
273
+ wait_sec *= 2
274
+ end
275
+ $tjpLog.info(self.class){"Become #{i["uid"]}'s fan successfully"}
276
+ }
277
+ }
278
+ tg.list.each{|i| i.join }
279
+ list |= set
280
+ offset += size
281
+ }
282
+ list.each{|i|
283
+ super_become_fan(i["uid"], level-1)
284
+ }
285
+ end
286
+
287
+ private
288
+ def api method, attr
289
+ #Build POST Request
290
+ req = Net::HTTP::Post.new(method)
291
+ req.set_form_data(attr)
292
+ req["Cookie"] = @cookie
293
+ #Build GET Request
294
+ #path = method + "?" + URI.encode_www_form(attr)
295
+ #req2 = Net::HTTP::Get.new(path)
296
+ #req2["Cookie"] = @cookie
297
+
298
+ #Build HTTP connection
299
+ http = Net::HTTP.new('www.plurk.com', 80)
300
+ begin
301
+ resp = http.start{
302
+ $tjpLog.debug(self.class){"Request: #{method}, #{attr}"}
303
+ http.request(req)
304
+ }
305
+ @cookie ||= resp['set-cookie']
306
+ json = JSON.parse(resp.body)
307
+ $tjpLog.debug(self.class){"Response: #{json}"}
308
+ $tjpLog.error(self.class){json["error_text"]} if json.is_a?(Hash) && json["error_text"]
309
+ rescue Timeout::Error => ex
310
+ $tjpLog.warn(self.class){"Request timeout, retrying."}
311
+ retry
312
+ rescue Errno::ECONNRESET => ex
313
+ $tjpLog.warn(self.class){"Connection reset, retry."}
314
+ retry
315
+ rescue JSON::ParserError => ex
316
+ $tjpLog.error(self.class){"JSON parse error, request failed."}
317
+ rescue => ex
318
+ $tjpLog.fatal(self.class){"Unknown error, skip."}
319
+ puts ex.class, ex.message, ex.backtrace
320
+ return
321
+ end
322
+ return json
323
+ end
324
+
325
+ # Format the json from the comet server. See TJPlurker#comet_channel
326
+ class NotificationData
327
+ # Can be +new_plurk+ or +new_response+.
328
+ attr_reader :type
329
+ attr_reader :response_count
330
+ attr_reader :plurk_id
331
+ attr_reader :lang
332
+ attr_reader :user_id
333
+ attr_reader :qualifier
334
+ attr_reader :content
335
+ # Notification ID.
336
+ attr_reader :id
337
+ # Time of the listened post.
338
+ attr_reader :posted
339
+ attr_reader :owner_id
340
+ # The original JSON object from server response.
341
+ attr_reader :origin
342
+
343
+ def initialize member
344
+ @type = member['type']
345
+ @response_count = member['response_count']
346
+ @plurk_id = member['plurk_id']
347
+
348
+ @lang = @type=="new_plurk" ? member['lang'] : member['response']['lang']
349
+ @user_id = @type=="new_plurk" ? member['user_id'] : member['response']['user_id']
350
+ @qualifier = @type=="new_plurk" ? member['qualifier'] : member['response']['qualifier']
351
+ @content = @type=="new_plurk" ? member['content'] : member['response']['content']
352
+ @id = @type=="new_plurk" ? member['id'] : member['response']['id']
353
+ @posted = @type=="new_plurk" ? member['posted'] : member['response']['posted']
354
+
355
+ @owner_id = @type=="new_plurk" ? member['owner_id'] : member['plurk']['owner_id']
356
+
357
+ @origin = member
358
+ end
359
+ end
360
+
361
+ class Robot
362
+ def initialize *params
363
+ case params.size
364
+ when 1
365
+ @tjplurker = params[0]
366
+ when 3
367
+ api_key, user, passwd = params
368
+ @tjplurker = TJPlurker.new(api_key, user, passwd)
369
+ when 4
370
+ api_key, user, passwd, auto_login = params
371
+ @tjplurker = TJPlurker.new(api_key, user, passwd, auto_login)
372
+ end
373
+ @services = []
374
+ end
375
+
376
+ # Starting a loop and invokes services when a notification was listened.
377
+ def start
378
+ $tjpLog.info(self.class){"Start robot."}
379
+ Thread.new{
380
+ loop{
381
+ @tjplurker.add_all_as_friends
382
+ sleep(60)
383
+ }
384
+ }
385
+ loop{
386
+ @tjplurker.comet_channel{|data|
387
+ @services.each{|service|
388
+ begin
389
+ service.serve(@tjplurker, data)
390
+ rescue => ex
391
+ $tjpLog.error(self.class){"An error occured in #{service.class}, class: #{ex.class}, message: #{ex.message}, backtrace: #{ex.backtrace}"}
392
+ end
393
+ }
394
+ }
395
+ }
396
+ end
397
+
398
+ # +service+:: Can be instance TJPlurker::Service or class TJPlurker::Service or it's descendant classes.
399
+ def add_service service
400
+ if service.is_a? Service
401
+ @services << service
402
+ $tjpLog.info(self.class){"Add service: #{service.name}"}
403
+ elsif service <= Service
404
+ tmp = service.new
405
+ @services << tmp
406
+ $tjpLog.info(self.class){"Add service: #{tmp.name}"}
407
+ else
408
+ $tjpLog.warn(self.class){"Unrecognized service, ignored."}
409
+ end
410
+ end
411
+ end
412
+
413
+ class Service
414
+ attr_reader :name
415
+ def initialize name=self.class.to_s, &proc
416
+ @name = name
417
+ if block_given?
418
+ @serve_proc = proc
419
+ end
420
+ end
421
+
422
+ # Invoke when listened a new plurk.
423
+ def decide tjplurker, data
424
+ true
425
+ end
426
+
427
+ def action tjplurker, data
428
+ if @serve_proc
429
+ @serve_proc[tjplurker, data]
430
+ else
431
+ p data
432
+ end
433
+ end
434
+
435
+ def serve tjplurker, data
436
+ action(tjplurker, data) if decide(tjplurker, data)
437
+ end
438
+ end
439
+
440
+ class ThreadService < Service
441
+ # +lasting+:: How long in second will a thread lives. If one plurk just be responsed, then it will live another +lasting+ time.
442
+ # +refresh+:: Interval that checks if thread should terminate.
443
+ def initialize name=self.class.to_s, lasting=60, refresh=1, &proc
444
+ super name
445
+ @lasting = lasting
446
+ @refresh = refresh
447
+ @plurk_id_hash = Hash.new
448
+ @mutex = Mutex.new
449
+ end
450
+
451
+ # Invoke after thread termination.
452
+ # You can use this to tell users this service just terminate.
453
+ def final tjplurker, data
454
+
455
+ end
456
+
457
+ def serve tjplurker, data
458
+ if data.type=='new_plurk' && decide(tjplurker, data)
459
+ @plurk_id_hash[data.plurk_id] = Thread.new(data.plurk_id){|plurk_id|
460
+ $tjpLog.debug(self.class){"Create thread in plurk ##{plurk_id}"}
461
+ Thread.current[:last_time] = Time.now.to_i
462
+ loop{
463
+ sleep(@refresh)
464
+ $tjpLog.debug(self.class){"Plurk ##{plurk_id} remain #{Thread.current[:last_time]+@lasting-Time.now.to_i} seconds."}
465
+ @mutex.lock
466
+ if Time.now.to_i - Thread.current[:last_time] > @lasting
467
+ @mutex.unlock
468
+ break
469
+ end
470
+ @mutex.unlock
471
+ }
472
+ $tjpLog.debug(self.class){"Terminate thread in plurk ##{plurk_id}"}
473
+ @mutex.lock
474
+ @plurk_id_hash.delete(plurk_id)
475
+ @mutex.unlock
476
+ final(tjplurker, data)
477
+ }
478
+ else
479
+ @mutex.lock
480
+ if data.type=='new_response' && @plurk_id_hash.has_key?(data.plurk_id)
481
+ @plurk_id_hash[data.plurk_id][:last_time] = Time.now.to_i
482
+ action(tjplurker, data)
483
+ end
484
+ @mutex.unlock
485
+ end
486
+ end
487
+ end
488
+ end
489
+ end
@@ -0,0 +1,142 @@
1
+ module TJP
2
+ class Robot
3
+ # :call-seq: new(tjplurker)
4
+ # new(key, screte)
5
+ # new(key, secret, access_token, access_token_screte)
6
+ # key:: consumer key
7
+ # secret:: consumer secret
8
+ attr_reader :tjplurker
9
+ def initialize *params
10
+ case params.size
11
+ when 1
12
+ @tjplurker = params[0]
13
+ when 2
14
+ key, screte = params
15
+ @tjplurker = TJPlurker.new(key, screte)
16
+ when 4
17
+ key, secret, access_token, access_token_screte = params
18
+ @tjplurker = TJPlurker.new(key, secret, access_token, access_token_screte)
19
+ end
20
+ @services = []
21
+ end
22
+
23
+ # Starting a loop and invokes services when a notification was listened.
24
+ def start
25
+ $tjpLog.info(self.class){"Start robot."}
26
+ Thread.new{
27
+ loop{
28
+ @tjplurker.add_all_as_friends
29
+ sleep(60)
30
+ }
31
+ }
32
+ loop{
33
+ @tjplurker.comet_channel{|data|
34
+ @services.each{|service|
35
+ begin
36
+ service.serve(@tjplurker, data)
37
+ rescue => ex
38
+ $tjpLog.error(self.class){"An error occured in #{service.class}, class: #{ex.class}, message: #{ex.message}, backtrace: #{ex.backtrace}"}
39
+ end
40
+ }
41
+ }
42
+ }
43
+ end
44
+
45
+ # service:: Can be instance TJPlurker::Service or class TJPlurker::Service or it's descendant classes.
46
+ def add_service service
47
+ if service.is_a? Service
48
+ @services << service
49
+ $tjpLog.info(self.class){"Add service: #{service.name}"}
50
+ elsif service <= Service
51
+ tmp = service.new
52
+ @services << tmp
53
+ $tjpLog.info(self.class){"Add service: #{tmp.name}"}
54
+ else
55
+ $tjpLog.warn(self.class){"Unrecognized service, ignored."}
56
+ end
57
+ end
58
+ end
59
+
60
+ class Service
61
+ attr_reader :name
62
+ def initialize name=self.class.to_s, &proc
63
+ @name = name
64
+ if block_given?
65
+ @serve_proc = proc
66
+ end
67
+ end
68
+
69
+ # Invoke when listened a new plurk.
70
+ # data:: notification data
71
+ def decide tjplurker, data
72
+ true
73
+ end
74
+
75
+ # Invoke when decide return true
76
+ # data:: notification data
77
+ def action tjplurker, data
78
+ if @serve_proc
79
+ @serve_proc[tjplurker, data]
80
+ else
81
+ p data
82
+ end
83
+ end
84
+
85
+ # It has higher priority than decide and action
86
+ # data:: notification data
87
+ def serve tjplurker, data
88
+ action(tjplurker, data) if decide(tjplurker, data)
89
+ end
90
+ end
91
+
92
+ class ThreadService < Service
93
+ # lasting:: How long in second will a thread lives. If one plurk just be responsed,
94
+ # then it will live another +lasting+ time.
95
+ # refresh:: Interval that checks if thread should terminate.
96
+ def initialize name=self.class.to_s, lasting=60, refresh=1, &proc
97
+ super name
98
+ @lasting = lasting
99
+ @refresh = refresh
100
+ @plurk_id_hash = Hash.new
101
+ @mutex = Mutex.new
102
+ end
103
+
104
+ # Invoke after thread termination.
105
+ # You can use this to tell users this service just terminate.
106
+ # data:: notification data
107
+ def final tjplurker, data
108
+
109
+ end
110
+
111
+ def serve tjplurker, data
112
+ if data.type=='new_plurk' && decide(tjplurker, data)
113
+ @plurk_id_hash[data.plurk_id] = Thread.new(data.plurk_id){|plurk_id|
114
+ $tjpLog.debug(self.class){"Create thread in plurk ##{plurk_id}"}
115
+ Thread.current[:last_time] = Time.now.to_i
116
+ loop{
117
+ sleep(@refresh)
118
+ $tjpLog.debug(self.class){"Plurk ##{plurk_id} remain #{Thread.current[:last_time]+@lasting-Time.now.to_i} seconds."}
119
+ @mutex.lock
120
+ if Time.now.to_i - Thread.current[:last_time] > @lasting
121
+ @mutex.unlock
122
+ break
123
+ end
124
+ @mutex.unlock
125
+ }
126
+ $tjpLog.debug(self.class){"Terminate thread in plurk ##{plurk_id}"}
127
+ @mutex.lock
128
+ @plurk_id_hash.delete(plurk_id)
129
+ @mutex.unlock
130
+ final(tjplurker, data)
131
+ }
132
+ else
133
+ @mutex.lock
134
+ if data.type=='new_response' && @plurk_id_hash.has_key?(data.plurk_id)
135
+ @plurk_id_hash[data.plurk_id][:last_time] = Time.now.to_i
136
+ action(tjplurker, data)
137
+ end
138
+ @mutex.unlock
139
+ end
140
+ end
141
+ end
142
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tjplurker
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: '1.2'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-07-11 00:00:00.000000000Z
12
+ date: 2011-07-15 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
16
- requirement: &18855720 !ruby/object:Gem::Requirement
16
+ requirement: &18195672 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,7 +21,18 @@ dependencies:
21
21
  version: 1.4.2
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *18855720
24
+ version_requirements: *18195672
25
+ - !ruby/object:Gem::Dependency
26
+ name: oauth
27
+ requirement: &18195384 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 0.4.5
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *18195384
25
36
  description: A safe Plurk API wrapper based on Ruby and mainly designed for easily
26
37
  making plurk robot.
27
38
  email: tonytonyjan.cs97@g2.nctu.edu.tw
@@ -30,6 +41,9 @@ extensions: []
30
41
  extra_rdoc_files: []
31
42
  files:
32
43
  - lib/tjplurker.rb
44
+ - lib/tjplurker/core.rb
45
+ - lib/tjplurker/legacy.rb
46
+ - lib/tjplurker/robot.rb
33
47
  homepage: http://code.google.com/p/tjplurker
34
48
  licenses: []
35
49
  post_install_message: