chatterbot 0.7.1 → 0.9.0

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