chatterbot 1.0.2 → 2.0.0.pre

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/LICENSE.txt +18 -9
  4. data/README.markdown +83 -65
  5. data/bin/chatterbot-register +0 -1
  6. data/chatterbot.gemspec +3 -10
  7. data/examples/class_bot.rb +0 -1
  8. data/examples/echoes_bot.rb +2 -2
  9. data/examples/search_bot.rb +1 -1
  10. data/examples/streaming_bot.rb +21 -15
  11. data/lib/chatterbot.rb +7 -12
  12. data/lib/chatterbot/blocklist.rb +61 -0
  13. data/lib/chatterbot/bot.rb +118 -13
  14. data/lib/chatterbot/client.rb +52 -20
  15. data/lib/chatterbot/config.rb +92 -215
  16. data/lib/chatterbot/config_manager.rb +49 -0
  17. data/lib/chatterbot/direct_messages.rb +46 -0
  18. data/lib/chatterbot/dsl.rb +157 -78
  19. data/lib/chatterbot/followers.rb +4 -0
  20. data/lib/chatterbot/handler.rb +29 -0
  21. data/lib/chatterbot/helpers.rb +14 -3
  22. data/lib/chatterbot/home_timeline.rb +5 -8
  23. data/lib/chatterbot/logging.rb +0 -17
  24. data/lib/chatterbot/profile.rb +0 -1
  25. data/lib/chatterbot/reply.rb +6 -11
  26. data/lib/chatterbot/retweet.rb +2 -6
  27. data/lib/chatterbot/safelist.rb +33 -0
  28. data/lib/chatterbot/search.rb +26 -16
  29. data/lib/chatterbot/skeleton.rb +7 -38
  30. data/lib/chatterbot/streaming.rb +26 -33
  31. data/lib/chatterbot/tweet.rb +0 -1
  32. data/lib/chatterbot/ui.rb +9 -2
  33. data/lib/chatterbot/utils.rb +13 -0
  34. data/lib/chatterbot/version.rb +1 -1
  35. data/spec/blocklist_spec.rb +170 -0
  36. data/spec/bot_spec.rb +83 -8
  37. data/spec/client_spec.rb +61 -7
  38. data/spec/config_manager_spec.rb +59 -0
  39. data/spec/config_spec.rb +33 -158
  40. data/spec/direct_messages_spec.rb +115 -0
  41. data/spec/dsl_spec.rb +95 -53
  42. data/spec/handler_spec.rb +27 -0
  43. data/spec/helpers_spec.rb +7 -11
  44. data/spec/home_timeline_spec.rb +42 -31
  45. data/spec/logging_spec.rb +0 -34
  46. data/spec/reply_spec.rb +10 -34
  47. data/spec/search_spec.rb +65 -6
  48. data/spec/spec_helper.rb +25 -1
  49. data/spec/streaming_spec.rb +56 -58
  50. data/spec/whitelist_spec.rb +10 -10
  51. data/specs.watchr +2 -4
  52. data/templates/skeleton.txt +148 -12
  53. metadata +20 -22
  54. data/bin/chatterbot-blacklist +0 -65
  55. data/bin/chatterbot-status +0 -55
  56. data/examples/loop_bot.rb +0 -44
  57. data/lib/chatterbot/blacklist.rb +0 -61
  58. data/lib/chatterbot/db.rb +0 -79
  59. data/lib/chatterbot/streaming_handler.rb +0 -96
  60. data/lib/chatterbot/whitelist.rb +0 -32
  61. data/spec/blacklist_spec.rb +0 -116
  62. data/spec/db_spec.rb +0 -53
  63. data/spec/streaming_handler_spec.rb +0 -78
@@ -1,4 +1,8 @@
1
1
  module Chatterbot
2
+
3
+ #
4
+ # methods related to following other users
5
+ #
2
6
  module Followers
3
7
 
4
8
  #
@@ -0,0 +1,29 @@
1
+ module Chatterbot
2
+
3
+ #
4
+ # class for holding onto a block/arguments we will use when calling
5
+ # methods on the Twitter API
6
+ #
7
+ class Handler
8
+ attr_reader :opts
9
+ attr_reader :last_ran_at
10
+
11
+ def initialize(opts, &block)
12
+ if block_given?
13
+ @opts = *opts
14
+ @block = block
15
+ else
16
+ @opts = nil
17
+ @block = opts
18
+ end
19
+ end
20
+
21
+ #
22
+ # call the block with the specified arguments
23
+ #
24
+ def call(*args)
25
+ @last_ran_at = Time.now
26
+ @block.call(*args)
27
+ end
28
+ end
29
+ end
@@ -3,6 +3,11 @@ module Chatterbot
3
3
  #
