chatterbot 0.5.1 → 0.6.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/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
|
+
[](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
|