bot_nyan 0.1.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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +63 -0
- data/Rakefile +1 -0
- data/TODO.md +5 -0
- data/bot_nyan.gemspec +22 -0
- data/lib/bot_nyan.rb +4 -0
- data/lib/bot_nyan/base.rb +304 -0
- data/lib/bot_nyan/main.rb +18 -0
- data/lib/bot_nyan/version.rb +3 -0
- metadata +88 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Taiki ONO
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# About
|
2
|
+
|
3
|
+
Bot_nyan is simple twitter-bot-framework with DSL like Sinatra.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'bot_nyan'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install bot_nyan
|
18
|
+
|
19
|
+
Or clone and build yourself:
|
20
|
+
|
21
|
+
$ git clone git://github.com/taiki45/bot_nyan.git && cd bot_nyan
|
22
|
+
|
23
|
+
$ gem build bot_nyan.gemspec
|
24
|
+
|
25
|
+
$ rake install
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
Simple echo and say-hello Bot.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# bot.rb
|
33
|
+
# -*- encoding: utf-8 -*-
|
34
|
+
require 'bot_nyan'
|
35
|
+
|
36
|
+
set :consumer_key, {:key => 'XXXXXX',
|
37
|
+
:secret => 'XXXXXX'}
|
38
|
+
set :access_token, {:token => 'XXXXXX',
|
39
|
+
:secret => 'XXXXXX'}
|
40
|
+
set :name, 'my_bot_name'
|
41
|
+
|
42
|
+
on_matched_reply /(^@my_bot_name\s)(Hello)/u do |status, user|
|
43
|
+
reply "@#{user.screen_name} Hello!"
|
44
|
+
end
|
45
|
+
|
46
|
+
on_replied do |status, user|
|
47
|
+
reply "@#{user.screen_name} #{status.text}"
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
And Simply do it.
|
52
|
+
|
53
|
+
```
|
54
|
+
$ ruby bot.rb
|
55
|
+
```
|
56
|
+
|
57
|
+
## Contributing
|
58
|
+
|
59
|
+
1. Fork it
|
60
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
61
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
62
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
63
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/TODO.md
ADDED
data/bot_nyan.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'bot_nyan/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "bot_nyan"
|
8
|
+
gem.version = BotNyan::VERSION
|
9
|
+
gem.authors = ["Taiki ONO"]
|
10
|
+
gem.email = ["taiks.4559@gmail.com"]
|
11
|
+
gem.description = %q{Bot_nyan is quickly creating twitter-bot in Ruby with Sinatra like DSL}
|
12
|
+
gem.summary = %q{Classy twitter-bot-framework in a DSL}
|
13
|
+
gem.homepage = "http://taiki45.github.com/bot_nyan"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency 'oauth', "~>0.4.7"
|
21
|
+
gem.add_dependency 'twitter', "~>3.7.0"
|
22
|
+
end
|
data/lib/bot_nyan.rb
ADDED
@@ -0,0 +1,304 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'net/https'
|
3
|
+
require 'uri'
|
4
|
+
require 'oauth'
|
5
|
+
require 'twitter'
|
6
|
+
require 'json'
|
7
|
+
require 'logger'
|
8
|
+
|
9
|
+
require "bot_nyan/version"
|
10
|
+
|
11
|
+
module BotNyan
|
12
|
+
|
13
|
+
# For print debugs, infos, warns
|
14
|
+
module Info
|
15
|
+
def logger_set!(cond)
|
16
|
+
@logger = Logger.new STDOUT
|
17
|
+
if cond
|
18
|
+
@logger.level = Logger::DEBUG
|
19
|
+
else
|
20
|
+
@logger.level = Logger::INFO
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def info(msg)
|
25
|
+
@logger.info msg
|
26
|
+
end
|
27
|
+
|
28
|
+
def warn(msg)
|
29
|
+
@logger.warn msg
|
30
|
+
end
|
31
|
+
|
32
|
+
def error(msg)
|
33
|
+
@logger.error msg
|
34
|
+
end
|
35
|
+
|
36
|
+
def debug(msg)
|
37
|
+
@logger.debug msg
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Base
|
42
|
+
include BotNyan::Info
|
43
|
+
|
44
|
+
def self.run!
|
45
|
+
self.new.run
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
logger_set! debug?
|
50
|
+
end
|
51
|
+
|
52
|
+
def debug?
|
53
|
+
nil
|
54
|
+
end
|
55
|
+
|
56
|
+
def run
|
57
|
+
@wrapper = set_wrapper
|
58
|
+
info "starting bot for @#{name}"
|
59
|
+
begin
|
60
|
+
loop do
|
61
|
+
begin
|
62
|
+
@wrapper.connect do |event|
|
63
|
+
catch :halt do
|
64
|
+
debug event.event
|
65
|
+
if event.text and event.text.match /(^@#{name}\s)/u
|
66
|
+
debug "tweet event"
|
67
|
+
debug event
|
68
|
+
match? event
|
69
|
+
else
|
70
|
+
debug 'not tweets'
|
71
|
+
debug event
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
rescue Timeout::Error
|
76
|
+
info "reconnectting to twitter..."
|
77
|
+
sleep 30
|
78
|
+
end
|
79
|
+
end
|
80
|
+
rescue Interrupt
|
81
|
+
info "\nexitting bot service for @#{name}..."
|
82
|
+
exit 0
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_wrapper
|
87
|
+
Wrapper::TwitterWrapper.new name, consumer_key, access_token, debug?
|
88
|
+
end
|
89
|
+
|
90
|
+
def match?(event)
|
91
|
+
get_matched_reply_actions.each do |regexp, block|
|
92
|
+
if event.text.match regexp
|
93
|
+
debug "matched to #{regexp}"
|
94
|
+
instance_exec event, event.user, &block
|
95
|
+
throw :halt
|
96
|
+
end
|
97
|
+
end
|
98
|
+
if event.text.match(/(^@#{name}\s)/u) and get_relpy_action
|
99
|
+
debug "respond to default reply"
|
100
|
+
instance_exec event, event.user, &get_relpy_action
|
101
|
+
throw :halt
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Inner methods and called from given blocks
|
106
|
+
def status
|
107
|
+
@wrapper.status
|
108
|
+
end
|
109
|
+
|
110
|
+
def update(msg)
|
111
|
+
@wrapper.update msg
|
112
|
+
end
|
113
|
+
|
114
|
+
def reply(msg)
|
115
|
+
@wrapper.reply msg
|
116
|
+
end
|
117
|
+
|
118
|
+
# Inner methods that call self.class methods
|
119
|
+
def get_matched_reply_actions
|
120
|
+
self.class.get_matched_reply_actions
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_relpy_action
|
124
|
+
self.class.get_relpy_action
|
125
|
+
end
|
126
|
+
|
127
|
+
def add_on_replied(&block)
|
128
|
+
@replied_action = block
|
129
|
+
end
|
130
|
+
|
131
|
+
class << self
|
132
|
+
# Outer methods that called from inner of Base
|
133
|
+
def get_matched_reply_actions
|
134
|
+
@matched_reply_actions
|
135
|
+
end
|
136
|
+
|
137
|
+
def get_relpy_action
|
138
|
+
@reply_action
|
139
|
+
end
|
140
|
+
|
141
|
+
# Outer methods that called from main objrct
|
142
|
+
def on_matched_reply(regexp, &block)
|
143
|
+
@matched_reply_actions ||= {}
|
144
|
+
@matched_reply_actions[regexp] = block
|
145
|
+
end
|
146
|
+
|
147
|
+
def on_replied(&block)
|
148
|
+
@reply_action ||= block
|
149
|
+
end
|
150
|
+
|
151
|
+
def set(key, value)
|
152
|
+
keys = [:consumer_key, :access_token, :name, :debug?]
|
153
|
+
if keys.include? key
|
154
|
+
self.instance_eval do
|
155
|
+
define_method key, lambda { value }
|
156
|
+
private key
|
157
|
+
end
|
158
|
+
else
|
159
|
+
raise NotImplementedError, "This option is not support, #{key}, #{value}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def set!(key, value)
|
164
|
+
keys = [:run?]
|
165
|
+
if keys.include? key
|
166
|
+
define_singleton_method key, value
|
167
|
+
else
|
168
|
+
raise NotImplementedError, "This option is not support, #{key}, #{value}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Wrapper module
|
175
|
+
# it wrapping twitter connect or update methods
|
176
|
+
module Wrapper
|
177
|
+
class TwitterWrapper
|
178
|
+
include BotNyan::Info
|
179
|
+
|
180
|
+
def initialize(name, consumer_keys, access_tokens, cond)
|
181
|
+
logger_set! cond
|
182
|
+
@name = name
|
183
|
+
unless name and consumer_keys and access_tokens
|
184
|
+
error @name, @consumer_keys, @access_tokens
|
185
|
+
raise RuntimeError, "Necessarys are not difined!"
|
186
|
+
end
|
187
|
+
@consumer = OAuth::Consumer.new(
|
188
|
+
consumer_keys[:key],
|
189
|
+
consumer_keys[:secret],
|
190
|
+
:site => 'http://twitter.com'
|
191
|
+
)
|
192
|
+
@access_token = OAuth::AccessToken.new(
|
193
|
+
@consumer,
|
194
|
+
access_tokens[:token],
|
195
|
+
access_tokens[:secret]
|
196
|
+
)
|
197
|
+
Twitter.configure do |c|
|
198
|
+
c.consumer_key = consumer_keys[:key]
|
199
|
+
c.consumer_secret = consumer_keys[:secret]
|
200
|
+
c.oauth_token = access_tokens[:token]
|
201
|
+
c.oauth_token_secret = access_tokens[:secret]
|
202
|
+
end
|
203
|
+
@json = nil
|
204
|
+
end
|
205
|
+
|
206
|
+
def connect
|
207
|
+
uri = URI.parse("https://userstream.twitter.com/2/user.json?track=#{@name}")
|
208
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
209
|
+
https.use_ssl = true
|
210
|
+
|
211
|
+
https.start do |https|
|
212
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
213
|
+
request["User-Agent"] = "bot servise for @#{@name}"
|
214
|
+
request.oauth!(https, @consumer, @access_token)
|
215
|
+
|
216
|
+
buf = String.new
|
217
|
+
https.request(request) do |response|
|
218
|
+
raise Exception.new "Authorize failed. #{request.body}" if response.code == '401'
|
219
|
+
response.read_body do |chunk|
|
220
|
+
buf << chunk
|
221
|
+
while (line = buf[/.+?(\r\n)+/m]) != nil
|
222
|
+
begin
|
223
|
+
buf.sub!(line,"")
|
224
|
+
line.strip!
|
225
|
+
status = JSON.parse(line)
|
226
|
+
rescue
|
227
|
+
break
|
228
|
+
end
|
229
|
+
@json = status
|
230
|
+
yield status
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# wrapping methods for twitter state
|
238
|
+
def status
|
239
|
+
@json
|
240
|
+
end
|
241
|
+
|
242
|
+
def update(msg)
|
243
|
+
update_core :update, msg, @json
|
244
|
+
end
|
245
|
+
|
246
|
+
def reply(msg)
|
247
|
+
update_core :reply, msg, @json
|
248
|
+
end
|
249
|
+
|
250
|
+
def update_core(mode, msg, json)
|
251
|
+
i = 0
|
252
|
+
if mode == :update
|
253
|
+
post_text = lambda do |m|
|
254
|
+
@access_token.post(
|
255
|
+
'/statuses/update.json',
|
256
|
+
'status' => m,
|
257
|
+
)
|
258
|
+
end
|
259
|
+
elsif mode == :reply
|
260
|
+
post_text = lambda do |m|
|
261
|
+
@access_token.post(
|
262
|
+
'/statuses/update.json',
|
263
|
+
'status' => m,
|
264
|
+
'in_reply_to_status_id' => json['id']
|
265
|
+
)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
while post_text.call(msg).code == '403' do
|
269
|
+
sleep 0.3
|
270
|
+
i += 1
|
271
|
+
msg << " ."
|
272
|
+
if i > 12
|
273
|
+
puts "error to post reply to below"
|
274
|
+
return false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
puts "replied to #{json['id']}"
|
278
|
+
true
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
# Delegator module
|
284
|
+
# it delegate some DSL methods to main Object
|
285
|
+
module Delegator
|
286
|
+
def self.delegate(*methods)
|
287
|
+
methods.each do |method_name|
|
288
|
+
define_method(method_name) do |*args, &block|
|
289
|
+
return super(*args, &block) if respond_to? method_name
|
290
|
+
Bot.send(method_name, *args, &block)
|
291
|
+
end
|
292
|
+
private method_name
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
delegate :set, :on_matched_reply, :on_replied
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
class Hash
|
301
|
+
def method_missing(name, *args)
|
302
|
+
self[name.to_s]
|
303
|
+
end
|
304
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'bot_nyan/base'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
module BotNyan
|
7
|
+
class Bot < Base
|
8
|
+
set! :run?, lambda { __FILE__ == $0 }
|
9
|
+
if ARGV.any?
|
10
|
+
OptionParser.new do |op|
|
11
|
+
op.on('-d', 'set the debug print is on') { set :debug?, true }
|
12
|
+
end.parse!(ARGV.dup)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
at_exit { BotNyan::Bot.run! if $!.nil? }
|
16
|
+
end
|
17
|
+
|
18
|
+
extend BotNyan::Delegator
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bot_nyan
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Taiki ONO
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: oauth
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.4.7
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.4.7
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: twitter
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 3.7.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 3.7.0
|
46
|
+
description: Bot_nyan is quickly creating twitter-bot in Ruby with Sinatra like DSL
|
47
|
+
email:
|
48
|
+
- taiks.4559@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- TODO.md
|
59
|
+
- bot_nyan.gemspec
|
60
|
+
- lib/bot_nyan.rb
|
61
|
+
- lib/bot_nyan/base.rb
|
62
|
+
- lib/bot_nyan/main.rb
|
63
|
+
- lib/bot_nyan/version.rb
|
64
|
+
homepage: http://taiki45.github.com/bot_nyan
|
65
|
+
licenses: []
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 1.8.23
|
85
|
+
signing_key:
|
86
|
+
specification_version: 3
|
87
|
+
summary: Classy twitter-bot-framework in a DSL
|
88
|
+
test_files: []
|