chatterbot 0.2.8 → 0.2.9

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -7,11 +7,12 @@ gemspec
7
7
  # Add dependencies to develop your gem here.
8
8
  # Include everything needed to run rake, tests, features, etc.
9
9
  group :development do
10
+ gem 'simplecov', :require => false, :group => :test
11
+
10
12
  gem "shoulda", ">= 0"
11
13
  gem "rspec"
12
14
 
13
15
  gem "bundler", "~> 1.0.0"
14
- gem "rcov"
15
16
  gem "watchr"
16
17
  end
17
18
 
@@ -1,4 +1,5 @@
1
- = Chatterbot
1
+ Chatterbot
2
+ ===========
2
3
 
3
4
  Chatterbot is a Ruby library for making bots on Twitter. It is basic
4
5
  enough that you can put it into use quickly, but can be used to make
@@ -6,49 +7,62 @@ pretty involved bots. It handles searches, replies and tweets, and has
6
7
  a simple blacklist system to help keep you from spamming people who
7
8
  don't want to hear from your bot.
8
9
 
9
- == A quick list of features:
10
-
10
+ Features
11
+ --------
11
12
  * Works via Twitter's OAuth system.
12
13
  * Handles search queries and replies to your bot
13
14
  * Use a simple DSL, or extend a Bot class if you need it
14
15
  * Simple blacklistling system to limit your annoyance of users
15
16
  * Optionally log tweets to the database for metrics and tracking purposes
16
17
 
17
- == Using Chatterbot
18
+ Using Chatterbot
19
+ ================
18
20
 
19
- === Make a Twitter account
21
+ Make a Twitter account
22
+ ----------------------
20
23
 
21
24
  First thing you'll need to do is create an account for your bot on
22
25
  Twitter. That's the easy part.
23
26
 
24
- === Write your bot
27
+ Run the generator
28
+ -----------------
29
+
30
+ Chatterbot comes with a script named `chatterbot-register` which will
31
+ handle two tasks -- it will authorize your bot with Twitter and it
32
+ will generate a skeleton script, which you use to implement your
33
+ actual bot.
34
+
35
+ Write your bot
36
+ --------------
25
37
 
26
38
  Chatterbot has a very simple DSL inspired by Sinatra and Twibot, an
