chatterbot 0.7.1 → 0.9.0

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -1
  3. data/Gemfile +7 -5
  4. data/README.markdown +1 -1
  5. data/Rakefile +8 -0
  6. data/bin/chatterbot-blacklist +2 -0
  7. data/bin/chatterbot-register +2 -0
  8. data/bin/chatterbot-status +2 -0
  9. data/chatterbot.gemspec +6 -6
  10. data/examples/streaming_bot.rb +42 -0
  11. data/lib/chatterbot.rb +16 -1
  12. data/lib/chatterbot/blacklist.rb +3 -1
  13. data/lib/chatterbot/bot.rb +4 -0
  14. data/lib/chatterbot/client.rb +21 -26
  15. data/lib/chatterbot/config.rb +26 -30
  16. data/lib/chatterbot/db.rb +2 -0
  17. data/lib/chatterbot/dsl.rb +117 -3
  18. data/lib/chatterbot/favorite.rb +23 -0
  19. data/lib/chatterbot/followers.rb +9 -0
  20. data/lib/chatterbot/helpers.rb +3 -1
  21. data/lib/chatterbot/logging.rb +4 -3
  22. data/lib/chatterbot/profile.rb +40 -0
  23. data/lib/chatterbot/reply.rb +10 -2
  24. data/lib/chatterbot/retweet.rb +10 -4
  25. data/lib/chatterbot/search.rb +12 -6
  26. data/lib/chatterbot/skeleton.rb +6 -0
  27. data/lib/chatterbot/streaming.rb +67 -0
  28. data/lib/chatterbot/streaming_handler.rb +96 -0
  29. data/lib/chatterbot/tweet.rb +2 -0
  30. data/lib/chatterbot/ui.rb +2 -1
  31. data/lib/chatterbot/utils.rb +11 -0
  32. data/lib/chatterbot/version.rb +1 -1
  33. data/spec/blacklist_spec.rb +24 -23
  34. data/spec/bot_spec.rb +12 -0
  35. data/spec/client_spec.rb +36 -32
  36. data/spec/config_spec.rb +135 -85
  37. data/spec/db_spec.rb +5 -5
  38. data/spec/dsl_spec.rb +74 -27
  39. data/spec/favorite_spec.rb +34 -0
  40. data/spec/followers_spec.rb +32 -11
  41. data/spec/helpers_spec.rb +25 -20
  42. data/spec/logging_spec.rb +16 -15
  43. data/spec/profile_spec.rb +65 -0
  44. data/spec/reply_spec.rb +24 -24
  45. data/spec/retweet_spec.rb +17 -9
  46. data/spec/search_spec.rb +26 -26
  47. data/spec/skeleton_spec.rb +6 -6
  48. data/spec/spec_helper.rb +44 -32
  49. data/spec/streaming_handler_spec.rb +78 -0
  50. data/spec/streaming_spec.rb +172 -0
  51. data/spec/tweet_spec.rb +18 -18
  52. data/spec/utils_spec.rb +29 -0
  53. data/specs.watchr +1 -13
  54. metadata +27 -16
@@ -0,0 +1,23 @@
1
+ module Chatterbot
2
+
3
+ # routines for favorites
4
+ module Favorite
5
+
6
+ # simple wrapper for favoriting a message
7
+ # @param [id] id A tweet or the ID of a tweet. if not specified,
8
+ # tries to use the current tweet if available
9
+ def favorite(id=@current_tweet)
10
+ return if require_login == false
11
+
12
+ id = id_from_tweet(id)
13
+
14
+ #:nocov:
15
+ if debug_mode?
16
+ debug "I'm in debug mode, otherwise I would favorite tweet id: #{id}"
17
+ return
18
+ end
19
+ #:nocov:
20
+ client.favorite id
21
+ end
22
+ end
23
+ end
@@ -9,5 +9,14 @@ module Chatterbot
9
9
  return unless require_login
10
10
  client.followers(opts).to_a
11
11
  end
12
+
13
+ #
14
+ # follow a user
15
+ #
16
+ # @param u a Twitter::User or user id
17
+ def follow(u)
18
+ return unless require_login
19
+ client.follow(u)
20
+ end
12
21
  end
13
22
  end
