chatterbot 0.2.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.
@@ -0,0 +1,70 @@
1
+ module Chatterbot
2
+
3
+ #
4
+ # routines for optionally interacting with a database for logging
5
+ # tweets, and storing config data there. Uses Sequel to handle the
6
+ # heavy lifing.
7
+ module DB
8
+ #
9
+ # connect to the database, and generate any missing tables
10
+ def db
11
+ @_db ||= connect_and_validate
12
+ end
13
+
14
+ protected
15
+
16
+ #
17
+ # get a DB object from Sequel
18
+ def get_connection
19
+ if has_sequel?
20
+ Sequel.connect(config[:db_uri])
21
+ end
22
+ end
23
+
24
+ #
25
+ # try and connect to the DB, and create tables that are missing.
26
+ def connect_and_validate
27
+ conn = get_connection
28
+ return if conn.nil?
29
+
30
+ if ! conn.tables.include?(:blacklist)
31
+ conn.create_table :blacklist do
32
+ String :user, :primary_key => true
33
+ DateTime :created_at
34
+ end
35
+ end
36
+
37
+ if ! conn.tables.include?(:tweets)
38
+ conn.create_table :tweets do
39
+ primary_key :id
40
+ String :txt
41
+ String :bot
42
+ String :user
43
+ String :source_id
44
+ String :source_tweet
45
+
46
+ DateTime :created_at
47
+ end
48
+ end
49
+
50
+ if ! conn.tables.include?(:config)
51
+ conn.create_table :config do
52
+ String :id, :primary_key => true
53
+
54
+ Bignum :since_id
55
+
56
+ String :secret
57
+ String :token
58
+ String :consumer_secret
59
+ String :consumer_key
60
+
61
+ DateTime :created_at
62
+ DateTime :updated_at
63
+ end
64
+ end
65
+
66
+ conn
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,100 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'chatterbot')
2
+ require 'optparse'
3
+
4
+ module Chatterbot
5
+ #
6
+ # very basic DSL to handle the common stuff you would want to do with a bot.
7
+ module DSL
8
+
9
+ #
10
+ # generate a Bot object. if the DSL is being called from a Bot object, just return it
11
+ # otherwise create a bot and return that
12
+ def bot
13
+ return @bot unless @bot.nil?
14
+
15
+ #
16
+ # parse any command-line options and use them to initialize the bot
17
+ #
18
+ params = {}
19
+
20
+ opts = OptionParser.new
21
+
22
+ opts.banner = "Usage: #{File.basename($0)} [options]"
23
+
24
+ opts.separator ""
25
+ opts.separator "Specific options:"
26
+
27
+ opts.on('-d', '--db [ARG]', "Specify a DB connection URI") { |d| ENV["chatterbot_db"] = d }
28
+ opts.on('-c', '--config [ARG]', "Specify a config file to use") { |c| ENV["chatterbot_config"] = c }
29
+ opts.on('-t', '--test', "Run the bot without actually sending any tweets") { params[:debug_mode] = true }
30
+ 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 }
31
+ opts.on('-s', '--since_id [ARG]', "Check for tweets since tweet id #[ARG]") { |s| params[:since_id] = s }
32
+
33
+ opts.on_tail("-h", "--help", "Show this message") do
34
+ puts opts
35
+ exit
36
+ end
37
+
38
+ opts.parse!(ARGV)
39
+
40
+ @bot = Chatterbot::Bot.new(params)
41
+ end
42
+
43
+ #
44
+ # specify a bot-specific blacklist of users. accepts an array, or a
45
+ # comma-delimited string
46
+ def blacklist(b=nil)
47
+ if b.is_a?(String)
48
+ b = b.split(",").collect { |s| s.strip }
49
+ end
50
+
51
+ if b.nil? || b.empty?
52
+ bot.blacklist = []
53
+ else
54
+ bot.blacklist += b
55
+ end
56
+
57
+ end
58
+
59
+ #
60
+ # specify list of strings we will check when deciding to respond to a tweet or not
61
+ def exclude(e=nil)
62
+ if e.is_a?(String)
63
+ e = e.split(",").collect { |s| s.strip }
64
+ end
65
+
66
+ if e.nil? || e.empty?
67
+ bot.exclude = []
68
+ else
69
+ bot.exclude += e
70
+ end
71
+ end
72
+
73
+ #
74
+ # search twitter for the specified terms
75
+ def search(query, &block)
76
+ @bot.search(query, &block)
77
+ end
78
+
79
+ #
80
+ # handle replies to the bot
81
+ def replies(&block)
82
+ @bot.replies(&block)
83
+ end
84
+
85
+ #
86
+ # send a tweet
87
+ def tweet(txt, params = {}, original = nil)
88
+ @bot.tweet(txt, params, original)
89
+ end
90
+
91
+ #
92
+ # reply to a tweet
93
+ def reply(txt, source)
94
+ @bot.reply(txt, source)
95
+ end
96
+ end
97
+ end
98
+
99
+ include Chatterbot::DSL
100
+ include Chatterbot::Helpers
@@ -0,0 +1,29 @@
1
+ module Chatterbot
2
+
3
+ #
4
+ # a bunch of helper routines for bots
5
+ module Helpers
6
+
7
+ #
8
+ # The name of the currently running bot
9
+ def botname
10
+ if self.class < Bot
11
+ self.class.to_s.downcase
12
+ else
13
+ File.basename($0,".rb")
14
+ end
15
+ end
16
+
17
+ #
18
+ # Take the incoming tweet/user name, and turn it into something suitable for replying
19
+ # to a user. Basically, get their handle and add a '@' to it.
20
+ def tweet_user(tweet)
21
+ if ! tweet.is_a?(String)
22
+ base = tweet.has_key?(:from_user) ? tweet[:from_user] : tweet[:user][:screen_name]
23
+ else
24
+ base = tweet
25
+ end
26
+ base =~ /^@/ ? base : "@#{base}"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ require 'logger'
2
+
3
+ module Chatterbot
4
+
5
+ #
6
+ # routines for outputting log messages, as well as logging tweets
7
+ # to the database if desired.
8
+ module Logging
9
+
10
+ #
11
+ # log a message
12
+ def debug(s)
13
+ logger.debug s unless ! logging?
14
+ end
15
+
16
+ #
17
+ # something really bad happened, print it out and log it
18
+ def critical(s)
19
+ puts s
20
+ debug s
21
+ end
22
+
23
+ #
24
+ # log a tweet to the database
25
+ def log(txt, source=nil)
26
+ return unless log_tweets?
27
+
28
+ data = {:txt => txt, :bot => botname, :created_at => Time.now}
29
+ if source != nil
30
+ data = data.merge(:user => source[:from_user],
31
+ :source_id => source[:id],
32
+ :source_tweet => source[:text])
33
+ end
34
+
35
+ # populate the table
36
+ db[:tweets].insert(data)
37
+ end
38
+
39
+ protected
40
+ #
41
+ # initialize a Logger object, writing to log_dest
42
+ def logger
43
+ # log to the dest specified in the config file, rollover after 10mb of data
44
+ @_logger ||= Logger.new(log_dest, 0, 1024 * 1024)
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ module Chatterbot
2
+
3
+ #
4
+ # reply method for responding to tweets
5
+ module Reply
6
+
7
+ # handle replies for the bot
8
+ def replies(&block)
9
+ return unless require_login
10
+ debug "check for replies since #{since_id}"
11
+
12
+ results = client.replies(:since_id => since_id)
13
+
14
+ if results.is_a?(Hash) && results.has_key?("error")
15
+ critical results["error"]
16
+ else
17
+ results.each { |s|
18
+ s.symbolize_keys!
19
+ unless ! block_given? || on_blacklist?(s) || skip_me?(s)
20
+ update_since_id(s)
21
+ yield s
22
+ end
23
+ }
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ module Chatterbot
2
+
3
+ #
4
+ # handle Twitter searches
5
+ module Search
6
+
7
+ # internal search code
8
+ def search(queries, &block)
9
+ return unless init_client
10
+
11
+ debug "check for tweets since #{since_id}"
12
+
13
+ if queries.is_a?(String)
14
+ queries = [queries]
15
+ end
16
+
17
+ #
18
+ # search twitter
19
+ #
20
+ queries.each { |query|
21
+ search = client.search(query, default_opts)
22
+ update_since_id(search)
23
+
24
+ if search != nil
25
+ search["results"].each { |s|
26
+ s.symbolize_keys!
27
+ yield s unless ! block_given? || on_blacklist?(s) || skip_me?(s)
28
+ }
29
+ end
30
+ }
31
+ end
32
+
33
+ end
34
+ end
35
+
@@ -0,0 +1,25 @@
1
+ module Chatterbot
2
+
3
+ #
4
+ # routines for sending tweets
5
+ module Tweet
6
+
7
+ # simple wrapper for sending a message
8
+ def tweet(txt, params = {}, original = nil)
9
+ return if require_login == false
10
+
11
+ if debug_mode?
12
+ debug "I'm in debug mode, otherwise I would tweet: #{txt}"
13
+ else
14
+ debug txt
15
+ log txt, original
16
+ client.update txt, params
17
+ end
18
+ end
19
+
20
+ # reply to a tweet
21
+ def reply(txt, source)
22
+ self.tweet txt, {:in_reply_to_status_id => source["id"]}, source
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module Chatterbot
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,115 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Chatterbot::Blacklist" do
4
+ it "has a list of excluded phrases" do
5
+ @bot = test_bot
6
+ @bot.exclude = ["junk", "i hate bots", "foobar", "spam"]
7
+ @bot.skip_me?("did you know that i hate bots?").should == true
8
+ end
9
+
10
+ describe "skip_me?" do
11
+ before(:each) do
12
+ @bot = test_bot
13
+ @bot.stub!(:exclude).and_return(["junk", "i hate bots", "foobar", "spam"])
14
+ end
15
+
16
+ it "blocks tweets with phrases we don't want" do
17
+ @bot.skip_me?("did you know that i hate bots?").should == true
18
+ end
19
+
20
+ it "isn't case-specific" do
21
+ @bot.skip_me?("DID YOU KNOW THAT I HATE BOTS?").should == true
22
+ end
23
+
24
+
25
+ it "allows users we do want" do
26
+ @bot.skip_me?("a tweet without any bad content").should == false
27
+ end
28
+
29
+ it "works with result hashes" do
30
+ @bot.skip_me?({:text => "did you know that i hate bots?"}).should == true
31
+ @bot.skip_me?({:text => "a tweet without any bad content"}).should == false
32
+ end
33
+ end
34
+
35
+ describe "on_blacklist?" do
36
+ before(:each) do
37
+ @bot = test_bot
38
+ @bot.stub!(:blacklist).and_return(["skippy", "blippy", "dippy"])
39
+ end
40
+
41
+ it "blocks users we don't want" do
42
+ @bot.on_blacklist?("skippy").should == true
43
+ end
44
+
45
+ it "allows users we do want" do
46
+ @bot.on_blacklist?("flippy").should == false
47
+ end
48
+
49
+ it "isn't case-specific" do
50
+ @bot.on_blacklist?("FLIPPY").should == false
51
+ @bot.on_blacklist?("SKIPPY").should == true
52
+ end
53
+
54
+ it "works with result hashes" do
55
+ @bot.on_blacklist?({:from_user => "skippy"}).should == true
56
+ @bot.on_blacklist?({:from_user => "flippy"}).should == false
57
+ end
58
+ end
59
+
60
+ describe "on_global_blacklist?" do
61
+ before(:each) do
62
+ @bot = test_bot
63
+ end
64
+
65
+ 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
68
+ end
69
+
70
+ it "collects name from the db if it exists" do
71
+ @bot.stub!(:has_db?).and_return(true)
72
+ blacklist_table = mock(Object)
73
+ mock_dataset = mock(Object, {:count => 1})
74
+ blacklist_table.should_receive(:where).
75
+ with({ :user => "a"}).
76
+ and_return( mock_dataset )
77
+
78
+
79
+ missing_dataset = mock(Object, {:count => 0})
80
+ blacklist_table.should_receive(:where).
81
+ with({ :user => "b"}).
82
+ and_return( missing_dataset )
83
+
84
+ @bot.stub!(:db).and_return({
85
+ :blacklist => blacklist_table
86
+ })
87
+ @bot.on_global_blacklist?("a").should == true
88
+ @bot.on_global_blacklist?("b").should == false
89
+ end
90
+ end
91
+
92
+
93
+ describe "db interaction" do
94
+ before(:each) do
95
+ @db_tmp = Tempfile.new("blacklist.db")
96
+ @db_uri = "sqlite://#{@db_tmp.path}"
97
+
98
+ @bot = Chatterbot::Bot.new
99
+ @bot.config[:db_uri] = @db_uri
100
+ @bot.db
101
+ end
102
+
103
+ describe "add_to_blacklist" do
104
+ it "adds to the blacklist table" do
105
+ @bot.add_to_blacklist("tester")
106
+ end
107
+
108
+ it "doesn't add a double entry" do
109
+ @bot.add_to_blacklist("tester")
110
+ @bot.add_to_blacklist("tester")
111
+ end
112
+ end
113
+
114
+ end
115
+ end