27
- earlier Twitter bot framework. Here's an example, based on @dr_rumack
28
- on Twitter (http://twitter.com/#!/Dr_Rumack):
39
+ earlier Twitter bot framework. Here's an example, based on
40
+ [@dr_rumack](http://twitter.com/#!/Dr_Rumack), an actual bot running
41
+ on Twitter:
29
42
 
30
- require 'chatterbot/dsl'
31
- search("'surely you must be joking'") do |tweet|
32
- reply "@#{tweet_user(tweet)} I am serious, and don't call me Shirley!", tweet
33
- end
43
+ require 'chatterbot/dsl'
44
+ search("'surely you must be joking'") do |tweet|
45
+ reply "@#{tweet_user(tweet)} I am serious, and don't call me Shirley!", tweet
46
+ end
34
47
 
35
- Or, you can create a bot object yourself, extend it if needed, and use
36
- it like so:
48
+ Or, you can create a bot object yourself, extend it if needed, and use it like so:
37
49
 
38
- bot = Chatterbot::Bot.new
39
- bot.search("'surely you must be joking'") do |tweet|
40
- bot.reply "@#{tweet_user(tweet)} I am serious, and don't call me Shirley!", tweet
41
- end
50
+ bot = Chatterbot::Bot.new
51
+ bot.search("'surely you must be joking'") do |tweet|
52
+ bot.reply "@#{tweet_user(tweet)} I am serious, and don't call me Shirley!", tweet
53
+ end
42
54
 
43
55
  That's it!
44
56
 
45
- === Authorize your bot
57
+ Authorization
58
+ -------------
46
59
 
47
60
  If you only want to use Chatterbot to search for tweets, it will work
48
61
  out of the box without any authorization. However, if you want to
49
62
  reply to tweets, or check for replies to your bot, you will have to
50
- jump through a few authorization hoops with Twitter. The very
51
- first time you want to setup a bot, you will need to register an
63
+ jump through a few authorization hoops with Twitter.
64
+
65
+ Before you setup a bot for the first time, you will need to register an
52
66
  application with Twitter. Twitter requires all API communication to be via an
53
67
  app which is registered on Twitter. I would set one up and make it
54
68
  part of Chatterbot, but unfortunately Twitter doesn't allow developers
@@ -56,29 +70,29 @@ to publicly post the OAuth consumer key/secret that you would need to
56
70
  use. If you're planning to run more than one bot, you only need to do
57
71
  this step once -- you can use the same app setup for other bots too.
58
72
 
59
- I've put together a {walkthrough of the process}[http://muffinlabs.com/content/walkthrough-setting-twitter-app-use-chatterbot] of setting up an app, and then authorizing your bot. It might be useful for first-time users
73
+ The helper script `chatterbot-register` will walk you through most of
74
+ this without too much hand-wringing. And, if you write a bot without
75
+ `chatterbot-register`, you'll still be sent through the authorization
76
+ process the first time you run your script. But if you prefer, here's
77
+ the instructions if you want to do it yourself:
60
78
 
79
+ 1. [Setup your own app](https://twitter.com/apps/new) on Twitter.
61
80
 
81
+ 2. Choose 'Client' as the app type.
62
82
 
83
+ 3. Choose 'Read & Write' access unless you don't need to send tweets.
63
84
 
64
- Chatterbot will walk you through what is needed to get this setup. If
65
- you run your bot without having an app setup, it will prompt you with
66
- the instructions. Here's the instructions if you want to do it yourself:
85
+ 4. Take the consumer key/consumer secret values, and either run your bot, and enter them
86
+ in when prompted, or store them in a config file for your bot. (See
87
+ below for details on this). It should look like this:
67
88
 
68
- 1. Setup your own app on Twitter at this URL https://twitter.com/apps/new
69
- 2. Choose 'Client' as the app type
70
- 3. Choose 'Read & Write' access unless you don't need to send tweets.
71
- 4. Take the consumer key/consumer secret values, and either run your
72
- bot, and enter them in when prompted, or store them in a config
73
- file for your bot. (See below for details on this). It should look
74
- like this:
89
+ ---
90
+ :consumer_secret: CONSUMER SECRET GOES HERE
91
+ :consumer_key: CONSUMER KEY GOES HERE
75
92
 
76
- ---
77
- :consumer_secret: CONSUMER SECRET GOES HERE
78
- :consumer_key: CONSUMER KEY GOES HERE
79
93
 
80
- Chatterbot will point you at the URL in Step #1, then ask for the
81
- same values as in Step #4.
94
+ When you do this via the helper script, chatterbot will point you at
95
+ the URL in Step #1, then ask you to paste the same values as in Step #4.
82
96
 
83
97
  Once this is done, you will need to setup authorization for the actual
84
98
  bot with Twitter. At the first run, you'll get a message asking you to go
@@ -90,30 +104,32 @@ bot. Hit return, and you should be all set.
90
104
  This is obviously a bunch of effort, but once you're done, you're
91
105
  ready to go!
92
106
 
93
- === Configuration
107
+ Configuration
108
+ -------------
94
109
 
95
110
  Chatterbot offers a couple different methods of storing the config for
96
111
  your bot:
97
112
 
98
113
  1. In a YAML file with the same name as the bot, so if you have
99
114
  botname.rb or a Botname class, store your config in botname.yaml
100
- 2. In a global config file at <tt>/etc/chatterbot.yml</tt> -- values stored here
115
+ 2. In a global config file at `/etc/chatterbot.yml` -- values stored here
101
116
  will apply to any bots you run.
102
117
  3. In another global config file specified in the environment variable
103
- <tt>'chatterbot_config'</tt>.
104
- 4. In a <tt>global.yml</tt> file in the same directory as your bot. This
118
+ `'chatterbot_config'`.
119
+ 4. In a `global.yml` file in the same directory as your bot. This
105
120
  gives you the ability to have a global configuration file, but keep
106
121
  it with your bots if desired.
107
122
  5. In a database. You can store your configuration in a DB, and then
108
123
  specify the connection string either in one of the global config
109
- files, or on the command-line by using the <tt>--db="db_uri"</tt>
124
+ files, or on the command-line by using the `--db="db_uri"`
110
125
  configuration option. Any calls to the database are handled by the
111
126
  Sequel gem, and MySQL and Sqlite should work. The DB URI should
112
- be in the form of <tt>mysql://username:password@host/database</tt> -- see
127
+ be in the form of `mysql://username:password@host/database` -- see
113
128
  http://sequel.rubyforge.org/rdoc/files/doc/opening_databases_rdoc.html
114
129
  for details.
115
130
 
116
- === Running Your Bot
131
+ Running Your Bot
132
+ ----------------
117
133
 
118
134
  There's a couple ways of running your bot:
119
135
 
@@ -121,51 +137,58 @@ Run it on the command-line whenever you like. Whee!
121
137
 
122
138
  Run it via cron. Here's an example of running a bot every two minutes
123
139
 
124
- */2 * * * * . ~/.bash_profile; cd /path/to/bot/; ./bot.rb
140
+ */2 * * * * . ~/.bash_profile; cd /path/to/bot/; ./bot.rb
125
141
 
126
142
  Run it as a background process. Just put the guts of your bot in a loop like this:
127
- loop do
128
- replies do |tweet|
129
- # do stuff
130
- end
131
143
 
132
- sleep 60
133
- end
144
+ loop do
145
+ replies do |tweet|
146
+ # do stuff
147
+ end
134
148
 
135
- === Database logging
149
+ sleep 60
150
+ end
151
+
152
+ Database logging
153
+ ----------------
136
154
 
137
155
  Chatterbot can log tweet activity to the database if desired. This
138
156
  can be handy for tracking what's going on with your bot. See
139
- <tt>Chatterbot::Logging</tt> for details on this.
157
+ `Chatterbot::Logging` for details on this.
140
158
 
141
159
 
142
- === Blacklists
160
+ Blacklists
161
+ ----------
143
162
 
144
163
  Not everyone wants to hear from your bot. To keep annoyances to a
145
164
  minimum, Chatterbot has a global blacklist option, as well as
146
165
  bot-specific blacklists if desired. The global blacklist is stored in
147
166
  the database, and you can add entries to it by using the
148
- <tt>chatterbot-blacklist</tt> script included with the gem.
167
+ `chatterbot-blacklist` script included with the gem.
149
168
 
150
169
  You can also specify users to skip as part of the DSL:
151
170
 
152
- require 'chatterbot'
153
- blacklist "mean_user, private_user"
171
+ require 'chatterbot'
172
+ blacklist "mean_user, private_user"
173
+
174
+ You should really respect the wishes of users who don't want to hear
175
+ from your bot, and add them to your blacklist whenever requested.
154
176
 
155
177
  There's also an 'exclude' method which can be used to add
156
178
  words/phrases you might want to ignore -- for example, if you wanted
157
179
  to ignore tweets with links, you could do something like this:
158
180
 
159
- exclude "http://"
181
+ exclude "http://"
160
182
 
161
- == TODO
183
+ TODO
184
+ ====
162
185
 
163
- * A skeleton bot generator
164
186
  * web-based frontend for tracking bot activity
165
187
  * opt-out system that adds people to blacklist if they reply to a bot
166
188
  in the right way
167
189
 
168
- == Contributing to Chatterbot
190
+ Contributing to Chatterbot
191
+ --------------------------
169
192
 
170
193
  Since this code is based off of actual Twitter bots, it's mostly
171
194
  working the way I want it to, and I might prefer to keep it that way.
@@ -174,7 +197,8 @@ fork the project and hack away. I'll pull anything back that makes
174
197
  sense if requested.
175
198
 
176
199
 
177
- == Copyright/License
200
+ Copyright/License
201
+ -----------------
178
202
 
179
203
  Copyright (c) 2011 Colin Mitchell. Chatterbot is distributed under a
180
204
  modified WTFPL licence -- it's the 'Do what the fuck you want to --
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
- $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
2
+ #$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
3
 
4
4
  require 'chatterbot'
5
5
  require 'optparse'
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env ruby
2
+ #$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'chatterbot'
5
+ require 'optparse'
6
+
7
+ include Chatterbot::Helpers
8
+ include Chatterbot::Config
9
+ include Chatterbot::DB
10
+ include Chatterbot::Logging
11
+
12
+ @force = false
13
+
14
+
15
+ #
16
+ # pull in any arguments and set them as env variables
17
+ #
18
+ opts = OptionParser.new
19
+ opts.banner = <<-EOS
20
+
21
+ Register a bot with Twitter, and generate a script to run
22
+ it. This script will walk you through the whole process.
23
+
24
+ Usage: #{File.basename($0)} [options]
25
+ EOS
26
+
27
+ opts.separator ""
28
+ opts.separator "Options:"
29
+
30
+ opts.on('-d', '--db [ARG]', "Specify a DB connection URI") { |d| ENV["chatterbot_db"] = d }
31
+ opts.on('-c', '--config [ARG]', "Specify a config file to load") { |c| ENV["chatterbot_config"] = c }
32
+ opts.on('-b', '--bot [ARG]', "Name of the bot you want to register. If you don't specify this, it will be grabbed from twitter when you authenticate") { |b| @botname = b }
33
+ opts.on('-f', '--force', "Overwrite existing config values") { @force = true }
34
+
35
+ opts.on_tail("-h", "--help", "Show this message") do
36
+ puts opts
37
+ exit
38
+ end
39
+
40
+ #
41
+ # skeleton code for a bot -- pretty basic but covers the bases
42
+ #
43
+ skeleton = <<'EOF'
44
+ #!/usr/bin/env ruby
45
+
46
+ require 'rubygems'
47
+ require 'chatterbot/dsl'
48
+
49
+ # remove this to send out tweets
50
+ debug_mode
51
+
52
+ # remove this to update the db
53
+ no_update
54
+ # remove this to get less output when running
55
+ verbose
56
+
57
+ # here's a list of users to ignore
58
+ blacklist "abc", "def"
59
+
60
+ # here's a list of things to exclude from searches
61
+ exclude "hi", "spammer", "junk"
62
+
63
+ search "keyword" do |tweet|
64
+ reply "Hey #USER# nice to meet you!", tweet
65
+ end
66
+
67
+ replies do |tweet|
68
+ reply "Yes #USER#, you are very kind to say that!", tweet
69
+ end
70
+
71
+ EOF
72
+
73
+ extra = opts.parse!(ARGV)
74
+
75
+
76
+ if @botname.nil?
77
+ @botname = "tmp_bot_name"
78
+ end
79
+
80
+ @bot = Chatterbot::Bot.new(:name => @botname)
81
+
82
+ if ! @bot.needs_auth_token? && @force == false
83
+ puts "Looks like your bot has already been authorized."
84
+ puts "If something went wrong, run #{File.basename($0)} again with the '-f' flag."
85
+ end
86
+
87
+ @bot.config.delete(:token)
88
+ @bot.require_login(false)
89
+
90
+ if @bot.client.authorized?
91
+ if @botname == "tmp_bot_name"
92
+ puts "change botname to #{@bot.access_token.params[:screen_name]}"
93
+ @bot.botname = @bot.access_token.params[:screen_name]
94
+ end
95
+
96
+ puts "Storing config info"
97
+ @bot.update_config
98
+
99
+ skel_dest = File.join(Dir.getwd, "#{@bot.botname}.rb")
100
+ if File.exist?(skel_dest)
101
+ puts "#{skel_dest} exists already, not doing anything"
102
+ else
103
+ puts "writing skeleton to #{skel_dest}"
104
+
105
+ f = File.new(skel_dest, "w")
106
+ f.write(skeleton)
107
+ f.close
108
+
109
+ File.chmod(0740, skel_dest)
110
+ end
111
+ else
112
+ puts "Looks like something went wrong, better luck next time!"
113
+ end
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ #$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ require 'chatterbot'
5
+ require 'optparse'
6
+
7
+ include Chatterbot::Helpers
8
+ include Chatterbot::Config
9
+ include Chatterbot::DB
10
+
11
+ @tweet_count = 5
12
+
13
+ #
14
+ # pull in any arguments and set them as env variables
15
+ #
16
+ opts = OptionParser.new
17
+ opts.banner = "Usage: #{File.basename($0)} [options]"
18
+
19
+ opts.separator ""
20
+ opts.separator "Specific options:"
21
+
22
+ opts.on('-d', '--db [ARG]', "Specify a DB connection URI") { |d| ENV["chatterbot_db"] = d }
23
+ opts.on('-c', '--config [ARG]', "Specify a config file to use") { |c| ENV["chatterbot_config"] = c }
24
+ opts.on('-t', '--tweets [ARG]', "How many tweets to display") { |t| @tweet_count = t.to_i }
25
+
26
+ opts.on_tail("-h", "--help", "Show this message") do
27
+ puts opts
28
+ exit
29
+ end
30
+
31
+ extra = opts.parse!(ARGV)
32
+
33
+ #
34
+ # will only work if user is tracking tweets in a db
35
+ #
36
+ if ! has_db?
37
+ puts "You need to specify a db connection!"
38
+ exit
39
+ end
40
+
41
+ #
42
+ # return total number of tweets sent in last 'days' days
43
+ #
44
+ def count_for_period(bot, days=1)
45
+ db[:tweets].filter(:bot => bot[:id]).filter{created_at > (Time.now - (days*86400))}.count
46
+ end
47
+
48
+ db[:config].each do |bot|
49
+ puts "Tweets from #{bot[:id]} #{[1,7,30].collect{|d| count_for_period(bot, d)}.join("/")}"
50
+ db[:tweets].filter(:bot => bot[:id]).order(:created_at.desc).limit(@tweet_count).each do |t|
51
+ puts "#{t[:created_at]}: #{t[:txt]} (#{t[:source_tweet]})"
52
+ end
53
+ end
data/chatterbot.gemspec CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |s|
29
29
  s.add_development_dependency(%q<rspec>, [">= 0"])
30
30
  s.add_development_dependency(%q<rdoc>, [">= 0"])
31
31
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
32
- s.add_development_dependency(%q<rcov>, [">= 0"])
32
+ s.add_development_dependency(%q<simplecov>, [">= 0"])
33
33
  s.add_development_dependency(%q<watchr>, [">= 0"])
34
34
  else
35
35
  s.add_dependency(%q<twitter_oauth>, [">= 0"])
@@ -37,7 +37,7 @@ Gem::Specification.new do |s|
37
37
  s.add_dependency(%q<rspec>, [">= 0"])
38
38
  s.add_dependency(%q<rdoc>, [">= 0"])
39
39
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
40
- s.add_dependency(%q<rcov>, [">= 0"])
40
+ s.add_dependency(%q<simplecov>, [">= 0"])
41
41
  s.add_dependency(%q<watchr>, [">= 0"])
42
42
  end
43
43
  else
data/lib/chatterbot.rb CHANGED
@@ -47,4 +47,3 @@ end
47
47
 
48
48
  # mount up
49
49
  Chatterbot.load
50
-
@@ -25,18 +25,11 @@ module Chatterbot
25
25
  search = s.is_a?(Hash) ? s[:text] : s
26
26
  exclude.detect { |e| search.downcase.include?(e) } != nil
27
27
  end
28
-
29
- #
30
- # Pull the username from a tweet hash -- this is different depending on
31
- # if we're doing a search, or parsing through replies/mentions.
32
- def tweet_user(s)
33
- s.has_key?(:from_user) ? s[:from_user] : s[:user][:screen_name]
34
- end
35
28
 
36
29
  #
37
30
  # Is this tweet from a user on our blacklist?
38
31
  def on_blacklist?(s)
39
- search = (s.is_a?(Hash) ? tweet_user(s) : s).downcase
32
+ search = (s.is_a?(Hash) ? from_user(s) : s).downcase
40
33
  blacklist.any? { |b| search.include?(b.downcase) } ||
41
34
  on_global_blacklist?(search)
42
35
  end
@@ -16,6 +16,10 @@ module Chatterbot
16
16
  #
17
17
  # Create a new bot. No options for now.
18
18
  def initialize(params={})
19
+ if params.has_key?(:name)
20
+ @botname = params.delete(:name)
21
+ end
22
+
19
23
  @config = load_config(params)
20
24
 
21
25
  # update config when we exit
@@ -7,13 +7,18 @@ module Chatterbot
7
7
  # the Twitter client
8
8
  attr_accessor :client
9
9
 
10
+ # track the access token so we can get screen name when
11
+ # registering new bots
12
+ attr_accessor :access_token
13
+
10
14
  #
11
15
  # default options when querying twitter -- this could be extended
12
16
  # with a language, etc.
13
17
  def default_opts
14
18
  return {} if since_id <= 0
15
19
  {
16
- :since_id => since_id
20
+ :since_id => since_id,
21
+ :result_type => "recent"
17
22
  }
18
23
  end
19
24
 
@@ -34,9 +39,9 @@ module Chatterbot
34
39
  #
35
40
  # Call this before doing anything that requires an authorized Twitter
36
41
  # connection.
37
- def require_login
42
+ def require_login(do_update_config=true)
38
43
  init_client
39
- login
44
+ login(do_update_config)
40
45
  end
41
46
 
42
47
  #
@@ -49,6 +54,8 @@ module Chatterbot
49
54
 
50
55
  puts "Paste your PIN and hit enter when you have completed authorization."
51
56
  STDIN.readline.chomp
57
+ rescue Interrupt => e
58
+
52
59
  end
53
60
 
54
61
  #
@@ -87,13 +94,16 @@ module Chatterbot
87
94
  # error message for auth
88
95
  def display_oauth_error
89
96
  debug "Oops! Looks like something went wrong there, please try again!"
90
- # exit
91
97
  end
92
98
 
93
99
  #
94
100
  # handle oauth for this request. if the client isn't authorized, print
95
101
  # out the auth URL and get a pin code back from the user
96
- def login
102
+ # If +do_update_config+ is false, don't udpate the bots config
103
+ # file after authorization. This defaults to true but
104
+ # chatterbot-register will pass in false because it does some
105
+ # other work before saving.
106
+ def login(do_update_config=true)
97
107
  if needs_api_key?
98
108
  get_api_key
99
109
  end
@@ -102,16 +112,20 @@ module Chatterbot
102
112
  request_token = client.request_token
103
113
 
104
114
  pin = get_oauth_verifier(request_token)
105
- access_token = client.authorize(
115
+ return false if pin.nil?
116
+
117
+ @access_token = client.authorize(
106
118
  request_token.token,
107
119
  request_token.secret,
108
120
  :oauth_verifier => pin
109
121
  )
110
122
 
123
+
111
124
  if client.authorized?
112
- config[:token] = access_token.token
113
- config[:secret] = access_token.secret
114
- update_config
125
+ config[:token] = @access_token.token
126
+ config[:secret] = @access_token.secret
127
+
128
+ update_config unless do_update_config == false
115
129
  else
116
130
  display_oauth_error
117
131
  return false
@@ -35,6 +35,13 @@ module Chatterbot
35
35
  has_sequel? && config.has_key?(:db_uri)
36
36
  end
37
37
 
38
+ def debug_mode=(d)
39
+ config[:debug_mode] = d
40
+ end
41
+ def no_update=(d)
42
+ config[:no_update] = d
43
+ end
44
+
38
45
  #
39
46
  # are we in debug mode?
40
47
  def debug_mode?
@@ -53,6 +60,10 @@ module Chatterbot
53
60
  has_config? && config.has_key?(:log_dest)
54
61
  end
55
62
 
63
+ def verbose=(v)
64
+ config[:verbose] = v
65
+ end
66
+
56
67
  def verbose?
57
68
  config[:verbose] || false
58
69
  end
@@ -132,10 +143,32 @@ module Chatterbot
132
143
  config[:token].nil?
133
144
  end
134
145
 
146
+ #
147
+ # determine if we're being called by one of our internal scripts
148
+ #
149
+ def chatterbot_helper?
150
+ File.basename($0).include?("chatterbot-")
151
+ end
152
+
153
+ #
154
+ # if we are called by a bot, we want to use the directory of that
155
+ # script. If we are called by chatterbot-register or another
156
+ # helper script, we want to use the current working directory
157
+ #
158
+ def working_dir
159
+ if chatterbot_helper?
160
+ Dir.getwd
161
+ else
162
+ File.dirname($0)
163
+ end
164
+ end
165
+
135
166
  #
136
167
  # figure out what config file to load based on the name of the bot
137
168
  def config_file
138
- File.join(File.expand_path(File.dirname($0)), "#{botname}.yml")
169
+ dest = working_dir
170
+
171
+ x = File.join(File.expand_path(dest), "#{botname}.yml")
139
172
  end
140
173
 
141
174
  #
@@ -164,7 +197,7 @@ module Chatterbot
164
197
  ENV["chatterbot_config"],
165
198
 
166
199
  # 'global' config file local to the path of the ruby script
167
- File.join(File.dirname(File.expand_path($0)), "global.yml")
200
+ File.join(working_dir, "global.yml")
168
201
  ].compact
169
202
  end
170
203
 
@@ -172,6 +205,7 @@ module Chatterbot
172
205
  # get any config from our global config files
173
206
  def global_config
174
207
  tmp = {}
208
+
175
209
  global_config_files.each { |f|
176
210
  tmp.merge!(slurp_file(f) || {})
177
211
  }
@@ -41,29 +41,63 @@ module Chatterbot
41
41
  @bot = Chatterbot::Bot.new(params)
42
42
  end
43
43
 
44
+ #
45
+ # should we send tweets?
46
+ #
47
+ def debug_mode(d=nil)
48
+ d = true if d.nil?
49
+ bot.debug_mode = d
50
+ end
51
+
52
+ #
53
+ # should we update the db with a new since_id?
54
+ #
55
+ def no_update(d=nil)
56
+ d = true if d.nil?
57
+ bot.no_update = d
58
+ end
59
+
60
+ #
61
+ # turn on/off verbose output
62
+ #
63
+ def verbose(d=nil)
64
+ d = true if d.nil?
65
+ bot.verbose = d
66
+ end
67
+
68
+ #
69
+ # take a variable list of strings and possibly arrays and turn
70
+ # them into a single flat array of strings
71
+ #
72
+ def flatten_list_of_strings(args)
73
+ args.collect do |b|
74
+ if b.is_a?(String)
75
+ # string, split on commas and turn into array
76
+ b.split(",").collect { |s| s.strip }
77
+ else
78
+ # presumably an array
79
+ b
80
+ end
81
+ end.flatten
82
+ end
83
+
44
84
  #
45
85
  # specify a bot-specific blacklist of users. accepts an array, or a
46
86
  # comma-delimited string
47
- def blacklist(b=nil)
48
- if b.is_a?(String)
49
- b = b.split(",").collect { |s| s.strip }
50
- end
51
-
52
- if b.nil? || b.empty?
87
+ def blacklist(*args)
88
+ list = flatten_list_of_strings(args)
89
+
90
+ if list.nil? || list.empty?
53
91
  bot.blacklist = []
54
92
  else
55
- bot.blacklist += b
93
+ bot.blacklist += list
56
94
  end
57
-
58
95
  end
59
96
 
60
97
  #
61
98
  # specify list of strings we will check when deciding to respond to a tweet or not
62
- def exclude(e=nil)
63
- if e.is_a?(String)
64
- e = e.split(",").collect { |s| s.strip }
65
- end
66
-
99
+ def exclude(*args)
100
+ e = flatten_list_of_strings(args)
67
101
  if e.nil? || e.empty?
68
102
  bot.exclude = []
69
103
  else
@@ -3,27 +3,53 @@ module Chatterbot
3
3
  #
4
4
  # a bunch of helper routines for bots
5
5
  module Helpers
6
+ def botname=(b)
7
+ @botname = b
8
+ end
6
9
 
7
10
  #
8
11
  # The name of the currently running bot
9
12
  def botname
10
- if self.class < Bot
13
+ if !@botname.nil?
14
+ @botname
15
+ elsif self.class < Bot
11
16
  self.class.to_s.downcase
12
17
  else
13
18
  File.basename($0,".rb")
14
19
  end
15
20
  end
16
21
 
22
+ #
23
+ # Pull the username from a tweet hash -- this is different depending on
24
+ # if we're doing a search, or parsing through replies/mentions.
25
+ def from_user(s)
26
+ return s if s.is_a?(String)
27
+ s.has_key?(:from_user) ? s[:from_user] : s[:user][:screen_name]
28
+ end
29
+
30
+
17
31
  #
18
32
  # Take the incoming tweet/user name, and turn it into something suitable for replying
19
33
  # to a user. Basically, get their handle and add a '@' to it.
20
34
  def tweet_user(tweet)
21
- if ! tweet.is_a?(String)
22
- base = tweet.has_key?(:from_user) ? tweet[:from_user] : tweet[:user][:screen_name]
35
+ base = from_user(tweet)
36
+ base =~ /^@/ ? base : "@#{base}"
37
+ end
38
+
39
+
40
+ #
41
+ # do some simple variable substitution. for now, it only handles
42
+ # replacing #USER# with the screen of the incoming tweet, but it
43
+ # could do more if needed
44
+ #
45
+ def replace_variables(txt, original = nil)
46
+ if ! original.nil? && txt.include?("#USER#")
47
+ username = tweet_user(original)
48
+ txt.gsub("#USER#", username)
23
49
  else
24
- base = tweet
50
+ txt
25
51
  end
26
- base =~ /^@/ ? base : "@#{base}"
27
52
  end
53
+
28
54
  end
29
55
  end
@@ -8,7 +8,7 @@ module Chatterbot
8
8
  def replies(&block)
9
9
  return unless require_login
10
10
  debug "check for replies since #{since_id}"
11
-
11
+
12
12
  opts = since_id > 0 ? {:since_id => since_id} : {}
13
13
  results = client.replies(opts)
14
14
 
@@ -4,6 +4,13 @@ module Chatterbot
4
4
  # handle Twitter searches
5
5
  module Search
6
6
 
7
+ #
8
+ # modify a query string to exclude retweets from searches
9
+ #
10
+ def exclude_retweets(q)
11
+ q.include?("include:retweets") ? q : q += " -include:retweets"
12
+ end
13
+
7
14
  # internal search code
8
15
  def search(queries, opts = {}, &block)
9
16
  return unless init_client
@@ -18,13 +25,18 @@ module Chatterbot
18
25
  # search twitter
19
26
  #
20
27
  queries.each { |query|
21
- search = client.search(query, opts.merge(default_opts))
28
+ debug "search: #{query} #{opts.merge(default_opts)}"
29
+
30
+ search = client.search(exclude_retweets(query), opts.merge(default_opts))
22
31
  update_since_id(search)
23
32
 
24
33
  if search != nil
25
34
  search["results"].each { |s|
26
35
  s.symbolize_keys!
36
+ debug s[:text]
37
+ #if s[:text].downcase.include?(query.downcase)
27
38
  yield s unless ! block_given? || on_blacklist?(s) || skip_me?(s)
39
+ #end
28
40
  }
29
41
  end
30
42
  }
@@ -3,11 +3,13 @@ module Chatterbot
3
3
  #
4
4
  # routines for sending tweets
5
5
  module Tweet
6
-
6
+
7
7
  # simple wrapper for sending a message
8
8
  def tweet(txt, params = {}, original = nil)
9
9
  return if require_login == false
10
10
 
11
+ txt = replace_variables(txt, original)
12
+
11
13
  if debug_mode?
12
14
  debug "I'm in debug mode, otherwise I would tweet: #{txt}"
13
15
  else
@@ -1,3 +1,3 @@
1
1
  module Chatterbot
2
- VERSION = "0.2.8"
2
+ VERSION = "0.2.9"
3
3
  end
data/spec/config_spec.rb CHANGED
@@ -75,6 +75,28 @@ describe "Chatterbot::Config" do
75
75
  end
76
76
  end
77
77
 
78
+ describe "debug_mode=" do
79
+ it "works" do
80
+ @bot.debug_mode = true
81
+ @bot.config[:debug_mode].should == true
82
+ end
83
+ end
84
+
85
+ describe "no_update=" do
86
+ it "works" do
87
+ @bot.no_update = true
88
+ @bot.config[:no_update].should == true
89
+ end
90
+ end
91
+
92
+ describe "verbose=" do
93
+ it "works" do
94
+ @bot.verbose = true
95
+ @bot.config[:verbose].should == true
96
+ end
97
+ end
98
+
99
+
78
100
  describe "debug_mode?" do
79
101
  it "works when debug_mode isn't set" do
80
102
  @bot.debug_mode?.should == false
@@ -181,7 +203,19 @@ describe "Chatterbot::Config" do
181
203
  end
182
204
  end
183
205
 
206
+
207
+ describe "working_dir" do
208
+ it "returns getwd for chatterbot scripts" do
209
+ @bot.should_receive(:chatterbot_helper?).and_return(true)
210
+ @bot.working_dir.should == Dir.getwd
211
+ end
184
212
 
213
+ it "returns calling dir for non-chatterbot scripts" do
214
+ @bot.should_receive(:chatterbot_helper?).and_return(false)
215
+ @bot.working_dir.should == File.dirname($0)
216
+ end
217
+ end
218
+
185
219
  describe "file I/O" do
186
220
  it "loads in some YAML" do
187
221
  tmp = {:since_id => 0}
@@ -216,5 +250,21 @@ describe "Chatterbot::Config" do
216
250
  @bot.update_config
217
251
  end
218
252
  end
253
+
254
+ describe "store_local_config" do
255
+ before(:each) do
256
+ tmp = {:x => 123, :foo => :bar}
257
+
258
+ @src = Tempfile.new("config")
259
+
260
+ @bot.stub!(:config_file).and_return(@src.path)
261
+ @bot.stub!(:config_to_save).and_return(tmp)
262
+ end
263
+
264
+ it "should work" do
265
+ @bot.store_local_config
266
+ @bot.slurp_file(@src.path).should == { :x => 123, :foo => :bar }
267
+ end
268
+ end
219
269
 
220
270
  end
data/spec/dsl_spec.rb CHANGED
@@ -25,7 +25,26 @@ describe "Chatterbot::DSL" do
25
25
  blacklist "foo, bar"
26
26
  end
27
27
  end
28
-
28
+
29
+ [:no_update, :debug_mode, :verbose].each do |method|
30
+ describe method.to_s do
31
+ it "#{method.to_s} with nil passes along true to bot object" do
32
+ @bot.should_receive("#{method.to_s}=").with(true)
33
+ send method
34
+ end
35
+
36
+ it "#debug_mode with false is passed" do
37
+ @bot.should_receive("#{method.to_s}=").with(false)
38
+ send method, false
39
+ end
40
+
41
+ it "#debug_mode with true is passed" do
42
+ @bot.should_receive("#{method.to_s}=").with(true)
43
+ send method, true
44
+ end
45
+ end
46
+ end
47
+
29
48
  describe "exclude" do
30
49
  it "#exclude passes along to bot object" do
31
50
  @bot.should_receive(:exclude=).with(["foo"])
data/spec/helpers_spec.rb CHANGED
@@ -18,15 +18,39 @@ describe "Chatterbot::Helpers" do
18
18
  bot.tweet_user("foo").should == "@foo"
19
19
  bot.tweet_user("@foo").should == "@foo"
20
20
  end
21
-
21
+
22
+ describe "#from_user" do
23
+ before(:each) do
24
+ @bot = Chatterbot::Bot.new
25
+ end
26
+
27
+ it "should accept strings" do
28
+ @bot.from_user("x").should == "x"
29
+ end
30
+
31
+ it "should accept :from_user" do
32
+ @bot.from_user(:from_user => "x").should == "x"
33
+ end
34
+
35
+ it "should accept :screen_name" do
36
+ @bot.from_user(:user => {:screen_name => "x"}).should == "x"
37
+ end
38
+ end
39
+
22
40
  describe "#botname" do
23
41
  before(:each) do
42
+ class TestBot < Chatterbot::Bot; end
24
43
  @bot = Chatterbot::Bot.new
25
44
  @bot.client = mock(Object)
26
45
  end
27
46
 
47
+ it "can set botname" do
48
+ @bot = TestBot.new
49
+ @bot.botname = "foo"
50
+ @bot.botname.should == "foo"
51
+ end
52
+
28
53
  it "calls botname smartly for inherited classes" do
29
- class TestBot < Chatterbot::Bot; end
30
54
  @bot2 = TestBot.new
31
55
  @bot2.botname.should == "testbot"
32
56
  end
@@ -35,6 +59,30 @@ describe "Chatterbot::Helpers" do
35
59
  File.should_receive(:basename).and_return("bot")
36
60
  @bot.botname.should == "bot"
37
61
  end
62
+
63
+ it "uses specified name" do
64
+ @bot = Chatterbot::Bot.new(:name => 'xyzzy')
65
+ @bot.botname.should == "xyzzy"
66
+ end
38
67
  end
39
-
68
+
69
+ describe "#replace_variables" do
70
+ before(:each) do
71
+ @bot = Chatterbot::Bot.new(:name => 'xyzzy')
72
+ @tweet = {}
73
+ end
74
+
75
+ it "should replace username by calling tweet_user" do
76
+ @bot.should_receive(:tweet_user).and_return("@foobar")
77
+ @bot.replace_variables("#USER#", @tweet).should == "@foobar"
78
+ end
79
+
80
+ it "should be fine with not replacing anything" do
81
+ @bot.replace_variables("foobar", @tweet).should == "foobar"
82
+ end
83
+
84
+ it "should be fine without a tweet" do
85
+ @bot.replace_variables("foobar").should == "foobar"
86
+ end
87
+ end
40
88
  end
data/spec/reply_spec.rb CHANGED
@@ -72,6 +72,16 @@ describe "Chatterbot::Reply" do
72
72
 
73
73
  bot.replies
74
74
  end
75
-
75
+
76
+
77
+ it "outputs an error if we get one from API" do
78
+ bot = test_bot
79
+ bot.stub!(:require_login).and_return(true)
80
+ bot.stub!(:client).and_return(mock(Object, :replies => {"error" => "You messed up"}))
81
+
82
+ bot.should_receive(:critical).with("You messed up")
83
+
84
+ bot.replies
85
+ end
76
86
 
77
87
  end
data/spec/search_spec.rb CHANGED
@@ -1,12 +1,30 @@
1
1
  require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
2
 
3
3
  describe "Chatterbot::Search" do
4
+ describe "exclude_retweets" do
5
+ before(:each) do
6
+ @bot = Chatterbot::Bot.new
7
+ end
8
+
9
+ it "should tack onto query" do
10
+ @bot.exclude_retweets("foo").should == ("foo -include:retweets")
11
+ end
12
+
13
+ it "shouldn't tack onto query" do
14
+ @bot.exclude_retweets("foo -include:retweets").should == ("foo -include:retweets")
15
+ end
16
+
17
+ it "shouldn't tack onto query" do
18
+ @bot.exclude_retweets("foo include:retweets").should == ("foo include:retweets")
19
+ end
20
+ end
21
+
4
22
  it "calls search" do
5
23
  bot = Chatterbot::Bot.new
6
24
  bot.should_receive(:search)
7
25
  bot.search("foo")
8
26
  end
9
-
27
+
10
28
  it "calls init_client" do
11
29
  bot = test_bot
12
30
  bot.should_receive(:init_client).and_return(false)
@@ -27,8 +45,8 @@ describe "Chatterbot::Search" do
27
45
  #bot = Chatterbot::Bot.new
28
46
 
29
47
  bot.stub!(:client).and_return(fake_search(100))
30
- bot.client.should_receive(:search).with("foo", {})
31
- bot.client.should_receive(:search).with("bar", {})
48
+ bot.client.should_receive(:search).with("foo -include:retweets", {})
49
+ bot.client.should_receive(:search).with("bar -include:retweets", {})
32
50
 
33
51
  bot.search(["foo", "bar"])
34
52
  end
@@ -37,7 +55,7 @@ describe "Chatterbot::Search" do
37
55
  bot = test_bot
38
56
 
39
57
  bot.stub!(:client).and_return(fake_search(100))
40
- bot.client.should_receive(:search).with("foo", {:lang => "en"})
58
+ bot.client.should_receive(:search).with("foo -include:retweets", {:lang => "en"})
41
59
 
42
60
  bot.search("foo", :lang => "en")
43
61
  end
@@ -46,7 +64,7 @@ describe "Chatterbot::Search" do
46
64
  bot = test_bot
47
65
 
48
66
  bot.stub!(:client).and_return(fake_search(100))
49
- bot.client.should_receive(:search).with("foo", {})
67
+ bot.client.should_receive(:search).with("foo -include:retweets", {})
50
68
 
51
69
  bot.search("foo")
52
70
  end
@@ -56,7 +74,7 @@ describe "Chatterbot::Search" do
56
74
  bot.stub!(:since_id).and_return(123)
57
75
 
58
76
  bot.stub!(:client).and_return(fake_search(100))
59
- bot.client.should_receive(:search).with("foo", {:since_id => 123})
77
+ bot.client.should_receive(:search).with("foo -include:retweets", {:since_id => 123, :result_type => "recent"})
60
78
 
61
79
  bot.search("foo")
62
80
  end
data/spec/spec_helper.rb CHANGED
@@ -1,14 +1,16 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
- $LOAD_PATH.unshift(File.dirname(__FILE__))
1
+ require 'simplecov'
2
+ SimpleCov.start
3
+ #$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ #$LOAD_PATH.unshift(File.dirname(__FILE__))
3
5
 
4
6
  require 'bundler/setup'
5
7
  Bundler.require
6
8
 
7
- require 'rspec'
8
- require 'chatterbot'
9
9
 
10
- require "twitter_oauth"
10
+ #require 'rspec'
11
+ #require 'chatterbot'
11
12
 
13
+ require "twitter_oauth"
12
14
 
13
15
  require 'tempfile'
14
16
  require 'sqlite3'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chatterbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.2.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-06-15 00:00:00.000000000Z
12
+ date: 2011-10-15 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: twitter_oauth
16
- requirement: &23683200 !ruby/object:Gem::Requirement
16
+ requirement: &9989900 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *23683200
24
+ version_requirements: *9989900
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: shoulda
27
- requirement: &23682700 !ruby/object:Gem::Requirement
27
+ requirement: &9989400 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *23682700
35
+ version_requirements: *9989400
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &23682220 !ruby/object:Gem::Requirement
38
+ requirement: &9988920 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *23682220
46
+ version_requirements: *9988920
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rdoc
49
- requirement: &23681740 !ruby/object:Gem::Requirement
49
+ requirement: &9988440 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *23681740
57
+ version_requirements: *9988440
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: bundler
60
- requirement: &23681260 !ruby/object:Gem::Requirement
60
+ requirement: &9987960 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.0.0
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *23681260
68
+ version_requirements: *9987960
69
69
  - !ruby/object:Gem::Dependency
70
- name: rcov
71
- requirement: &23711280 !ruby/object:Gem::Requirement
70
+ name: simplecov
71
+ requirement: &9987480 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *23711280
79
+ version_requirements: *9987480
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: watchr
82
- requirement: &23710800 !ruby/object:Gem::Requirement
82
+ requirement: &9987000 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,12 +87,14 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *23710800
90
+ version_requirements: *9987000
91
91
  description: A framework for writing bots that run on Twitter. Comes with a simple
92
92
  DSL for easy coding.
93
93
  email: colin@muffinlabs.com
94
94
  executables:
95
95
  - chatterbot-blacklist
96
+ - chatterbot-register
97
+ - chatterbot-status
96
98
  extensions: []
97
99
  extra_rdoc_files: []
98
100
  files:
@@ -100,9 +102,11 @@ files:
100
102
  - .gitignore
101
103
  - Gemfile
102
104
  - LICENSE.txt
103
- - README.rdoc
105
+ - README.markdown
104
106
  - Rakefile
105
107
  - bin/chatterbot-blacklist
108
+ - bin/chatterbot-register
109
+ - bin/chatterbot-status
106
110
  - chatterbot.gemspec
107
111
  - examples/class_bot.rb
108
112
  - examples/config.yml.example
@@ -155,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
155
159
  version: '0'
156
160
  requirements: []
157
161
  rubyforge_project: chatterbot
158
- rubygems_version: 1.8.3
162
+ rubygems_version: 1.8.6
159
163
  signing_key:
160
164
  specification_version: 3
161
165
  summary: A framework for writing Twitter bots