chatterbot 0.5.1 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +81 -12
- data/bin/chatterbot-register +3 -3
- data/chatterbot.gemspec +13 -28
- data/examples/echoes_bot.rb +4 -4
- data/lib/chatterbot.rb +5 -2
- data/lib/chatterbot/blacklist.rb +2 -2
- data/lib/chatterbot/bot.rb +13 -6
- data/lib/chatterbot/client.rb +103 -61
- data/lib/chatterbot/config.rb +25 -13
- data/lib/chatterbot/dsl.rb +104 -45
- data/lib/chatterbot/helpers.rb +3 -0
- data/lib/chatterbot/reply.rb +9 -13
- data/lib/chatterbot/retweet.rb +1 -1
- data/lib/chatterbot/search.rb +10 -14
- data/lib/chatterbot/tweet.rb +6 -4
- data/lib/chatterbot/ui.rb +96 -0
- data/lib/chatterbot/version.rb +1 -1
- data/spec/blacklist_spec.rb +4 -4
- data/spec/bot_spec.rb +1 -1
- data/spec/client_spec.rb +40 -10
- data/spec/config_spec.rb +24 -9
- data/spec/dsl_spec.rb +12 -6
- data/spec/helpers_spec.rb +1 -1
- data/spec/reply_spec.rb +17 -23
- data/spec/retweet_spec.rb +2 -2
- data/spec/search_spec.rb +21 -26
- data/spec/spec_helper.rb +25 -17
- data/spec/tweet_spec.rb +4 -4
- metadata +136 -18
data/README.markdown
CHANGED
@@ -7,6 +7,9 @@ pretty involved bots. It handles searches, replies and tweets, and has
|
|
7
7
|
a simple blacklist system to help keep you from spamming people who
|
8
8
|
don't want to hear from your bot.
|
9
9
|
|
10
|
+
[![Build Status](https://secure.travis-ci.org/muffinista/chatterbot.png?branch=master)](http://travis-ci.org/muffinista/chatterbot)
|
11
|
+
|
12
|
+
|
10
13
|
Features
|
11
14
|
--------
|
12
15
|
* Works via Twitter's OAuth system.
|
@@ -15,6 +18,18 @@ Features
|
|
15
18
|
* Simple blacklistling system to limit your annoyance of users
|
16
19
|
* Optionally log tweets to the database for metrics and tracking purposes
|
17
20
|
|
21
|
+
Is Writing Bots a Good Use of My Time?
|
22
|
+
======================================
|
23
|
+
|
24
|
+
Quick answer: if you're planning on being spammish on Twitter, you
|
25
|
+
should probably find something else to do. If you're planning on
|
26
|
+
writing a bot that isn't too rude or annoying, or that expects a
|
27
|
+
certain amount of opt-in from users, then you're probably good. I've
|
28
|
+
written a blog post about bots on Twitter here:
|
29
|
+
http://muffinlabs.com/2012/06/04/the-current-state-of-bots-on-twitter/
|
30
|
+
|
31
|
+
|
32
|
+
|
18
33
|
Using Chatterbot
|
19
34
|
================
|
20
35
|
|
@@ -54,6 +69,72 @@ Or, you can create a bot object yourself, extend it if needed, and use it like s
|
|
54
69
|
|
55
70
|
That's it!
|
56
71
|
|
72
|
+
Chatterbot uses the the Twitter gem
|
73
|
+
(https://github.com/sferik/twitter) to handle the underlying API
|
74
|
+
calls. Any calls to the search/reply methods will return
|
75
|
+
Twitter::Status objects, which are basically extended hashes.
|
76
|
+
|
77
|
+
What Can I Do?
|
78
|
+
--------------
|
79
|
+
|
80
|
+
Here's a list of the important methods in the Chatterbot DSL:
|
81
|
+
|
82
|
+
**search** -- You can use this to perform a search on Twitter:
|
83
|
+
|
84
|
+
search("'surely you must be joking'") do |tweet|
|
85
|
+
reply "@#{tweet_user(tweet)} I am serious, and don't call me Shirley!", tweet
|
86
|
+
end
|
87
|
+
|
88
|
+
**replies** -- Use this to check for replies and mentions:
|
89
|
+
|
90
|
+
replies do |tweet|
|
91
|
+
reply "#USER# Thanks for contacting me!", tweet
|
92
|
+
end
|
93
|
+
|
94
|
+
Note that the string #USER# will be replaced with the username of the person who sent the original tweet.
|
95
|
+
|
96
|
+
**tweet** -- send a Tweet out for this bot:
|
97
|
+
|
98
|
+
tweet "I AM A BOT!!!"
|
99
|
+
|
100
|
+
**reply** -- reply to another tweet:
|
101
|
+
|
102
|
+
reply "THIS IS A REPLY TO #USER#!", original_tweet
|
103
|
+
|
104
|
+
**retweet** -- Chatterbot can retweet tweets as well:
|
105
|
+
|
106
|
+
```rb
|
107
|
+
search "xyzzy" do |tweet|
|
108
|
+
retweet(tweet[:id])
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
|
113
|
+
**blacklist** -- you can use this to specify a list of users you don't
|
114
|
+
want to interact with. If you put the following line at the top of
|
115
|
+
your bot:
|
116
|
+
|
117
|
+
blacklist "user1, user2, user3"
|
118
|
+
|
119
|
+
None of those users will trigger your bot if they come up in a
|
120
|
+
search. However, if a user replies to one of your tweets or mentions
|
121
|
+
your bot in a tweet, you will receive that tweet when calling the
|
122
|
+
reply method.
|
123
|
+
|
124
|
+
**exclude** -- similarly, you can specify a list of words/phrases
|
125
|
+
which shouldn't trigger your bot. If you use the following:
|
126
|
+
|
127
|
+
exclude "spam"
|
128
|
+
|
129
|
+
Any tweets or mentions with the word 'spam' in them will be ignored by
|
130
|
+
the bot. If you wanted to ignore any tweets with links in them (a wise
|
131
|
+
precaution if you want to avoid spreading spam), you could call:
|
132
|
+
|
133
|
+
exclude "http://"
|
134
|
+
|
135
|
+
For more details, check out dsl.rb in the source code.
|
136
|
+
|
137
|
+
|
57
138
|
Authorization
|
58
139
|
-------------
|
59
140
|
|
@@ -162,17 +243,6 @@ end
|
|
162
243
|
has processed -- if you don't have this call, you will get duplicate
|
163
244
|
tweets.
|
164
245
|
|
165
|
-
Retweet
|
166
|
-
-------
|
167
|
-
|
168
|
-
Chatterbot can retweet the tweets found based upon the search:
|
169
|
-
|
170
|
-
```rb
|
171
|
-
search "xyzzy" do |tweet|
|
172
|
-
retweet(tweet[:id])
|
173
|
-
end
|
174
|
-
```
|
175
|
-
|
176
246
|
|
177
247
|
Database logging
|
178
248
|
----------------
|
@@ -210,7 +280,6 @@ TODO
|
|
210
280
|
|
211
281
|
* document DSL methods
|
212
282
|
* document database setup
|
213
|
-
* consider switching to Twitter gem for API
|
214
283
|
* web-based frontend for tracking bot activity
|
215
284
|
* opt-out system that adds people to blacklist if they reply to a bot
|
216
285
|
in the right way
|
data/bin/chatterbot-register
CHANGED
@@ -89,10 +89,10 @@ end
|
|
89
89
|
@bot.config.delete(:token)
|
90
90
|
@bot.require_login(false)
|
91
91
|
|
92
|
-
|
92
|
+
unless @bot.screen_name.nil?
|
93
93
|
if @botname == "tmp_bot_name"
|
94
|
-
puts "change botname to #{@bot.
|
95
|
-
@bot.botname = @bot.
|
94
|
+
puts "change botname to #{@bot.screen_name}"
|
95
|
+
@bot.botname = @bot.screen_name
|
96
96
|
end
|
97
97
|
|
98
98
|
puts "Storing config info"
|
data/chatterbot.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.authors = ["Colin Mitchell"]
|
10
10
|
s.email = %q{colin@muffinlabs.com}
|
11
11
|
s.homepage = %q{http://github.com/muffinista/chatterbot}
|
12
|
-
s.summary = %q{A framework for writing Twitter bots}
|
13
|
-
s.description = %q{A framework for writing bots that run on Twitter. Comes with a simple DSL for easy coding.}
|
12
|
+
s.summary = %q{A ruby framework for writing Twitter bots}
|
13
|
+
s.description = %q{A ruby framework for writing bots that run on Twitter. Comes with a simple DSL for easy coding.}
|
14
14
|
|
15
15
|
s.rubyforge_project = "chatterbot"
|
16
16
|
|
@@ -20,31 +20,16 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
s.licenses = ["WTFDBAL"]
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
s.add_dependency(%q<twitter_oauth>, [">= 0"])
|
35
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
36
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
37
|
-
s.add_dependency(%q<rdoc>, [">= 0"])
|
38
|
-
s.add_dependency(%q<simplecov>, [">= 0"])
|
39
|
-
s.add_dependency(%q<watchr>, [">= 0"])
|
40
|
-
end
|
41
|
-
else
|
42
|
-
s.add_dependency(%q<twitter_oauth>, [">= 0"])
|
43
|
-
s.add_dependency(%q<shoulda>, [">= 0"])
|
44
|
-
s.add_dependency(%q<rspec>, [">= 0"])
|
45
|
-
s.add_dependency(%q<rdoc>, [">= 0"])
|
46
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
47
|
-
s.add_dependency(%q<watchr>, [">= 0"])
|
48
|
-
end
|
23
|
+
s.add_runtime_dependency(%q<oauth>, [">= 0"])
|
24
|
+
s.add_runtime_dependency(%q<twitter>, [">= 3.4.1"])
|
25
|
+
s.add_runtime_dependency(%q<launchy>, [">= 2.1.2"])
|
26
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
27
|
+
s.add_development_dependency(%q<redcarpet>, [">= 0"])
|
28
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
29
|
+
s.add_development_dependency(%q<rake>, [">= 0"])
|
30
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
31
|
+
s.add_development_dependency(%q<rdoc>, [">= 0"])
|
32
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
33
|
+
s.add_development_dependency(%q<watchr>, [">= 0"])
|
49
34
|
end
|
50
35
|
|
data/examples/echoes_bot.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
## This is a very simple working example of a bot you can build with
|
4
|
+
## chatterbot. It looks for mentions of '@echoes_bot' (the twitter
|
5
|
+
## handle the bot uses), and sends replies with the name switched to
|
6
|
+
## the handle of the sending user
|
3
7
|
|
4
8
|
#
|
5
9
|
# require the dsl lib to include all the methods you see below.
|
@@ -19,10 +23,6 @@ blacklist "mean_user, private_user"
|
|
19
23
|
|
20
24
|
puts "checking for replies to me"
|
21
25
|
replies do |tweet|
|
22
|
-
|
23
|
-
# replace the incoming username with the handle of the user who tweeted us
|
24
|
-
# src = tweet[:text].gsub(/@echoes_bot/, tweet_user(tweet))
|
25
|
-
|
26
26
|
# replace the incoming username with #USER#, which will be replaced
|
27
27
|
# with the handle of the user who tweeted us by the
|
28
28
|
# replace_variables helper
|
data/lib/chatterbot.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'yaml'
|
2
|
-
require '
|
2
|
+
require 'oauth'
|
3
|
+
require 'twitter'
|
4
|
+
require 'launchy'
|
3
5
|
|
4
6
|
#
|
5
7
|
# Try and load Sequel, but don't freak out if it's not there
|
@@ -34,7 +36,8 @@ module Chatterbot
|
|
34
36
|
require "chatterbot/db"
|
35
37
|
require "chatterbot/logging"
|
36
38
|
require "chatterbot/blacklist"
|
37
|
-
require "chatterbot/
|
39
|
+
require "chatterbot/ui"
|
40
|
+
require "chatterbot/client"
|
38
41
|
require "chatterbot/search"
|
39
42
|
require "chatterbot/tweet"
|
40
43
|
require "chatterbot/retweet"
|
data/lib/chatterbot/blacklist.rb
CHANGED
@@ -22,14 +22,14 @@ module Chatterbot
|
|
22
22
|
#
|
23
23
|
# Based on the text of this tweet, should it be skipped?
|
24
24
|
def skip_me?(s)
|
25
|
-
search = s.
|
25
|
+
search = s.respond_to?(:text) ? s.text : s
|
26
26
|
exclude.detect { |e| search.downcase.include?(e) } != nil
|
27
27
|
end
|
28
28
|
|
29
29
|
#
|
30
30
|
# Is this tweet from a user on our blacklist?
|
31
31
|
def on_blacklist?(s)
|
32
|
-
search = (s.
|
32
|
+
search = (s.respond_to?(:user) ? from_user(s) : s).downcase
|
33
33
|
blacklist.any? { |b| search.include?(b.downcase) } ||
|
34
34
|
on_global_blacklist?(search)
|
35
35
|
end
|
data/lib/chatterbot/bot.rb
CHANGED
@@ -10,6 +10,7 @@ module Chatterbot
|
|
10
10
|
include Tweet
|
11
11
|
include Retweet
|
12
12
|
include Reply
|
13
|
+
include UI
|
13
14
|
include Client
|
14
15
|
include DB
|
15
16
|
include Helpers
|
@@ -22,13 +23,19 @@ module Chatterbot
|
|
22
23
|
end
|
23
24
|
|
24
25
|
@config = load_config(params)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
raise $! if $!
|
26
|
+
|
27
|
+
if reset_bot?
|
28
|
+
reset_since_id
|
29
29
|
update_config
|
30
|
-
|
30
|
+
puts "Reset to #{@config[:since_id]}"
|
31
|
+
exit
|
32
|
+
else
|
33
|
+
# update config when we exit
|
34
|
+
at_exit do
|
35
|
+
raise $! if $!
|
36
|
+
update_config
|
37
|
+
end
|
38
|
+
end
|
31
39
|
end
|
32
|
-
|
33
40
|
end
|
34
41
|
end
|
data/lib/chatterbot/client.rb
CHANGED
@@ -2,31 +2,79 @@ module Chatterbot
|
|
2
2
|
|
3
3
|
#
|
4
4
|
# routines for connecting to Twitter and validating the bot
|
5
|
+
#
|
5
6
|
module Client
|
7
|
+
attr_accessor :screen_name, :client, :search_client
|
8
|
+
|
9
|
+
#
|
10
|
+
# the main interface to the Twitter API
|
11
|
+
#
|
12
|
+
def client
|
13
|
+
@client ||= Twitter::Client.new(
|
14
|
+
:endpoint => base_url,
|
15
|
+
:consumer_key => client_params[:consumer_key],
|
16
|
+
:consumer_secret => client_params[:consumer_secret],
|
17
|
+
:oauth_token => client_params[:token],
|
18
|
+
:oauth_token_secret => client_params[:secret]
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# client for running searches -- for some reason Twitter::Client was overwriting
|
24
|
+
# the endpoint for searches in a destructive fashion, so I had two
|
25
|
+
# clients. That appears to be fixed, but if not, this takes care of that problem
|
26
|
+
#
|
27
|
+
def search_client
|
28
|
+
client
|
29
|
+
# return client
|
30
|
+
|
31
|
+
# @search_client ||= Twitter::Client.new(
|
32
|
+
# :endpoint => base_url,
|
33
|
+
# :consumer_key => client_params[:consumer_key],
|
34
|
+
# :consumer_secret => client_params[:consumer_secret],
|
35
|
+
# :oauth_token => client_params[:token],
|
36
|
+
# :oauth_token_secret => client_params[:secret]
|
37
|
+
# )
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# reset the since_id for this bot to the highest since_id we can
|
42
|
+
# get, by running a really open search and updating config with
|
43
|
+
# the max_id
|
44
|
+
#
|
45
|
+
def reset_since_id
|
46
|
+
result = search_client.search("a")
|
47
|
+
update_since_id(result)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
6
51
|
|
7
|
-
#
|
8
|
-
|
52
|
+
#
|
53
|
+
# the URL we should use for api calls
|
54
|
+
#
|
55
|
+
def base_url
|
56
|
+
"https://api.twitter.com"
|
57
|
+
end
|
9
58
|
|
10
|
-
# track the access token so we can get screen name when
|
11
|
-
# registering new bots
|
12
|
-
attr_accessor :access_token
|
13
59
|
|
14
60
|
#
|
15
61
|
# default options when querying twitter -- this could be extended
|
16
62
|
# with a language, etc.
|
17
63
|
def default_opts
|
18
|
-
|
19
|
-
{
|
20
|
-
:since_id => since_id,
|
64
|
+
opts = {
|
21
65
|
:result_type => "recent"
|
22
66
|
}
|
67
|
+
opts[:since_id] = since_id if since_id > 0
|
68
|
+
|
69
|
+
opts
|
23
70
|
end
|
24
71
|
|
25
72
|
|
26
73
|
#
|
27
|
-
# Initialize the Twitter client
|
74
|
+
# Initialize the Twitter client, and check to see if it has credentials or not
|
75
|
+
# @return true/false depending on if client has OAuth credentials
|
28
76
|
def init_client
|
29
|
-
|
77
|
+
client.credentials?
|
30
78
|
end
|
31
79
|
|
32
80
|
#
|
@@ -44,53 +92,49 @@ module Chatterbot
|
|
44
92
|
login(do_update_config)
|
45
93
|
end
|
46
94
|
|
47
|
-
#
|
48
|
-
# print out a message about getting a PIN from twitter, then output
|
49
|
-
# the URL the user needs to visit to authorize
|
50
|
-
#
|
51
|
-
def get_oauth_verifier(request_token)
|
52
|
-
puts "Please go to the following URL and authorize the bot.\n"
|
53
|
-
puts "#{request_token.authorize_url}\n"
|
54
95
|
|
55
|
-
puts "Paste your PIN and hit enter when you have completed authorization."
|
56
|
-
STDIN.readline.chomp
|
57
|
-
rescue Interrupt => e
|
58
96
|
|
97
|
+
#
|
98
|
+
# simple OAuth client for setting up with Twitter
|
99
|
+
#
|
100
|
+
def consumer
|
101
|
+
@consumer ||= OAuth::Consumer.new(
|
102
|
+
config[:consumer_key],
|
103
|
+
config[:consumer_secret],
|
104
|
+
:site => base_url
|
105
|
+
)
|
59
106
|
end
|
60
107
|
|
61
108
|
#
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
reset_client
|
82
|
-
|
83
|
-
#
|
84
|
-
# capture ctrl-c and exit without a stack trace
|
85
|
-
#
|
86
|
-
rescue Interrupt => e
|
87
|
-
# exit
|
109
|
+
# copied from t, the awesome twitter cli app
|
110
|
+
# @see https://github.com/sferik/t/blob/master/lib/t/authorizable.rb
|
111
|
+
#
|
112
|
+
def generate_authorize_url(request_token)
|
113
|
+
request = consumer.create_signed_request(:get,
|
114
|
+
consumer.authorize_path, request_token,
|
115
|
+
{:oauth_callback => 'oob'})
|
116
|
+
|
117
|
+
params = request['Authorization'].sub(/^OAuth\s+/, '').split(/,\s+/).map do |param|
|
118
|
+
key, value = param.split('=')
|
119
|
+
value =~ /"(.*?)"/
|
120
|
+
"#{key}=#{CGI::escape($1)}"
|
121
|
+
end.join('&')
|
122
|
+
|
123
|
+
"#{base_url}#{request.path}?#{params}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def request_token
|
127
|
+
@request_token ||= consumer.get_request_token
|
88
128
|
end
|
89
129
|
|
90
130
|
#
|
91
|
-
#
|
92
|
-
|
93
|
-
|
131
|
+
# query twitter for the bots screen name. we do this during the bot registration process
|
132
|
+
#
|
133
|
+
def get_screen_name
|
134
|
+
return unless @screen_name.nil?
|
135
|
+
|
136
|
+
oauth_response = @access_token.get('/1/account/verify_credentials.json')
|
137
|
+
@screen_name = JSON.parse(oauth_response.body)["screen_name"]
|
94
138
|
end
|
95
139
|
|
96
140
|
#
|
@@ -106,30 +150,28 @@ module Chatterbot
|
|
106
150
|
end
|
107
151
|
|
108
152
|
if needs_auth_token?
|
109
|
-
|
110
|
-
|
111
|
-
pin = get_oauth_verifier(request_token)
|
153
|
+
pin = get_oauth_verifier #(request_token)
|
112
154
|
return false if pin.nil?
|
113
155
|
|
114
|
-
@access_token = client.authorize(
|
115
|
-
request_token.token,
|
116
|
-
request_token.secret,
|
117
|
-
:oauth_verifier => pin
|
118
|
-
)
|
119
156
|
|
157
|
+
begin
|
158
|
+
# this will throw an error that we can try and catch
|
159
|
+
@access_token = request_token.get_access_token(:oauth_verifier => pin.chomp)
|
160
|
+
get_screen_name
|
120
161
|
|
121
|
-
if client.authorized?
|
122
162
|
config[:token] = @access_token.token
|
123
163
|
config[:secret] = @access_token.secret
|
124
164
|
|
125
|
-
update_config unless do_update_config
|
126
|
-
|
165
|
+
update_config unless ! do_update_config
|
166
|
+
reset_client
|
167
|
+
|
168
|
+
rescue OAuth::Unauthorized => e
|
127
169
|
display_oauth_error
|
128
170
|
return false
|
129
171
|
end
|
130
172
|
end
|
131
173
|
|
132
|
-
true
|
174
|
+
return true
|
133
175
|
end
|
134
176
|
end
|
135
177
|
end
|