@@ -25,7 +25,9 @@ module Chatterbot
25
25
  def from_user(s)
26
26
  case s
27
27
  when Twitter::Tweet
28
- s.from_user
28
+ s.user.screen_name
29
+ when Twitter::User
30
+ s.screen_name
29
31
  when String
30
32
  s
31
33
  else
@@ -27,10 +27,11 @@ module Chatterbot
27
27
  return unless log_tweets?
28
28
 
29
29
  data = {:txt => txt, :bot => botname, :created_at => Time.now}
30
+
30
31
  if source != nil
31
- data = data.merge(:user => source[:from_user],
32
- :source_id => source[:id],
33
- :source_tweet => source[:text])
32
+ data = data.merge(:user => source.user.screen_name,
33
+ :source_id => source.id,
34
+ :source_tweet => source.text)
34
35
  end
35
36
 
36
37
  # populate the table
@@ -0,0 +1,40 @@
1
+ module Chatterbot
2
+
3
+ # routines for managing your profile
4
+ module Profile
5
+
6
+ #
7
+ # get/set the profile description
8
+ #
9
+ def profile_text(p=nil)
10
+ return if require_login == false
11
+
12
+ if p.nil?
13
+ client.user.description
14
+ else
15
+ data = {
16
+ description: p
17
+ }
18
+ client.update_profile(data)
19
+ p
20
+ end
21
+ end
22
+
23
+ #
24
+ # get/set the profile URL
25
+ #
26
+ def profile_website(w=nil)
27
+ return if require_login == false
28
+
29
+ if w.nil?
30
+ client.user.website
31
+ else
32
+ data = {
33
+ url: w
34
+ }
35
+ client.update_profile(data)
36
+ w
37
+ end
38
+ end
39
+ end
40
+ end
@@ -10,16 +10,24 @@ module Chatterbot
10
10
 
11
11
  debug "check for replies since #{since_id_reply}"
12
12
 
13
- opts = since_id_reply > 0 ? {:since_id => since_id_reply} : {}
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
14
19
  opts[:count] = 200
15
20
 
16
- results = client.mentions(opts)
21
+ results = client.mentions_timeline(opts)
22
+ @current_tweet = nil
17
23
  results.each { |s|
18
24
  update_since_id_reply(s)
19
25
  unless ! block_given? || on_blacklist?(s) || skip_me?(s)
26
+ @current_tweet = s
20
27
  yield s
21
28
  end
22
29
  }
30
+ @current_tweet = nil
23
31
  end
24
32
  end
25
33
  end
@@ -4,14 +4,20 @@ module Chatterbot
4
4
  module Retweet
5
5
 
6
6
  # simple wrapper for retweeting a message
7
- def retweet(id)
8
- return if require_login == false
7
+ # @param [id] id A tweet or the ID of a tweet. if not specified,
8
+ # tries to use the current tweet if available
9
+ def retweet(id=@current_tweet)
10
+ return if require_login == false || id.nil?
9
11
 
12
+ id = id_from_tweet(id)
13
+ #:nocov:
10
14
  if debug_mode?
11
15
  debug "I'm in debug mode, otherwise I would retweet with tweet id: #{id}"
12
- else
13
- client.retweet id
16
+ return
14
17
  end
18
+ #:nocov:
19
+
20
+ client.retweet id
15
21
  end
16
22
  end
17
23
  end
@@ -18,22 +18,28 @@ module Chatterbot
18
18
  if queries.is_a?(String)
19
19
  queries = [queries]
20
20
  end
21
+
21
22
 
22
23
  #
23
24
  # search twitter
24
25
  #
