chatterbot 0.5.1 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -89,10 +89,10 @@ end
89
89
  @bot.config.delete(:token)
90
90
  @bot.require_login(false)
91
91
 
92
- if @bot.client.authorized?
92
+ unless @bot.screen_name.nil?
93
93
  if @botname == "tmp_bot_name"
94
- puts "change botname to #{@bot.access_token.params[:screen_name]}"
95
- @bot.botname = @bot.access_token.params[:screen_name]
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"
@@ -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
- if s.respond_to? :specification_version then
24
- s.specification_version = 3
25
-
26
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
27
- s.add_runtime_dependency(%q<twitter_oauth>, [">= 0"])
28
- s.add_development_dependency(%q<shoulda>, [">= 0"])
29
- s.add_development_dependency(%q<rspec>, [">= 0"])
30
- s.add_development_dependency(%q<rdoc>, [">= 0"])
31
- s.add_development_dependency(%q<simplecov>, [">= 0"])
32
- s.add_development_dependency(%q<watchr>, [">= 0"])
33
- else
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
 
@@ -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
@@ -1,5 +1,7 @@
1
1
  require 'yaml'
2
- require 'twitter_oauth'
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/client"
39
+ require "chatterbot/ui"
40
+ require "chatterbot/client"
38
41
  require "chatterbot/search"
39
42
  require "chatterbot/tweet"
40
43
  require "chatterbot/retweet"
@@ -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.is_a?(Hash) ? s[:text] : 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.is_a?(Hash) ? from_user(s) : s).downcase
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
@@ -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
- # update config when we exit
27
- at_exit do
28
- raise $! if $!
26
+
27
+ if reset_bot?
28
+ reset_since_id
29
29
  update_config
30
- end
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
@@ -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
- # the Twitter client
8
- attr_accessor :client
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
- return {} if since_id <= 0
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
- @client ||= TwitterOAuth::Client.new(client_params)
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
- # Ask the user to get an API key from Twitter.
63
- def get_api_key
64
- puts "****************************************"
65
- puts "****************************************"
66
- puts "**** API SETUP TIME! ****"
67
- puts "****************************************"
68
- puts "****************************************"
69
-
70
- puts "Hey, looks like you need to get an API key from Twitter before you can get started."
71
- puts "Please go to this URL: https://twitter.com/apps/new"
72
-
73
- print "\n\nPaste the 'Consumer Key' here: "
74
- STDOUT.flush
75
- config[:consumer_key] = STDIN.readline.chomp
76
-
77
- print "Paste the 'Consumer Secret' here: "
78
- STDOUT.flush
79
- config[:consumer_secret] = STDIN.readline.chomp
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
- # error message for auth
92
- def display_oauth_error
93
- debug "Oops! Looks like something went wrong there, please try again!"
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
- request_token = client.request_token
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 == false
126
- else
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