4
4
  # a bunch of helper routines for bots
5
5
  module Helpers
6
+
7
+ #
8
+ # Set the username of the bot. This is used when generating
9
+ # config/skeleton file during registration
10
+ #
6
11
  def botname=(b)
7
12
  @botname = b
8
13
  end
@@ -27,11 +32,9 @@ module Chatterbot
27
32
  when Twitter::Tweet
28
33
  s.user.screen_name
29
34
  when Twitter::User
30
- s.screen_name
35
+ s.name
31
36
  when String
32
37
  s
33
- else
34
- s.has_key?(:from_user) ? s[:from_user] : s[:user][:screen_name]
35
38
  end
36
39
  end
37
40
 
@@ -59,5 +62,13 @@ module Chatterbot
59
62
  end
60
63
  end
61
64
 
65
+ #
66
+ # find the user of the current tweet/object we are dealing with
67
+ #
68
+ def current_user
69
+ return nil if @current_tweet.nil?
70
+ return @current_tweet.sender if @current_tweet.respond_to?(:sender)
71
+ return @current_tweet.user
72
+ end
62
73
  end
63
74
  end
@@ -5,24 +5,21 @@ module Chatterbot
5
5
  module HomeTimeline
6
6
 
7
7
  # handle the bots timeline
8
- def home_timeline(opts={}, &block)
8
+ def home_timeline(*args, &block)
9
9
  return unless require_login
10
10
 
11
11
  debug "check for home_timeline tweets since #{since_id}"
12
12
 
13
13
  opts = {
14
- :since_id => since_id,
14
+ :since_id => since_id_home_timeline,
15
15
  :count => 200
16
- }.merge(opts)
17
-
16
+ }
18
17
  results = client.home_timeline(opts)
19
18
 
20
19
  @current_tweet = nil
