drip 0.0.1
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/.gitignore +4 -0
- data/Gemfile +6 -0
- data/README +6 -0
- data/Rakefile +2 -0
- data/drip.gemspec +20 -0
- data/drip.txt +380 -0
- data/install.rb +10 -0
- data/lib/drip.rb +294 -0
- data/lib/drip/version.rb +3 -0
- data/lib/my_drip.rb +64 -0
- data/sample/copocopo.rb +82 -0
- data/sample/drip_tw.rb +255 -0
- data/sample/gca.rb +70 -0
- data/sample/hello_tw.rb +5 -0
- data/sample/my_status.rb +52 -0
- data/sample/simple-oauth.rb +140 -0
- data/sample/tw_markov.rb +154 -0
- data/test/basic.rb +156 -0
- metadata +71 -0
data/lib/drip/version.rb
ADDED
data/lib/my_drip.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'drb/drb'
|
2
|
+
|
3
|
+
MyDrip = DRbObject.new_with_uri('drbunix:' + File.expand_path('~/.drip/port'))
|
4
|
+
|
5
|
+
def MyDrip.invoke
|
6
|
+
fork do
|
7
|
+
Process.daemon
|
8
|
+
|
9
|
+
require 'drip'
|
10
|
+
require 'fileutils'
|
11
|
+
|
12
|
+
dir = File.expand_path('~/.drip')
|
13
|
+
uri = 'drbunix:' + File.join(dir, 'port')
|
14
|
+
ro = DRbObject.new_with_uri(uri)
|
15
|
+
begin
|
16
|
+
ro.older(nil) #ping
|
17
|
+
exit
|
18
|
+
rescue
|
19
|
+
end
|
20
|
+
|
21
|
+
FileUtils.mkdir_p(dir)
|
22
|
+
FileUtils.cd(dir)
|
23
|
+
|
24
|
+
drip = Drip.new('drip')
|
25
|
+
def drip.quit
|
26
|
+
Thread.new do
|
27
|
+
synchronize do |key|
|
28
|
+
exit(0)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
DRb.start_service(uri, drip)
|
34
|
+
File.open('pid', 'w') {|fp| fp.puts($$)}
|
35
|
+
|
36
|
+
DRb.thread.join
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class DripCursor
|
41
|
+
def initialize(drip, bufsiz=10, at_least=10)
|
42
|
+
@drip = drip
|
43
|
+
@cur = nil
|
44
|
+
@bufsiz = bufsiz
|
45
|
+
@at_least = at_least
|
46
|
+
end
|
47
|
+
attr_accessor :cur
|
48
|
+
|
49
|
+
def now
|
50
|
+
@cur ? @drip.key_to_time(@cur) : nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def seek_at(time)
|
54
|
+
@cur = @drip.time_to_key(time)
|
55
|
+
end
|
56
|
+
|
57
|
+
def past_each(tag=nil)
|
58
|
+
while kv = @drip.older(@cur, tag)
|
59
|
+
@cur, value = kv
|
60
|
+
yield(value)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
data/sample/copocopo.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'drip_tw'
|
3
|
+
require 'my_drip'
|
4
|
+
require 'date'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
def dig(root, *keys)
|
8
|
+
keys.inject(root) do |node, key|
|
9
|
+
return nil if node.nil?
|
10
|
+
node[key] rescue nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class CopoCopo
|
15
|
+
def initialize(drip=MyDrip)
|
16
|
+
@app = DripDemo.new('CopoCopo OAuth')
|
17
|
+
@drip = drip
|
18
|
+
_, @last = @drip.older(nil, 'CopoCopo Footprint')
|
19
|
+
@last = 0 if @last.nil?
|
20
|
+
@friends = %w(m_seki miwa719 hsbt vestige mame)
|
21
|
+
end
|
22
|
+
attr_reader :app
|
23
|
+
|
24
|
+
def extract(str)
|
25
|
+
ary = []
|
26
|
+
str.scan(/(([ぁ-ん]{2,})\2)|(([ァ-ヴ]{2,})\4)/) do |x|
|
27
|
+
ary << (x[1] || x[3])
|
28
|
+
end
|
29
|
+
ary.uniq
|
30
|
+
end
|
31
|
+
|
32
|
+
def retweet?(event)
|
33
|
+
event['retweeted_status'] ? true : false
|
34
|
+
end
|
35
|
+
|
36
|
+
def mention?(event)
|
37
|
+
event['in_reply_to_status_id_str'] ? true : false
|
38
|
+
end
|
39
|
+
|
40
|
+
def created_at(event)
|
41
|
+
DateTime.parse(event['created_at']).to_time
|
42
|
+
rescue
|
43
|
+
Time.at(1)
|
44
|
+
end
|
45
|
+
|
46
|
+
def make_status(ary, name)
|
47
|
+
"@#{name} " + ary.collect { |s|
|
48
|
+
"#{s}#{s}、#{s}"
|
49
|
+
}.join(", ") + " (by copocopo)"
|
50
|
+
end
|
51
|
+
|
52
|
+
def main_loop
|
53
|
+
while true
|
54
|
+
@last, event = @drip.read_tag(@last, 'DripDemo Event', 1)[0]
|
55
|
+
next if retweet?(event)
|
56
|
+
next if mention?(event)
|
57
|
+
next unless Time.now < created_at(event) + 60000
|
58
|
+
name = dig(event, 'user', 'screen_name')
|
59
|
+
next unless @friends.include?(name)
|
60
|
+
ary = extract(event['text'] || '')
|
61
|
+
next if ary.empty?
|
62
|
+
tweet_id = event['id']
|
63
|
+
# @app.update(make_status(ary, name), tweet_id)
|
64
|
+
p [make_status(ary, name)]
|
65
|
+
# @drip.write(@last, 'CopoCopo Footprint')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
copo = CopoCopo.new
|
71
|
+
app = copo.app
|
72
|
+
|
73
|
+
unless app.has_token?
|
74
|
+
url = app.pin_url
|
75
|
+
puts url
|
76
|
+
system('open ' + url) # for OSX
|
77
|
+
app.set_pin(gets.scan(/\w+/)[0])
|
78
|
+
app.write_setting
|
79
|
+
end
|
80
|
+
|
81
|
+
copo.main_loop
|
82
|
+
|
data/sample/drip_tw.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
require 'simple-oauth'
|
2
|
+
require 'drb'
|
3
|
+
require 'pp'
|
4
|
+
require 'json'
|
5
|
+
require 'my_drip'
|
6
|
+
|
7
|
+
class DripFiber
|
8
|
+
def initialize(app)
|
9
|
+
@app = app
|
10
|
+
@fiber = Fiber.new do |event|
|
11
|
+
story(event)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def story(event)
|
16
|
+
pending = []
|
17
|
+
while event['id_str'].nil?
|
18
|
+
pending << event
|
19
|
+
event = Fiber.yield
|
20
|
+
end
|
21
|
+
|
22
|
+
@app.fill_timeline(event['id_str'])
|
23
|
+
|
24
|
+
while event = pending.shift
|
25
|
+
@app.write(event)
|
26
|
+
end
|
27
|
+
|
28
|
+
while true
|
29
|
+
event = Fiber.yield
|
30
|
+
@app.write(event)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def push(event)
|
35
|
+
@fiber.resume(event)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class JSONStream
|
40
|
+
def initialize(drip)
|
41
|
+
@buf = ''
|
42
|
+
@drip = drip
|
43
|
+
end
|
44
|
+
|
45
|
+
def push(str)
|
46
|
+
@buf << str
|
47
|
+
while (line = @buf[/.+?(\r\n)+/m]) != nil
|
48
|
+
begin
|
49
|
+
@buf.sub!(line,"")
|
50
|
+
line.strip!
|
51
|
+
event = JSON.parse(line)
|
52
|
+
rescue
|
53
|
+
break
|
54
|
+
end
|
55
|
+
pp event if $DEBUG
|
56
|
+
@drip.push(event)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class SimpleOAuthS < SimpleOAuth
|
62
|
+
def http_class
|
63
|
+
return Net::HTTP unless ENV['http_proxy']
|
64
|
+
proxy_url = URI.parse(ENV['http_proxy'])
|
65
|
+
Net::HTTP.Proxy(proxy_url.host, proxy_url.port)
|
66
|
+
end
|
67
|
+
|
68
|
+
def request(method, url, body =nil, headers = {}, &block)
|
69
|
+
method = method.to_s
|
70
|
+
url = URI.parse(url)
|
71
|
+
request = create_http_request(method, url.request_uri, body, headers)
|
72
|
+
request['Authorization'] = auth_header(method, url, request.body)
|
73
|
+
http = http_class.new(url.host, url.port)
|
74
|
+
if url.scheme == 'https'
|
75
|
+
http.use_ssl = true
|
76
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
77
|
+
store = OpenSSL::X509::Store.new
|
78
|
+
store.set_default_paths
|
79
|
+
http.cert_store = store
|
80
|
+
end
|
81
|
+
http.request(request, &block)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
class DripDemo
|
86
|
+
def initialize(oauth_tag = 'DripDemo OAuth')
|
87
|
+
@oauth_tag = oauth_tag
|
88
|
+
@oa = last_setting || {}
|
89
|
+
end
|
90
|
+
|
91
|
+
def has_token?
|
92
|
+
@oa.include?(:oauth_token)
|
93
|
+
end
|
94
|
+
|
95
|
+
def last_setting
|
96
|
+
MyDrip.older(nil, @oauth_tag)[1]
|
97
|
+
end
|
98
|
+
|
99
|
+
def write_setting
|
100
|
+
MyDrip.write(@oa, @oauth_tag)
|
101
|
+
end
|
102
|
+
|
103
|
+
def update_setting(body, keys=nil)
|
104
|
+
found = []
|
105
|
+
body.split('&').each do |pair|
|
106
|
+
k, v = pair.split('=')
|
107
|
+
if keys.nil? || keys.include?(k)
|
108
|
+
@oa[k.intern] = v
|
109
|
+
found << k
|
110
|
+
end
|
111
|
+
end
|
112
|
+
found
|
113
|
+
end
|
114
|
+
|
115
|
+
def oauth
|
116
|
+
SimpleOAuthS.new(@oa[:consumer_key],
|
117
|
+
@oa[:consumer_secret],
|
118
|
+
@oa[:oauth_token],
|
119
|
+
@oa[:oauth_token_secret])
|
120
|
+
end
|
121
|
+
|
122
|
+
def pin_url
|
123
|
+
@oa[:oauth_token] = ''
|
124
|
+
@oa[:oauth_token_secret] = ''
|
125
|
+
response = oauth.get('https://api.twitter.com/oauth/request_token')
|
126
|
+
raise response.message unless response.code == '200'
|
127
|
+
update_setting(response.body, ['oauth_token', 'oauth_token_secret'])
|
128
|
+
|
129
|
+
'http://twitter.com/oauth/authorize?oauth_token=' + @oa[:oauth_token]
|
130
|
+
end
|
131
|
+
|
132
|
+
def set_pin(pin)
|
133
|
+
response = oauth.get('https://api.twitter.com/oauth/access_token',
|
134
|
+
'oauth_token' => @oa[:oauth_token],
|
135
|
+
'oauth_velifier' => pin)
|
136
|
+
raise response.message unless response.code == '200'
|
137
|
+
update_setting(response.body)
|
138
|
+
end
|
139
|
+
|
140
|
+
def drip_stream
|
141
|
+
json = JSONStream.new(DripFiber.new(self))
|
142
|
+
oauth.request(:GET, 'https://userstream.twitter.com/2/user.json') do |r|
|
143
|
+
r.read_body do |chunk|
|
144
|
+
json.push(chunk)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def home_timeline(since_id, max_id)
|
150
|
+
url = "http://api.twitter.com/1/statuses/home_timeline.json?count=200&include_entities=true"
|
151
|
+
url += "&since_id=#{since_id}" if since_id
|
152
|
+
url += "&max_id=#{max_id}" if max_id
|
153
|
+
r = oauth.request(:GET, url)
|
154
|
+
JSON.parse(r.body)
|
155
|
+
end
|
156
|
+
|
157
|
+
def user_timeline(since_id, max_id)
|
158
|
+
url = "http://api.twitter.com/1/statuses/user_timeline.json?count=200&include_entities=true&trim_user=t"
|
159
|
+
url += "&since_id=#{since_id}" if since_id
|
160
|
+
url += "&max_id=#{max_id}" if max_id
|
161
|
+
r = oauth.request(:GET, url)
|
162
|
+
JSON.parse(r.body)
|
163
|
+
end
|
164
|
+
|
165
|
+
def last_tweet_id
|
166
|
+
key = nil
|
167
|
+
while kv = MyDrip.older(key, 'DripDemo Event')
|
168
|
+
key, value = kv
|
169
|
+
return value['id_str'] if value.include?('text')
|
170
|
+
end
|
171
|
+
nil
|
172
|
+
end
|
173
|
+
|
174
|
+
def fill_timeline(max_id)
|
175
|
+
since_id = last_tweet_id
|
176
|
+
return unless since_id
|
177
|
+
timeline = []
|
178
|
+
4.times do
|
179
|
+
return if since_id == max_id || max_id.nil?
|
180
|
+
ary = home_timeline(since_id, max_id)
|
181
|
+
pp [:fill_timeline, ary.size] if $DEBUG
|
182
|
+
max_id = nil
|
183
|
+
ary.reverse_each do |event|
|
184
|
+
next unless event['id']
|
185
|
+
max_id = event['id'] - 1
|
186
|
+
break
|
187
|
+
end
|
188
|
+
break if max_id.nil?
|
189
|
+
timeline += ary
|
190
|
+
end
|
191
|
+
timeline.reverse_each do |event|
|
192
|
+
write(event)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def compact_event(event)
|
197
|
+
return event unless Hash === event
|
198
|
+
result = {}
|
199
|
+
event.each do |k, v|
|
200
|
+
case v
|
201
|
+
when Hash
|
202
|
+
v = compact_event(v)
|
203
|
+
next if v.nil?
|
204
|
+
when [], '', 0, nil
|
205
|
+
next
|
206
|
+
when Array
|
207
|
+
v = v.collect {|vv| compact_event(vv)}
|
208
|
+
end
|
209
|
+
if k == 'user'
|
210
|
+
tmp = {}
|
211
|
+
%w(name screen_name id id_str).each {|attr| tmp[attr] = v[attr]}
|
212
|
+
v = tmp
|
213
|
+
end
|
214
|
+
result[k] = v
|
215
|
+
end
|
216
|
+
result.size == 0 ? nil : result
|
217
|
+
end
|
218
|
+
|
219
|
+
def write(event, tag='DripDemo Event')
|
220
|
+
event = compact_event(event)
|
221
|
+
key = MyDrip.write(event, tag)
|
222
|
+
pp [key, event['id_str'], event['text']] if $DEBUG
|
223
|
+
end
|
224
|
+
|
225
|
+
def update(str, in_reply_to=nil)
|
226
|
+
hash = { :status => str }
|
227
|
+
hash[:in_reply_to_status_id] = in_reply_to if in_reply_to
|
228
|
+
r = oauth.post('http://api.twitter.com/1/statuses/update.xml',
|
229
|
+
hash)
|
230
|
+
pp r.body if $DEBUG
|
231
|
+
end
|
232
|
+
|
233
|
+
def test
|
234
|
+
r = oauth.post('http://api.twitter.com/1/statuses/update.xml',
|
235
|
+
{:status => 'test'})
|
236
|
+
pp r.body if $DEBUG
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
if __FILE__ == $0
|
241
|
+
app = DripDemo.new
|
242
|
+
|
243
|
+
unless app.has_token?
|
244
|
+
url = app.pin_url
|
245
|
+
puts url
|
246
|
+
system('open ' + url) # for OSX
|
247
|
+
app.set_pin(gets.scan(/\w+/)[0])
|
248
|
+
app.write_setting
|
249
|
+
end
|
250
|
+
|
251
|
+
unless $DEBUG
|
252
|
+
Process.daemon
|
253
|
+
end
|
254
|
+
app.drip_stream
|
255
|
+
end
|
data/sample/gca.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
class GoogleChartApi
|
2
|
+
NtoS = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a
|
3
|
+
NtoE = NtoS + %w(- .)
|
4
|
+
URL = 'http://chart.apis.google.com/chart'
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@query = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def as_s(n)
|
11
|
+
NtoS[n]
|
12
|
+
end
|
13
|
+
|
14
|
+
def as_e(n)
|
15
|
+
h, v = n.divmod(64)
|
16
|
+
NtoE[h] + NtoE[v]
|
17
|
+
end
|
18
|
+
|
19
|
+
def chd_s(*ary_ary)
|
20
|
+
@query << "chd=s:" + ary_ary.collect do |ary|
|
21
|
+
ary.collect {|n| as_s(n)}.join('')
|
22
|
+
end.join("|")
|
23
|
+
end
|
24
|
+
|
25
|
+
def chd_e(*ary_ary)
|
26
|
+
@query << "chd=e:" + ary_ary.collect do |ary|
|
27
|
+
ary.collect {|n| as_e(n)}.join('')
|
28
|
+
end.join("|")
|
29
|
+
end
|
30
|
+
|
31
|
+
def chd_t(*ary_ary)
|
32
|
+
@query << "chd=t:" + ary_ary.collect do |ary|
|
33
|
+
ary.join(',')
|
34
|
+
end.join("|")
|
35
|
+
end
|
36
|
+
|
37
|
+
def chs(w, h)
|
38
|
+
@query << "chs=#{w}x#{h}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def chtt(title)
|
42
|
+
@query << "chtt=#{title.gsub(/ /, '+') .gsub(/\n/m, '|')}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def method_missing(name, *args, &blk)
|
46
|
+
if /^ch/ =~ name
|
47
|
+
@query << "#{name}=#{args[0]}"
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
[URL, @query.join("&")].join("?")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
gca = GoogleChartApi.new
|
59
|
+
|
60
|
+
gca.chs(460, 200)
|
61
|
+
gca.chd_t([62,12,5,2,19])
|
62
|
+
gca.cht('lc')
|
63
|
+
gca.chxt('r')
|
64
|
+
gca.chxr('0,60,130')
|
65
|
+
gca.chxl('0:80|100|120')
|
66
|
+
gca.chxp('0,80,100,120')
|
67
|
+
|
68
|
+
puts gca
|
69
|
+
system("open '#{gca}'")
|
70
|
+
|