25
26
  queries.each { |query|
26
- debug "search: #{query} #{opts.merge(default_opts)}"
27
- result = search_client.search(
27
+ debug "search: #{query} #{default_opts.merge(opts)}"
28
+ result = client.search(
28
29
  exclude_retweets(query),
29
- opts.merge(default_opts)
30
+ default_opts.merge(opts)
30
31
  )
31
- update_since_id(result.max_id)
32
+ update_since_id(result)
32
33
 
33
- result.collection.each { |s|
34
+ @current_tweet = nil
35
+ result.each { |s|
34
36
  debug s.text
35
- yield s unless ! block_given? || on_blacklist?(s) || skip_me?(s)
37
+ if block_given? && !on_blacklist?(s) && !skip_me?(s)
38
+ @current_tweet = s
39
+ yield s
40
+ end
36
41
  }
42
+ @current_tweet = nil
37
43
  }
38
44
  end
39
45
 
@@ -16,7 +16,10 @@ module Chatterbot
16
16
  puts opts.inspect
17
17
 
18
18
  if RUBY_VERSION =~ /^1\.8\./
19
+ #:nocov:
19
20
  apply_vars(src, opts)
21
+ #:nocov:
22
+
20
23
  else
21
24
  src % opts
22
25
  end
@@ -26,6 +29,7 @@ module Chatterbot
26
29
  # handle string interpolation in ruby 1.8. modified from
27
30
  # https://raw.github.com/svenfuchs/i18n/master/lib/i18n/core_ext/string/interpolate.rb
28
31
  #
32
+ #:nocov:
29
33
  def apply_vars(s, args)
30
34
  pattern = Regexp.union(
31
35
  /%\{(\w+)\}/, # matches placeholders like "%{foo}"
@@ -47,6 +51,8 @@ module Chatterbot
47
51
  end
48
52
  end
49
53
  end
54
+ #:nocov:
55
+
50
56
  end
51
57
  end
52
58
  end
@@ -0,0 +1,67 @@
1
+ module Chatterbot
2
+
3
+ #
4
+ # simple twitter stream handler
5
+ module Streaming
6
+
7
+ def authenticated_user
8
+ @user ||= client.user
9
+ end
10
+
11
+ # Streams messages for a single user, optionally including an
12
+ # additional search/etc
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)
29
+ debug object
30
+
31
+ case object
32
+ when Twitter::Tweet
33
+ if object.user == authenticated_user
34
+ puts "skipping #{object} because it's from me"
35
+ elsif streamer.tweet_handler && !on_blacklist?(object) && !skip_me?(object)
36
+ @current_tweet = object
37
+ streamer.tweet_handler.call object
38
+ @current_tweet = nil
39
+ end
40
+ when Twitter::Streaming::DeletedTweet
41
+ if streamer.delete_handler
42
+ streamer.delete_handler.call(object)
43
+ end
44
+ when Twitter::DirectMessage
45
+ if streamer.dm_handler # && !on_blacklist?(object) && !skip_me?(object)
46
+ @current_tweet = object
47
+ streamer.dm_handler.call object
48
+ @current_tweet = nil
49
+ end
50
+ when Twitter::Streaming::Event
51
+ if object.respond_to?(:source) && object.source == authenticated_user
52
+ puts "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)
57
+ end
58
+ when Twitter::Streaming::FriendList
59
+ debug "got friend list"
60
+ if streamer.friends_handler
61
+ streamer.friends_handler.call(object)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
@@ -0,0 +1,96 @@
1
+
2
+ class StreamingHandler
3
+ attr_reader :tweet_handler
4
+ attr_reader :favorite_handler
5
+ attr_reader :dm_handler
6
+ attr_reader :follow_handler
7
+ attr_reader :delete_handler
8
+ attr_reader :friends_handler
9
+ attr_reader :search_filter
10
+
11
+ attr_accessor :opts
12
+
13
+ def initialize(bot, opts = {})
14
+ @bot = bot
15
+ @opts = opts
16
+ end
17
+
18
+ def bot
19
+ @bot
20
+ end
21
+
22
+ def config
23
+ bot.config
24
+ end
25
+
26
+ # filter, firehose, sample, user
27
+ def endpoint
28
+ opts[:endpoint] || :user
29
+ end
30
+
31
+ def search(query, opts = {}, &block)
32
+ @search_filter = query
33
+ @search_opts = opts
34
+ apply_main_block(&block) if block_given?
35
+ end
36
+
37
+ def user(&block)
38
+ apply_main_block(&block)
39
+ end
40
+ alias_method :replies, :user
41
+ alias_method :timeline, :user
42
+ alias_method :sample, :user
43
+ alias_method :filter, :user
44
+
45
+ def favorited(&block)
46
+ @favorite_handler = block
47
+ end
48
+
49
+ def direct_message(&block)
50
+ @dm_handler = block
51
+ end
52
+
53
+ def followed(&block)
54
+ @follow_handler = block
55
+ end
56
+
57
+ def delete(&block)
58
+ @delete_handler = block
59
+ end
60
+
61
+ def friends(&block)
62
+ @friends_handler = block
63
+ end
64
+
65
+ def connection_params
66
+ opts = {
67
+ #:with => 'followings',
68
+ #:replies => false,
69
+ :stall_warnings => false
70
+ }.merge(@opts)
71
+
72
+ opts.delete(:endpoint)
73
+
74
+ # convert true/false to strings
75
+ opts.each { |k, v| opts[k] = v.to_s }
76
+
77
+ if @search_filter
78
+ opts[:track] = @search_filter
79
+ end
80
+
81
+ opts
82
+ end
83
+
84
+ def apply_main_block(&block)
85
+ if !@tweet_handler.nil?
86
+ warn "WARNING: when using streaming, you may only have one block of user/replies/timeline/sample/filter"
87
+ raise RuntimeError, 'Unable to load bot'
88
+ end
89
+ @tweet_handler = block
90
+ end
91
+
92
+
93
+ def apply(block)
94
+ instance_exec &block
95
+ end
96
+ end
@@ -17,8 +17,10 @@ module Chatterbot
17
17
  client.update txt, params
18
18
  end
19
19
  rescue Twitter::Error::Forbidden => e
20
+ #:nocov:
20
21
  debug e
21
22
  false
23
+ #:nocov:
22
24
  end
23
25
 
24
26
  # reply to a tweet
@@ -9,6 +9,7 @@ module Chatterbot
9
9
  # print out a message about getting a PIN from twitter, then output
10
10
  # the URL the user needs to visit to authorize
11
11
  #
12
+ #:nocov:
12
13
  def get_oauth_verifier
13
14
  puts "****************************************"
14
15
  puts "****************************************"
@@ -91,6 +92,6 @@ module Chatterbot
91
92
  def display_oauth_error
92
93
  STDERR.puts "Oops! Looks like something went wrong there, please try again!"
93
94
  end
94
-
95
95
  end
96
+ #:nocov:
96
97
  end
@@ -0,0 +1,11 @@
1
+ module Chatterbot
2
+ module Utils
3
+ def id_from_tweet(t)
4
+ t.is_a?(Twitter::Tweet) ? t.id : t
5
+ end
6
+
7
+ def id_from_user(u)
8
+ u.is_a?(Twitter::User) ? u.id : u
9
+ end
10
+ end
11
+ end
@@ -1,3 +1,3 @@
1
1
  module Chatterbot
2
- VERSION = "0.7.1"
2
+ VERSION = "0.9.0"
3
3
  end
@@ -4,56 +4,57 @@ describe "Chatterbot::Blacklist" do
4
4
  it "has a list of excluded phrases" do
5
5
  @bot = test_bot
6
6
  @bot.exclude = ["junk", "i hate bots", "foobar", "spam"]
7
- @bot.skip_me?("did you know that i hate bots?").should == true
7
+ expect(@bot.skip_me?("did you know that i hate bots?")).to eq(true)
8
8
  end
9
9
 
10
10
  describe "skip_me?" do
11
11
  before(:each) do
12
12
  @bot = test_bot
13
- @bot.stub(:exclude).and_return(["junk", "i hate bots", "foobar", "spam"])
13
+ allow(@bot).to receive(:exclude).and_return(["junk", "i hate bots", "foobar", "spam"])
14
14
  end
15
15
 
16
16
  it "blocks tweets with phrases we don't want" do
17
- @bot.skip_me?("did you know that i hate bots?").should == true
17
+ expect(@bot.skip_me?("did you know that i hate bots?")).to eq(true)
18
18
  end
19
19
 
20
20
  it "isn't case-specific" do
21
- @bot.skip_me?("DID YOU KNOW THAT I HATE BOTS?").should == true
21
+ expect(@bot.skip_me?("DID YOU KNOW THAT I HATE BOTS?")).to eq(true)
22
22
  end
23
23
 
24
-
25
24
  it "allows users we do want" do
26
- @bot.skip_me?("a tweet without any bad content").should == false
25
+ expect(@bot.skip_me?("a tweet without any bad content")).to eq(false)
27
26
  end
28
27
 
29
28
  it "works with result hashes" do
30
- @bot.skip_me?(Twitter::Tweet.new(:id => 1, :text => "did you know that i hate bots?")).should == true
31
- @bot.skip_me?(Twitter::Tweet.new(:id => 1, :text => "a tweet without any bad content")).should == false
29
+ expect(@bot.skip_me?(Twitter::Tweet.new(:id => 1, :text => "did you know that i hate bots?"))).to eq(true)
30
+ expect(@bot.skip_me?(Twitter::Tweet.new(:id => 1, :text => "a tweet without any bad content"))).to eq(false)
32
31
  end
33
32
  end
34
33
 
35
34
  describe "on_blacklist?" do
36
35
  before(:each) do
37
36
  @bot = test_bot
38
- @bot.stub(:blacklist).and_return(["skippy", "blippy", "dippy"])
37
+ allow(@bot).to receive(:blacklist).and_return(["skippy", "blippy", "dippy"])
39
38
  end
40
39
 
41
40
  it "blocks users we don't want" do
42
- @bot.on_blacklist?("skippy").should == true
41
+ expect(@bot.on_blacklist?("skippy")).to eq(true)
43
42
  end
44
43
 
45
44
  it "allows users we do want" do
46
- @bot.on_blacklist?("flippy").should == false
45
+ expect(@bot.on_blacklist?("flippy")).to eq(false)
47
46
  end
48
47
 
49
48
  it "isn't case-specific" do
50
- @bot.on_blacklist?("FLIPPY").should == false
51
- @bot.on_blacklist?("SKIPPY").should == true
49
+ expect(@bot.on_blacklist?("FLIPPY")).to eq(false)
50
+ expect(@bot.on_blacklist?("SKIPPY")).to eq(true)
52
51
  end
53
52
 
54
53
  it "works with result hashes" do
55
- @bot.on_blacklist?(Twitter::Tweet.new(:id => 1, :from_user => "skippy")).should == true
56
- @bot.on_blacklist?(Twitter::Tweet.new(:id => 1, :from_user => "flippy")).should == false
54
+ expect(@bot.on_blacklist?(Twitter::Tweet.new(:id => 1,
55
+ :user => {:id => 1, :screen_name => "skippy"}))).to eq(true)
56
+ expect(@bot.on_blacklist?(Twitter::Tweet.new(:id => 1,
57
+ :user => {:id => 1, :screen_name => "flippy"}))).to eq(false)
57
58
  end
58
59
  end
59
60
 
@@ -63,29 +64,29 @@ describe "Chatterbot::Blacklist" do
63
64
  end
64
65
 
65
66
  it "doesn't freak out if no db" do
66
- @bot.should_receive(:has_db?).and_return(false)
67
- @bot.on_global_blacklist?("foobar").should == false
67
+ expect(@bot).to receive(:has_db?).and_return(false)
68
+ expect(@bot.on_global_blacklist?("foobar")).to eq(false)
68
69
  end
69
70
 
70
71
  it "collects name from the db if it exists" do
71
- @bot.stub(:has_db?).and_return(true)
72
+ allow(@bot).to receive(:has_db?).and_return(true)
72
73
  blacklist_table = double(Object)
73
74
  double_dataset = double(Object, {:count => 1})
74
- blacklist_table.should_receive(:where).
75
+ expect(blacklist_table).to receive(:where).
75
76
  with({ :user => "a"}).
76
77
  and_return( double_dataset )
77
78
 
78
79
 
79
80
  missing_dataset = double(Object, {:count => 0})
80
- blacklist_table.should_receive(:where).
81
+ expect(blacklist_table).to receive(:where).
81
82
  with({ :user => "b"}).
82
83
  and_return( missing_dataset )
83
84
 
84
- @bot.stub(:db).and_return({
85
+ allow(@bot).to receive(:db).and_return({
85
86
  :blacklist => blacklist_table
86
87
  })
87
- @bot.on_global_blacklist?("a").should == true
88
- @bot.on_global_blacklist?("b").should == false
88
+ expect(@bot.on_global_blacklist?("a")).to eq(true)
89
+ expect(@bot.on_global_blacklist?("b")).to eq(false)
89
90
  end
90
91
  end
91
92