21
20
  results.each { |s|
22
- update_since_id(s)
23
- if has_whitelist? && !on_whitelist?(s)
24
- debug "skipping because user not on whitelist"
25
- elsif block_given? && !on_blacklist?(s) && !skip_me?(s)
21
+ update_since_id_home_timeline(s)
22
+ if block_given? && valid_tweet?(s)
26
23
  @current_tweet = s
27
24
  yield s
28
25
  end
@@ -21,23 +21,6 @@ module Chatterbot
21
21
  debug s
22
22
  end
23
23
 
24
- #
25
- # log a tweet to the database
26
- def log(txt, source=nil)
27
- return unless log_tweets?
28
-
29
- data = {:txt => txt, :bot => botname, :created_at => Time.now}
30
-
31
- if source != nil
32
- data = data.merge(:user => source.user.screen_name,
33
- :source_id => source.id,
34
- :source_tweet => source.text)
35
- end
36
-
37
- # populate the table
38
- db[:tweets].insert(data)
39
- end
40
-
41
24
  protected
42
25
  #
43
26
  # initialize a Logger object, writing to log_dest
@@ -8,7 +8,6 @@ module Chatterbot
8
8
  #
9
9
  def profile_text(p=nil)
10
10
  return if require_login == false
11
-
12
11
  if p.nil?
13
12
  client.user.description
14
13
  else
@@ -5,26 +5,21 @@ module Chatterbot
5
5
  module Reply
6
6
 
7
7
  # handle replies for the bot
8
- def replies(&block)
8
+ def replies(*args, &block)
9
9
  return unless require_login
10
10
 
11
11
  debug "check for replies since #{since_id_reply}"
12
12
 
13
- opts = {}
14
- if since_id_reply > 0
15
- opts[:since_id] = since_id_reply
16
- elsif since_id > 0
17
- opts[:since_id] = since_id
18
- end
19
- opts[:count] = 200
13
+ opts = {
14
+ :since_id => since_id_reply,
15
+ :count => 200
16
+ }
20
17
 
21
18
  results = client.mentions_timeline(opts)
22
19
  @current_tweet = nil
23
20
  results.each { |s|
24
21
  update_since_id_reply(s)
25
- if has_whitelist? && !on_whitelist?(s)
26
- debug "skipping because user not on whitelist"
27
- elsif block_given? && !on_blacklist?(s) && !skip_me?(s)
22
+ if block_given? && valid_tweet?(s)
28
23
  @current_tweet = s
29
24
  yield s
30
25
  end
@@ -16,12 +16,8 @@ module Chatterbot
16
16
  return
17
17
  end
18
18
  #:nocov:
19
-
20
- begin
21
- client.retweet id
22
- rescue StandardError => e
23
- puts e.inspect
24
- end
19
+
20
+ client.retweet id
25
21
  end
26
22
  end
27
23
  end
@@ -0,0 +1,33 @@
1
+ module Chatterbot
2
+
3
+ #
4
+ # methods for only tweeting to users on a specific list
5
+ module Safelist
6
+ attr_accessor :safelist
7
+
8
+ alias :whitelist :safelist
9
+
10
+ # A list of Twitter users that we can communicate with
11
+ def safelist
12
+ @safelist || []
13
+ end
14
+
15
+ def safelist=(b)
16
+ @safelist = b.flatten.collect { |e|
17
+ from_user(e).downcase
18
+ }
19
+ @safelist
20
+ end
21
+
22
+ def has_safelist?
23
+ !safelist.empty?
24
+ end
25
+
26
+ #
27
+ # Is this tweet from a user on our safelist?
28
+ def on_safelist?(s)
29
+ search = from_user(s).downcase
30
+ safelist.any? { |b| search.include?(b.downcase) }
31
+ end
32
+ end
33
+ end
@@ -4,21 +4,30 @@ module Chatterbot
4
4
  # handle Twitter searches
5
5
  module Search
6
6
 
7
+ # set a reasonable limit on the maximum number of tweets we will
8
+ # ever return. otherwise it is possible to exceed Twitter's rate limits
7
9
  MAX_SEARCH_TWEETS = 1000
8
10
 
9
11
  @skip_retweets = true
10
12
 
11
13
  #
12
- # modify a query string to exclude retweets from searches
14
+ # exclude retweets from searches
13
15
  #
14
16
  def exclude_retweets
15
17
  @skip_retweets = true
16
18
  end
17
19
 
20
+ #
21
+ # include retweets from searches
22
+ #
18
23
  def include_retweets
19
24
  @skip_retweets = false
20
25
  end
21
26
 
27
+
28
+ #
29
+ # check if this is a retweet that we want to skip
30
+ #
22
31
  def skippable_retweet?(t)
23
32
  @skip_retweets && t.retweeted_status?
24
33
  end
@@ -26,31 +35,32 @@ module Chatterbot
26
35
  # internal search code
27
36
  def search(queries, opts = {}, &block)
28
37
  debug "check for tweets since #{since_id}"
29
-
38
+
30
39
  max_tweets = opts.delete(:limit) || MAX_SEARCH_TWEETS
31
40
 
32
41
  if queries.is_a?(String)
33
42
  queries = [queries]
34
43
  end
35
44
 
45
+ query = queries.join(" OR ")
46
+
36
47
  #
37
48
  # search twitter
38
49
  #
39
- queries.each { |query|
40
- debug "search: #{query} #{default_opts.merge(opts)}"
41
- @current_tweet = nil
42
- client.search( query, default_opts.merge(opts) ).take(max_tweets).each { |s|
43
- update_since_id(s)
44
- debug s.text
45
- if has_whitelist? && !on_whitelist?(s)
46
- debug "skipping because user not on whitelist"
47
- elsif block_given? && !on_blacklist?(s) && !skip_me?(s) && !skippable_retweet?(s)
48
- @current_tweet = s
49
- yield s
50
- end
51
- }
52
- @current_tweet = nil
50
+
51
+ debug "search: #{query} #{default_opts.merge(opts)}"
52
+ @current_tweet = nil
53
+ client.search( query, default_opts.merge(opts) ).take(max_tweets).each { |s|
54
+ update_since_id(s)
55
+ debug s.text
56
+
57
+ if block_given? && valid_tweet?(s)
58
+ @current_tweet = s
59
+ yield s
60
+ end
53
61
  }
62
+ @current_tweet = nil
63
+
54
64
  end
55
65
 
56
66
  end
@@ -4,10 +4,15 @@ module Chatterbot
4
4
  # bot template generator
5
5
  class Skeleton
6
6
  class << self
7
+
8
+ #
9
+ # generate a template file for the specified bot
10
+ # @param [Bot] bot object
11
+ #
7
12
  def generate(bot)
8
13
  path = File.join(Chatterbot.libdir, "..", "templates", "skeleton.txt")
9
14
  src = File.read(path)
10
- puts bot.config.inspect
15
+
11
16
  opts = bot.config.merge({
12
17
  :name => bot.botname,
13
18
  :timestamp => Time.now
@@ -15,44 +20,8 @@ module Chatterbot
15
20
 
16
21
  puts opts.inspect
17
22
 
18
- if RUBY_VERSION =~ /^1\.8\./
19
- #:nocov:
20
- apply_vars(src, opts)
21
- #:nocov:
22
-
23
- else
24
- src % opts
25
- end
26
- end
27
-
28
- #
29
- # handle string interpolation in ruby 1.8. modified from
30
- # https://raw.github.com/svenfuchs/i18n/master/lib/i18n/core_ext/string/interpolate.rb
31
- #
32
- #:nocov:
33
- def apply_vars(s, args)
34
- pattern = Regexp.union(
35
- /%\{(\w+)\}/, # matches placeholders like "%{foo}"
36
- /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
37
- )
38
-
39
- pattern_with_escape = Regexp.union(
40
- /%%/,
41
- pattern
42
- )
43
-
44
- s.gsub(pattern_with_escape) do |match|
45
- if match == '%%'
46
- '%'
47
- else
48
- key = ($1 || $2).to_sym
49
- raise KeyError unless args.has_key?(key)
50
- $3 ? sprintf("%#{$3}", args[key]) : args[key]
51
- end
52
- end
23
+ src % opts
53
24
  end
54
- #:nocov:
55
-
56
25
  end
57
26
  end
58
27
  end
@@ -4,64 +4,57 @@ module Chatterbot
4
4
  # simple twitter stream handler
5
5
  module Streaming
6
6
 
7
- def authenticated_user
8
- @user ||= client.user
7
+ def streaming_tweet_handler
8
+ usable_handlers = [:home_timeline, :search]
9
+ name, block = @handlers.find { |k, v| usable_handlers.include?(k) }
10
+ block
9
11
  end
10
12
 
11
- # Streams messages for a single user, optionally including an
12
- # additional search/etc
13
13
  #
14
- # @param opts [Hash] options
15
- # @option options [String] :with Specifies whether to return information for just the users specified in the follow parameter, or include messages from accounts they follow.
16
- # @option options [String] :replies Specifies whether to return additional @replies.
17
- # @option options [String] :stall_warnings Specifies whether stall warnings should be delivered.
18
- # @option options [String] :track Includes additional Tweets matching the specified keywords. Phrases of keywords are specified by a comma-separated list.
19
- # @option options [String] :locations Includes additional Tweets falling within the specified bounding boxes.
20
- # @yield [Twitter::Tweet, Twitter::Streaming:
21
- def do_streaming(streamer)
22
- debug "streaming twitter client"
23
- streaming_client.send(streamer.endpoint, streamer.connection_params) do |object|
24
- handle_streaming_object(object, streamer)
25
- end
26
- end
27
-
28
- def handle_streaming_object(object, streamer)
14
+ # Take the passed in object and call the appropriate bot method
15
+ # for it
16
+ # @param [Class] object a streaming API object
17
+ #
18
+ def handle_streaming_object(object)
29
19
  debug object
30
20
 
31
21
  case object
32
22
  when Twitter::Tweet
33
23
  if object.user == authenticated_user
34
24
  debug "skipping #{object} because it's from me"
35
- elsif streamer.tweet_handler && !on_blacklist?(object) && !skip_me?(object)
25
+ elsif (h = streaming_tweet_handler) && valid_tweet?(object)
36
26
  @current_tweet = object
37
- streamer.tweet_handler.call object
27
+ update_since_id(object)
28
+
29
+ h.call(object)
38
30
  @current_tweet = nil
39
31
  end
40
32
  when Twitter::Streaming::DeletedTweet
41
- if streamer.delete_handler
42
- streamer.delete_handler.call(object)
33
+ if (h = @handlers[:deleted])
34
+ h.call(object)
43
35
  end
44
36
  when Twitter::DirectMessage
45
- if streamer.dm_handler # && !on_blacklist?(object) && !skip_me?(object)
37
+ if object.sender == authenticated_user
38
+ debug "skipping DM #{object} because it's from me"
39
+ elsif (h = @handlers[:direct_messages])
46
40
  @current_tweet = object
47
- streamer.dm_handler.call object
41
+ update_since_id_dm(object)
42
+ h.call(object)
48
43
  @current_tweet = nil
49
44
  end
50
45
  when Twitter::Streaming::Event
51
46
  if object.respond_to?(:source) && object.source == authenticated_user
52
47
  debug "skipping #{object} because it's from me"
53
- elsif object.name == :follow && streamer.follow_handler
54
- streamer.follow_handler.call(object.source)
55
- elsif object.name == :favorite && streamer.favorite_handler
56
- streamer.favorite_handler.call(object.source, object.target_object)
48
+ elsif object.name == :follow && (h = @handlers[:followed])
49
+ h.call(object.source)
50
+ elsif object.name == :favorite && (h = @handlers[:favorited])
51
+ h.call(object.source, object.target_object)
57
52
  end
58
53
  when Twitter::Streaming::FriendList
59
54
  debug "got friend list"
60
- if streamer.friends_handler
61
- streamer.friends_handler.call(object)
62
- end
63
55
  end
64
56
  end
65
- end
57
+
58
+ end
66
59
  end
67
60