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.
@@ -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