-
Notifications
You must be signed in to change notification settings - Fork 34
/
rubytter_proxy.rb
204 lines (183 loc) · 5.76 KB
/
rubytter_proxy.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# -*- coding: utf-8 -*-
begin
require 'nokogiri'
rescue LoadError
begin
require 'hpricot'
rescue LoadError
end
end
module Termtter
class JSONError < StandardError; end
class RubytterProxy
class FrequentAccessError < StandardError; end
include Hookable
attr_reader :rubytter
def initialize(*args)
@rubytter = OAuthRubytter.new(*args)
@initial_args = args
end
def method_missing(method, *args, &block)
if @rubytter.respond_to?(method)
result = nil
begin
modified_args = args
hooks = self.class.get_hooks("pre_#{method}")
hooks.each do |hook|
modified_args = hook.call(*modified_args)
end
from = Time.now if Termtter::Client.logger.debug?
Termtter::Client.logger.debug {
"rubytter_proxy: #{method}(#{modified_args.inspect[1...-1]})"
}
result = call_rubytter_or_use_cache(method, *modified_args, &block)
Termtter::Client.logger.debug {
"rubytter_proxy: #{method}(#{modified_args.inspect[1...-1]}), " +
"%.2fsec" % (Time.now - from)
}
self.class.call_hooks("post_#{method}", *args)
rescue HookCanceled
rescue TimeoutError => e
Termtter::Client.logger.debug {
"rubytter_proxy: #{method}(#{modified_args.inspect[1...-1]}) " +
"#{e.message} #{'%.2fsec' % (Time.now - from)}"
}
raise e
rescue => e
Termtter::Client.logger.debug {
"rubytter_proxy: #{method}(#{modified_args.inspect[1...-1]}) #{e.message}"
}
raise e
end
result
else
super
end
end
def call_rubytter_or_use_cache(method, *args, &block)
case method
when :show
unless status = cached_status(args[0])
status = call_rubytter(method, *args, &block)
store_status_cache(status)
end
status
when :user
unless user = cached_user(args[0])
user = call_rubytter(method, *args, &block)
store_user_cache(user)
end
user
when :home_timeline, :user_timeline, :friends_timeline, :search
statuses = call_rubytter(method, *args, &block)
statuses.each do |status|
store_status_cache(status)
end
statuses
else
call_rubytter(method, *args, &block)
end
end
def cached_user(screen_name_or_id)
user = Termtter::Client.memory_cache.get(['user', Termtter::Client.normalize_as_user_name(screen_name_or_id.to_s)].join('-'))
ActiveRubytter.new(user) if user
end
def cached_status(status_id)
status = Termtter::Client.memory_cache.get(['status', status_id].join('-'))
ActiveRubytter.new(status) if status
end
def store_status_cache(status)
Termtter::Client.memory_cache.set(['status', status.id].join('-'), status.to_hash, 3600 * 24 * 14)
store_user_cache(status.user)
end
def store_user_cache(user)
Termtter::Client.memory_cache.set(['user', user.id.to_i].join('-'), user.to_hash, 3600 * 24)
Termtter::Client.memory_cache.set(['user', Termtter::Client.normalize_as_user_name(user.screen_name)].join('-'), user.to_hash, 3600 * 24)
end
attr_accessor :safe_mode
def safe
new_instance = self.class.new(@rubytter)
new_instance.safe_mode = true
self.instance_variables.each{ |v|
new_instance.instance_variable_set(v, self.instance_variable_get(v))
}
new_instance
end
def current_limit
@limit_manager ||= LimitManager.new(@rubytter)
end
def call_rubytter(method, *args, &block)
raise FrequentAccessError if @safe_mode && !self.current_limit.safe?
config.retry.times do |now|
begin
timeout(config.timeout) do
return @rubytter.__send__(method, *args, &block)
end
rescue Rubytter::APIError => e
if /Status is over 140 characters/ =~ e.message
len = args[0].charsize
e2 = Rubytter::APIError.new("#{e.message} (+#{len - 140})")
e2.set_backtrace(e.backtrace)
raise e2
else
raise
end
rescue JSON::ParserError => e
if message = error_html_message(e)
puts message
raise Rubytter::APIError.new(message)
else
raise e
end
rescue StandardError, TimeoutError => e
if now + 1 == config.retry
raise e
else
Termtter::Client.logger.debug { "rubytter_proxy: retry (#{e.class.to_s}: #{e.message})" }
end
end
end
end
if defined? Nokogiri
def error_html_message(e)
Nokogiri(e.message).at('title, h2').text rescue nil
end
elsif defined? Hpricot
def error_html_message(e)
Hpricot(e.message).at('title, h2').inner_text rescue nil
end
else
def error_html_message(e)
m = %r'<title>(.*?)</title>'.match(e.message) and m.captures[0] rescue nil
end
end
private :error_html_message
# XXX: these methods should in oauth_rubytter
def access_token
@rubytter.instance_variable_get(:@access_token)
end
def consumer_token
access_token.consumer
end
class LimitManager
def initialize(rubytter)
@rubytter = rubytter
@limit = nil
@count = 0
end
def get
@count += 1
if @count > 5 || !@limit
@count = 0
@limit = @rubytter.limit_status
end
@limit
end
def safe?
limit = self.get
threshold = [(Time.parse(limit.reset_time) - Time.now) / 3600 - 0.1, 0.1].max * limit.hourly_limit
threshold < limit.remaining_hits
end
end
end
end