chatterbot 1.0.2 → 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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