tjplurker 1.0.4 → 1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: