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.
@@ -41,6 +41,13 @@ module Chatterbot
41
41
  def no_update=(d)
42
42
  config[:no_update] = d
43
43
  end
44
+
45
+ #
46
+ # should we reset the since_id for this bot?
47
+ #
48
+ def reset_bot?
49
+ config[:reset_since_id] || false
50
+ end
44
51
 
45
52
  #
46
53
  # are we in debug mode?
@@ -105,19 +112,24 @@ module Chatterbot
105
112
  # update the since_id with either the highest ID of the specified
106
113
  # tweets, unless it is lower than what we have already
107
114
  def update_since_id(search)
108
- unless search.nil?
109
- tmp_id = case
110
- # incoming tweets
111
- when search.has_key?(:id) then search[:id]
112
-
113
- # incoming searches
114
- when search.has_key?("max_id") then search["max_id"]
115
-
116
- # other?
117
- else 1
118
- end.to_i
119
- config[:tmp_since_id] = [config[:tmp_since_id].to_i, tmp_id].max
120
- end
115
+ return if search.nil?
116
+
117
+ tmp_id = case
118
+ # Twitter::SearchResults
119
+ when search.respond_to?(:max_id) then search.max_id
120
+
121
+ # incoming tweets
122
+ when search.respond_to?(:id) then search.id
123
+
124
+ # an enumeration
125
+ when search.respond_to?(:max) then search.max { |a, b| a.id <=> b.id }.id
126
+
127
+ # probably an actual tweet ID at this point,
128
+ # otherwise it will fail and return 0
129
+ else search
130
+ end.to_i
131
+
132
+ config[:tmp_since_id] = [config[:tmp_since_id].to_i, tmp_id].max
121
133
  end
122
134
 
123
135
  #
@@ -6,6 +6,60 @@ module Chatterbot
6
6
  # very basic DSL to handle the common stuff you would want to do with a bot.
7
7
  module DSL
8
8
 
9
+ #
10
+ # search twitter for the specified terms, then pass any matches to
11
+ # the block.
12
+ # @example
13
+ # search("chatterbot is cool!") do |tweet|
14
+ # puts tweet[:text] # this is the actual tweeted text
15
+ # reply "I agree!", tweet
16
+ # end
17
+ def search(query, opts = {}, &block)
18
+ bot.search(query, opts, &block)
19
+ end
20
+
21
+ #
22
+ # handle replies to the bot. Each time this is called, chatterbot
23
+ # will pass any replies since the last call to the specified block
24
+ #
25
+ # @example
26
+ # replies do |tweet|
27
+ # puts tweet[:text] # this is the actual tweeted text
28
+ # reply "Thanks for the mention!", tweet
29
+ # end
30
+ def replies(&block)
31
+ bot.replies(&block)
32
+ end
33
+
34
+ #
35
+ # send a tweet
36
+ #
37
+ # @param [String] txt the text you want to tweet
38
+ # @param [Hash] params opts for the tweet
39
+ # @see http://rdoc.info/gems/twitter/Twitter/API#update-instance_method
40
+ # @param [Tweet] original if this is a reply, the original tweet. this will
41
+ # be used for variable substitution, and for logging
42
+ def tweet(txt, params = {}, original = nil)
43
+ bot.tweet(txt, params, original)
44
+ end
45
+
46
+ #
47
+ # retweet a tweet
48
+ # @param [id] id the ID of the tweet
49
+ def retweet(id)
50
+ bot.retweet(id)
51
+ end
52
+
53
+ #
54
+ # reply to a tweet
55
+ #
56
+ # @param [String] txt the text you want to tweet
57
+ # @param [Tweet] source the original tweet you are replying to
58
+ def reply(txt, source)
59
+ bot.reply(txt, source)
60
+ end
61
+
62
+
9
63
  #
10
64
  # generate a Bot object. if the DSL is being called from a Bot object, just return it
11
65
  # otherwise create a bot and return that
@@ -30,6 +84,10 @@ module Chatterbot
30
84
  opts.on('-v', '--verbose', "verbose output to stdout") { params[:verbose] = true }
31
85
  opts.on('--dry-run', "Run the bot in test mode, and also don't update the database") { params[:debug_mode] = true ; params[:no_update] = true }
32
86
  opts.on('-s', '--since_id [ARG]', "Check for tweets since tweet id #[ARG]") { |s| params[:since_id] = s.to_i }
87
+ opts.on('-r', '--reset', "Reset your bot to ignore old tweets") {
88
+ params[:debug_mode] = true
89
+ params[:reset_since_id] = true
90
+ }
33
91
 
34
92
  opts.on_tail("-h", "--help", "Show this message") do
35
93
  puts opts
@@ -43,6 +101,7 @@ module Chatterbot
43
101
 
44
102
  #
45
103
  # should we send tweets?
104
+ # @param [Boolean] d true/false if we should send tweets
46
105
  #
47
106
  def debug_mode(d=nil)
48
107
  d = true if d.nil?
@@ -51,6 +110,7 @@ module Chatterbot
51
110
 
52
111
  #
53
112
  # should we update the db with a new since_id?
113
+ # @param [Boolean] d true/false if we should update the database
54
114
  #
55
115
  def no_update(d=nil)
56
116
  d = true if d.nil?
@@ -59,6 +119,7 @@ module Chatterbot
59
119
 
60
120
  #
61
121
  # turn on/off verbose output
122
+ # @param [Boolean] d true/false use verbose output
62
123
  #
63
124
  def verbose(d=nil)
64
125
  d = true if d.nil?
@@ -66,24 +127,14 @@ module Chatterbot
66
127
  end
67
128
 
68
129
  #
69
- # take a variable list of strings and possibly arrays and turn
70
- # them into a single flat array of strings
130
+ # specify a bot-specific blacklist of users. accepts an array, or a
131
+ # comma-delimited string. when called, any subsequent calls to
132
+ # search or replies will filter out these users.
71
133
  #
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
-
134
+ # @param [Array, String] args list of usernames
135
+ # @example
136
+ # blacklist "mean_user, private_user"
84
137
  #
85
- # specify a bot-specific blacklist of users. accepts an array, or a
86
- # comma-delimited string
87
138
  def blacklist(*args)
88
139
  list = flatten_list_of_strings(args)
89
140
 
@@ -95,7 +146,14 @@ module Chatterbot
95
146
  end
96
147
 
97
148
  #
98
- # specify list of strings we will check when deciding to respond to a tweet or not
149
+ # specify list of strings we will check when deciding to respond
150
+ # to a tweet or not. accepts an array or a comma-delimited string.
151
+ # when called, any subsequent calls to search or replies will
152
+ # filter out tweets with these strings
153
+ #
154
+ # @param [Array, String] args list of usernames
155
+ # @example
156
+ # exclude "spam, junk, something"
99
157
  def exclude(*args)
100
158
  e = flatten_list_of_strings(args)
101
159
  if e.nil? || e.empty?
@@ -105,47 +163,48 @@ module Chatterbot
105
163
  end
106
164
  end
107
165
 
166
+
108
167
  #
109
- # search twitter for the specified terms
110
- def search(query, opts = {}, &block)
111
- bot.search(query, opts, &block)
112
- end
113
-
114
- #
115
- # handle replies to the bot
116
- def replies(&block)
117
- bot.replies(&block)
118
- end
119
-
168
+ # The ID of the most recent tweet processed by the bot
120
169
  #
121
- # send a tweet
122
- def tweet(txt, params = {}, original = nil)
123
- bot.tweet(txt, params, original)
124
- end
125
-
126
- #
127
- # retweet
128
- def retweet(id)
129
- bot.retweet(id)
130
- end
131
-
132
- #
133
- # reply to a tweet
134
- def reply(txt, source)
135
- bot.reply(txt, source)
136
- end
137
-
138
170
  def since_id
139
171
  bot.config[:since_id]
140
172
  end
141
173
 
174
+ #
175
+ # explicitly save the configuration/state of the bot.
176
+ #
142
177
  def update_config
143
178
  bot.update_config
144
179
  end
145
180
 
181
+ #
182
+ # return the bot's current database connection, if available.
183
+ # handy if you need to manage data with your bot
184
+ #
146
185
  def db
147
186
  bot.db
148
187
  end
188
+
189
+
190
+ protected
191
+
192
+ #
193
+ # take a variable list of strings and possibly arrays and turn
194
+ # them into a single flat array of strings
195
+ #
196
+ def flatten_list_of_strings(args)
197
+ args.collect do |b|
198
+ if b.is_a?(String)
199
+ # string, split on commas and turn into array
200
+ b.split(",").collect { |s| s.strip }
201
+ else
202
+ # presumably an array
203
+ b
204
+ end
205
+ end.flatten
206
+ end
207
+
149
208
  end
150
209
  end
151
210
 
@@ -24,6 +24,9 @@ module Chatterbot
24
24
  # if we're doing a search, or parsing through replies/mentions.
25
25
  def from_user(s)
26
26
  return s if s.is_a?(String)
27
+ return s.from_user if s.respond_to?(:from_user) #&& ! s.from_user.nil?
28
+ # return s.user.screen_name if s.respond_to?(:user)
29
+
27
30
  s.has_key?(:from_user) ? s[:from_user] : s[:user][:screen_name]
28
31
  end
29
32
 
@@ -7,23 +7,19 @@ module Chatterbot
7
7
  # handle replies for the bot
8
8
  def replies(&block)
9
9
  return unless require_login
10
+
10
11
  debug "check for replies since #{since_id}"
11
12
 
12
13
  opts = since_id > 0 ? {:since_id => since_id} : {}
13
- results = client.replies(opts)
14
+ opts[:count] = 200
14
15
 
15
- if results.is_a?(Hash) && results.has_key?("error")
16
- critical results["error"]
17
- else
18
- results.each { |s|
19
- s.symbolize_keys!
20
- unless ! block_given? || on_blacklist?(s) || skip_me?(s)
21
- update_since_id(s)
22
- yield s
23
- end
24
- }
25
- end
16
+ results = client.mentions(opts)
17
+ results.each { |s|
18
+ unless ! block_given? || on_blacklist?(s) || skip_me?(s)
19
+ update_since_id(s)
20
+ yield s
21
+ end
22
+ }
26
23
  end
27
-
28
24
  end
29
25
  end
@@ -14,4 +14,4 @@ module Chatterbot
14
14
  end
15
15
  end
16
16
  end
17
- end
17
+ end
@@ -13,8 +13,6 @@ module Chatterbot
13
13
 
14
14
  # internal search code
15
15
  def search(queries, opts = {}, &block)
16
- return unless init_client
17
-
18
16
  debug "check for tweets since #{since_id}"
19
17
 
20
18
  if queries.is_a?(String)
@@ -26,19 +24,17 @@ module Chatterbot
26
24
  #
27
25
  queries.each { |query|
28
26
  debug "search: #{query} #{opts.merge(default_opts)}"
29
-
30
- search = client.search(exclude_retweets(query), opts.merge(default_opts))
31
- update_since_id(search)
27
+ result = search_client.search(
28
+ exclude_retweets(query),
29
+ opts.merge(default_opts)
30
+ )
31
+
32
+ update_since_id(result.max_id)
32
33
 
33
- if search != nil
34
- search["results"].each { |s|
35
- s.symbolize_keys!
36
- debug s[:text]
37
- #if s[:text].downcase.include?(query.downcase)
38
- yield s unless ! block_given? || on_blacklist?(s) || skip_me?(s)
39
- #end
40
- }
41
- end
34
+ result.collection.each { |s|
35
+ debug s.text
36
+ yield s unless ! block_given? || on_blacklist?(s) || skip_me?(s)
37
+ }
42
38
  }
43
39
  end
44
40
 
@@ -3,7 +3,6 @@ module Chatterbot
3
3
  #
4
4
  # routines for sending tweets
5
5
  module Tweet
6
-
7
6
  # simple wrapper for sending a message
8
7
  def tweet(txt, params = {}, original = nil)
9
8
  return if require_login == false
@@ -17,12 +16,15 @@ module Chatterbot
17
16
  log txt, original
18
17
  client.update txt, params
19
18
  end
20
- end
19
+ rescue Twitter::Error::Forbidden => e
20
+ debug e
21
+ false
22
+ end
21
23
 
22
24
  # reply to a tweet
23
25
  def reply(txt, source)
24
26
  debug txt
25
- self.tweet txt, {:in_reply_to_status_id => source[:id]}, source
26
- end
27
+ tweet txt, {:in_reply_to_status_id => source[:id]}, source
28
+ end
27
29
  end
28
30
  end
@@ -0,0 +1,96 @@
1
+ module Chatterbot
2
+
3
+ #
4
+ # routines for outputting setup instructions
5
+ #
6
+ module UI
7
+
8
+ #
9
+ # print out a message about getting a PIN from twitter, then output
10
+ # the URL the user needs to visit to authorize
11
+ #
12
+ def get_oauth_verifier
13
+ puts "****************************************"
14
+ puts "****************************************"
15
+ puts "**** BOT AUTH TIME! ****"
16
+ puts "****************************************"
17
+ puts "****************************************"
18
+
19
+ puts "You need to authorize your bot with Twitter.\n\nPlease login to Twitter under the bot's account. When you're ready, hit Enter.\n\nYour browser will open with the following URL, where you can authorize the bot.\n\n"
20
+
21
+ url = request_token.authorize_url
22
+
23
+ puts url
24
+
25
+ puts "\nIf that doesn't work, you can open the URL in your browser manually."
26
+
27
+ puts "\n\nHit enter to start.\n\n"
28
+
29
+ STDIN.readline.chomp
30
+
31
+ Launchy.open(url)
32
+
33
+ # sleep here so that if launchy has any output (which it does
34
+ # sometimes), it doesn't interfere with our input prompt
35
+
36
+ sleep(1)
37
+
38
+ puts "Paste your PIN and hit enter when you have completed authorization.\n\n"
39
+ print "> "
40
+
41
+ STDIN.readline.chomp
42
+ rescue Interrupt => e
43
+
44
+ end
45
+
46
+ #
47
+ # Ask the user to get an API key from Twitter.
48
+ def get_api_key
49
+ puts "****************************************"
50
+ puts "****************************************"
51
+ puts "**** API SETUP TIME! ****"
52
+ puts "****************************************"
53
+ puts "****************************************"
54
+
55
+ api_url = "https://dev.twitter.com/apps/new"
56
+
57
+ puts "Hey, looks like you need to get an API key from Twitter before you can get started."
58
+ puts "Please hit enter, and I will send you to #{api_url} to start the process."
59
+ puts "(If it doesn't work, you can open a browser and paste the URL in manually)"
60
+
61
+ puts "\nHit Enter to continue."
62
+
63
+ STDIN.readline
64
+
65
+ Launchy.open(api_url)
66
+ # pause to allow any launchy output
67
+ sleep(1)
68
+
69
+ puts "\n\n"
70
+
71
+ print "\n\nPaste the 'Consumer Key' here: "
72
+ STDOUT.flush
73
+ config[:consumer_key] = STDIN.readline.chomp
74
+
75
+ print "Paste the 'Consumer Secret' here: "
76
+ STDOUT.flush
77
+ config[:consumer_secret] = STDIN.readline.chomp
78
+
79
+ # reset the client so we can re-init with new OAuth credentials
80
+ reset_client
81
+
82
+ #
83
+ # capture ctrl-c and exit without a stack trace
84
+ #
85
+ rescue Interrupt => e
86
+ exit
87
+ end
88
+
89
+ #
90
+ # error message for auth
91
+ def display_oauth_error
92
+ STDERR.puts "Oops! Looks like something went wrong there, please try again!"
93
+ end
94
+
95
+ end
96
+ end