tweetbot 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@tweetbot
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in tweetbot.gemspec
4
+ gemspec
data/README ADDED
@@ -0,0 +1,39 @@
1
+ So you want to write a twitter bot. Use my gem. Then, you can just do this:
2
+
3
+ load 'twitter_auth.rb'
4
+ require 'tweetbot'
5
+
6
+ bot = TweetBot.configure do |config|
7
+ config.response_frequency = 100
8
+
9
+ config.respond_to_phrase "tweetbot example phrase" do |responses|
10
+ responses << "I am tweetbot!" << "You rang?" << "Pretty cool, thanks for saying hello"
11
+ end
12
+
13
+ config.respond_to_phrase "hey @tweetbot" do |responses|
14
+ responses << "Hey back at ya" << "You rang again?"
15
+ end
16
+
17
+ config.twitter_auth = TwitterAuth::AuthKeys
18
+ end
19
+
20
+ bot.talk
21
+
22
+ and build a file called twitter_auth.rb that has your keys
23
+
24
+ module TwitterAuth
25
+ MyName = 'twitter_name'
26
+ ApigeeEnpoint = nil
27
+ def self.use_apigee?
28
+ !ApigeeEnpoint.nil?
29
+ end
30
+ AuthKeys = {
31
+ consumer_key: "key",
32
+ consumer_secret: "secret",
33
+ oauth_token: "token",
34
+ oauth_token_secret: "token_secret"
35
+ }
36
+ end
37
+
38
+
39
+ Abstracting out the apigee stuff, don't worry. Just leave it like that for now.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,19 @@
1
+ load 'twitter_auth.rb'
2
+ require 'tweetbot'
3
+
4
+ bot = TweetBot.configure do |config|
5
+ config.response_frequency = 100
6
+
7
+ config.respond_to_phrase "tweetbot example phrase" do |responses|
8
+ responses << "I am tweetbot!" << "You rang?" << "Pretty cool, thanks for saying hello"
9
+ end
10
+
11
+ config.respond_to_phrase "hey @tweetbot" do |responses|
12
+ responses << "Hey back at ya" << "You rang again?"
13
+ end
14
+
15
+ config.twitter_auth = TwitterAuth::AuthKeys
16
+ end
17
+
18
+ bot.talk
19
+
@@ -0,0 +1,13 @@
1
+ module TwitterAuth
2
+ MyName = 'twitter_name'
3
+ ApigeeEnpoint = nil
4
+ def self.use_apigee?
5
+ !ApigeeEnpoint.nil?
6
+ end
7
+ AuthKeys = {
8
+ consumer_key: "key",
9
+ consumer_secret: "secret",
10
+ oauth_token: "token",
11
+ oauth_token_secret: "token_secret"
12
+ }
13
+ end
data/lib/tweetbot.rb ADDED
@@ -0,0 +1,11 @@
1
+ require_relative 'tweetbot/version'
2
+ require_relative 'tweetbot/talk'
3
+ require_relative 'tweetbot/bot'
4
+
5
+ module TweetBot
6
+ def self.configure
7
+ @bot ||= Bot.new
8
+ yield @bot if block_given?
9
+ @bot
10
+ end
11
+ end
@@ -0,0 +1,59 @@
1
+ module TweetBot
2
+ class Bot
3
+ include TweetBot::Talk
4
+ attr_accessor :response_frequency, :twitter_auth
5
+
6
+ DefaultFrequency = 20
7
+
8
+ def initialize()
9
+ self.response_frequency = DefaultFrequency
10
+ @responses_for_phrases = Hash.new { |hash, key| hash[key] = [] }
11
+ end
12
+
13
+ def phrases_to_search
14
+ @responses_for_phrases.keys
15
+ end
16
+
17
+ def responses_for(phrase)
18
+ @responses_for_phrases[phrase]
19
+ end
20
+
21
+ def respond_to_phrase(phrase)
22
+ responses = []
23
+ yield responses
24
+ add_responses_for_phrase(phrase, *responses)
25
+ end
26
+
27
+ def add_responses_for_phrase(phrase, *responses)
28
+ @responses_for_phrases[phrase.downcase] += responses
29
+ end
30
+
31
+ def response_for(tweet)
32
+ responses = responses_for_tweet(tweet)
33
+ "@#{tweet.user.screen_name} #{responses.sample}"
34
+ end
35
+
36
+ def should_i_respond_to?(tweet)
37
+ return false if under_rate_limit_pause?
38
+ matches = tweet_matches?(tweet)
39
+ frequency_check = (rand(100) < self.response_frequency)
40
+ matches && frequency_check
41
+ end
42
+
43
+ def rate_limited!
44
+ @rate_limited_until = Time.now + 3600
45
+ end
46
+
47
+ def under_rate_limit_pause?
48
+ @rate_limited_until && (Time.now < @rate_limited_until)
49
+ end
50
+
51
+ def tweet_matches?(tweet)
52
+ responses_for_tweet(tweet).any?
53
+ end
54
+
55
+ def responses_for_tweet(tweet)
56
+ @responses_for_phrases.select{|phrase, _| tweet.text =~ /#{phrase}/i}.values[0] || []
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,126 @@
1
+ require 'twitter'
2
+ require 'tweetstream'
3
+
4
+ if ENV["DEBUG"]
5
+ module Twitter
6
+ def self.configure
7
+ end
8
+
9
+ def self.update(status, options = {})
10
+ puts "DEBUG: #{status}"
11
+ end
12
+ end
13
+ end
14
+ module TweetBot
15
+ module Talk
16
+ def configure_twitter_auth
17
+ Twitter.configure do |config|
18
+ config.consumer_key = twitter_auth[:consumer_key]
19
+ config.consumer_secret = twitter_auth[:consumer_secret]
20
+ config.oauth_token = twitter_auth[:oauth_token]
21
+ config.oauth_token_secret = twitter_auth[:oauth_token_secret]
22
+ end
23
+ TweetStream.configure do |config|
24
+ config.consumer_key = twitter_auth[:consumer_key]
25
+ config.consumer_secret = twitter_auth[:consumer_secret]
26
+ config.oauth_token = twitter_auth[:oauth_token]
27
+ config.oauth_token_secret = twitter_auth[:oauth_token_secret]
28
+ config.auth_method = :oauth
29
+ end
30
+ end
31
+
32
+ def talk
33
+ configure_twitter_auth
34
+
35
+ if TwitterAuth.use_apigee?
36
+ twitter_api_endpoint = if ENV['APIGEE_TWITTER_API_ENDPOINT']
37
+ ENV['APIGEE_TWITTER_API_ENDPOINT']
38
+ else
39
+ # Get this value from Heroku.
40
+ # Once you have enabled the addon, boot up the 'heroku console' and run the following:
41
+ # puts ENV['APIGEE_TWITTER_API_ENDPOINT']
42
+ # this will spit out your correct api endpoint
43
+ TwitterAuth::ApigeeEnpoint
44
+ end
45
+ Twitter.configure do |config|
46
+ config.gateway = twitter_api_endpoint
47
+ end
48
+ end
49
+
50
+ bot = self
51
+
52
+ announce_wake_up
53
+ puts "Listening... #{Time.now}"
54
+
55
+
56
+ client = TweetStream::Client.new
57
+
58
+ ["INT", "TERM", "STOP"].each do |signal|
59
+ trap(signal) do
60
+ puts "Got #{signal}"
61
+ client.stop
62
+ exit!(1)
63
+ end
64
+ end
65
+
66
+ at_exit do
67
+ puts "Shutting down... #{Time.now}"
68
+ begin
69
+ send_twitter_message "Going to sleep... #{Time.now}"
70
+ rescue
71
+ end
72
+ end
73
+
74
+
75
+ client.on_error do |message|
76
+ puts "Error: #{Time.now}"
77
+ puts message
78
+ end
79
+
80
+ #EM.defer
81
+ #EM::HttpRequest
82
+ client.track(*bot.phrases_to_search) do |status|
83
+ if status.user.screen_name == TwitterAuth::MyName
84
+ puts "#{Time.now} Caught myself saying it"
85
+ else
86
+ puts "#{Time.now} #{status.user.screen_name} said #{status.text}"
87
+ if bot.should_i_respond_to?(status)
88
+ response = bot.response_for(status)
89
+ begin
90
+ send_twitter_message(response, :in_reply_to_status_id => status.id)
91
+ rescue Twitter::Forbidden => ex
92
+ puts "Rate limited!"
93
+ bot.rate_limited!
94
+ rescue Exception => e
95
+ puts "Exception while sending the reply"
96
+ puts e
97
+ end
98
+ else
99
+ puts "Bot told me not to respond"
100
+ end
101
+ end
102
+ end
103
+ end
104
+
105
+ def send_twitter_message(message, options = {})
106
+ Twitter.update message, options
107
+ end
108
+
109
+ def announce_wake_up
110
+ puts "Waking up to greet the world... #{Time.now}"
111
+ send_twitter_message "Waking up to greet the world... #{Time.now}"
112
+ rescue Twitter::Forbidden => ex
113
+ puts "Twitter Forbidden Error while waking up"
114
+ puts ex
115
+ puts "Continuing"
116
+ rescue Twitter::Error => ex
117
+ puts "Twitter Error while waking up"
118
+ puts ex
119
+ exit!(1)
120
+ rescue => ex
121
+ puts "Unknown Error while waking up"
122
+ puts ex
123
+ exit!(1)
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,3 @@
1
+ module Tweetbot
2
+ VERSION = "0.1.3"
3
+ end
@@ -0,0 +1,103 @@
1
+ require_relative '../lib/tweetbot'
2
+ module Twitter
3
+ end
4
+ module TweetStream
5
+ end
6
+
7
+ describe "configuring tweetbot with a config block" do
8
+ class DummyAuth
9
+ attr_accessor :consumer_key, :consumer_secret, :oauth_token,
10
+ :oauth_token_secret, :auth_method
11
+ end
12
+
13
+ before do
14
+ Twitter.stub(:configure)
15
+ TweetStream.stub(:configure)
16
+ end
17
+
18
+ context "configuring with twitter authentication" do
19
+ it "configures twitter with the oauth keys" do
20
+ auth = DummyAuth.new
21
+ bot = TweetBot.configure do |bot|
22
+ bot.twitter_auth = {
23
+ consumer_key: "ckey",
24
+ consumer_secret: "csecret",
25
+ oauth_token: "token",
26
+ oauth_token_secret: "tokensecret"
27
+ }
28
+ end
29
+ Twitter.stub(:configure).and_yield auth
30
+ bot.configure_twitter_auth
31
+ auth.consumer_key.should == "ckey"
32
+ auth.consumer_secret.should == "csecret"
33
+ auth.oauth_token.should == "token"
34
+ auth.oauth_token_secret.should == "tokensecret"
35
+ end
36
+ it "configures tweetstream with the oauth keys" do
37
+ auth = DummyAuth.new
38
+ bot = TweetBot.configure do |bot|
39
+ bot.twitter_auth = {
40
+ consumer_key: "ckey",
41
+ consumer_secret: "csecret",
42
+ oauth_token: "token",
43
+ oauth_token_secret: "tokensecret"
44
+ }
45
+ end
46
+ TweetStream.stub(:configure).and_yield auth
47
+ bot.configure_twitter_auth
48
+ auth.consumer_key.should == "ckey"
49
+ auth.consumer_secret.should == "csecret"
50
+ auth.oauth_token.should == "token"
51
+ auth.oauth_token_secret.should == "tokensecret"
52
+ end
53
+ end
54
+
55
+ context "configuring twice" do
56
+ it "uses the same bot" do
57
+ bot1 = TweetBot.configure do |bot|
58
+ bot.response_frequency = 4
59
+ end
60
+
61
+ bot2 = TweetBot.configure do |bot|
62
+ bot.response_frequency = 10
63
+ end
64
+
65
+ bot1.should be(bot2)
66
+ bot1.response_frequency.should == 10
67
+ end
68
+ end
69
+
70
+ context "configuring frequency" do
71
+ let(:bot) do
72
+ TweetBot.configure do |bot|
73
+ bot.response_frequency = 4
74
+ end
75
+ end
76
+
77
+ it "saves the response_frequency" do
78
+ bot.response_frequency.should == 4
79
+ end
80
+ end
81
+
82
+ context "configuring responses" do
83
+ let(:bot) do
84
+ TweetBot.configure do |bot|
85
+ bot.respond_to_phrase "code and coffee" do |responses|
86
+ responses << "good times" << "bad times"
87
+ end
88
+ end
89
+ end
90
+
91
+ it "saves the response given in block" do
92
+ bot.responses_for("code and coffee").should =~ ["good times", "bad times"]
93
+ end
94
+ end
95
+
96
+ context "without a configure block" do
97
+ it "returns a bot that I can use" do
98
+ bot = TweetBot.configure
99
+ bot.response_frequency = 4
100
+ bot.response_frequency.should == 4
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,136 @@
1
+ require_relative '../lib/tweetbot'
2
+ require 'timecop'
3
+
4
+
5
+ describe TweetBot::Bot do
6
+ let(:bot) { TweetBot::Bot.new }
7
+ let(:tweet) { stub(:text => "hello world", :user => stub(:screen_name => "fun_person")) }
8
+
9
+ before do
10
+ bot.add_responses_for_phrase "hello world", "and hello to you"
11
+ end
12
+
13
+ after do
14
+ Timecop.return
15
+ end
16
+
17
+
18
+ describe "#phrases_to_search" do
19
+ it "returns the phrases that have responses associated with them" do
20
+ bot.add_responses_for_phrase "good night", ""
21
+ bot.phrases_to_search.should == ["hello world", "good night"]
22
+ end
23
+ end
24
+
25
+ describe "#tweet_matches?" do
26
+ before do
27
+ bot.add_responses_for_phrase "the night", "a dark night"
28
+ end
29
+ it "looks to see if any phrases match the tweet" do
30
+ tweet.stub(:text) { "the night" }
31
+ bot.tweet_matches?(tweet).should be_true
32
+ tweet.stub(:text) { "the day" }
33
+ bot.tweet_matches?(tweet).should be_false
34
+ end
35
+
36
+ it "compares case-insensitive" do
37
+ tweet.stub(:text) { "The Night"}
38
+ bot.tweet_matches?(tweet).should be_true
39
+ end
40
+
41
+ it "matches if anywhere in text" do
42
+ tweet.stub(:text) { "It is cold The Night"}
43
+ bot.tweet_matches?(tweet).should be_true
44
+ end
45
+ end
46
+
47
+ describe "#response_for" do
48
+ it "replies to the user" do
49
+ tweet.user.stub(:screen_name) { "corey" }
50
+ bot.response_for(tweet).should =~ /^@corey/
51
+ end
52
+
53
+ it "uses responses associated with phrase" do
54
+ bot.add_responses_for_phrase("good morning", "morning response")
55
+ bot.add_responses_for_phrase("good night", "night response")
56
+
57
+ tweet.stub(:text) { "good morning" }
58
+ bot.response_for(tweet).should =~ /morning response/
59
+ tweet.stub(:text) { "good night" }
60
+ bot.response_for(tweet).should =~ /night response/
61
+ end
62
+
63
+ it "it randomly uses a phrase from the responses" do
64
+ tweet.stub(:text) { "good morning" }
65
+ bot.add_responses_for_phrase("good morning", "response 1", "response 2")
66
+ responses = (1..20).map do
67
+ bot.response_for(tweet)
68
+ end
69
+ responses.reject! {|response| response =~ /response 1/}
70
+ responses.should_not be_empty
71
+ responses.reject! {|response| response =~ /response 2/}
72
+ responses.should be_empty
73
+ end
74
+
75
+ it "uses responses if phrase appears anywhere in tweet (case-insensitive)" do
76
+ bot.add_responses_for_phrase("good afternoon", "afternoon response")
77
+ tweet.stub(:text) { "this is a Good Afternoon" }
78
+ bot.response_for(tweet).should =~ /afternoon response/
79
+ end
80
+ end
81
+
82
+ describe "#should_i_respond_to?" do
83
+ before do
84
+ bot.stub(:rand) { 1 }
85
+ bot.stub(:tweet_matches?) { true }
86
+ end
87
+
88
+ context "Under rate limit" do
89
+ let(:now) { Time.now }
90
+ let(:status) { stub }
91
+ before do
92
+ Timecop.freeze(now)
93
+ bot.rate_limited!
94
+ end
95
+
96
+ it "won't allow response for 1 hour" do
97
+ bot.should_i_respond_to?(status).should be_false
98
+ Timecop.travel(now + 60 * 59) do
99
+ bot.should_i_respond_to?(status).should be_false
100
+ end
101
+ end
102
+
103
+ it "will allow response after an hour" do
104
+ Timecop.travel(now + 3600) do
105
+ bot.should_i_respond_to?(status).should be_true
106
+ end
107
+ end
108
+ end
109
+
110
+ it "only responds if rand is less than response_frequency" do
111
+ bot.response_frequency = 30
112
+ bot.stub(:rand) { 29 }
113
+ bot.should_i_respond_to?(stub).should be_true
114
+ bot.stub(:rand) { 30 }
115
+ bot.should_i_respond_to?(stub).should be_false
116
+ bot.stub(:rand) { 31 }
117
+ bot.should_i_respond_to?(stub).should be_false
118
+ end
119
+
120
+ it "only responds if phrase matches" do
121
+ bot.stub(:tweet_matches?) { true }
122
+ bot.should_i_respond_to?(stub).should be_true
123
+ bot.stub(:tweet_matches?) { false }
124
+ bot.should_i_respond_to?(stub).should be_false
125
+ end
126
+ end
127
+
128
+ describe "#respond_to_phrase" do
129
+ it "stores the given responses" do
130
+ bot.respond_to_phrase "morning fun" do |responses|
131
+ responses << "evening delight" << "afternoon rockets"
132
+ end
133
+ bot.responses_for_tweet(stub(:text => "morning fun")).should =~ ["evening delight", "afternoon rockets"]
134
+ end
135
+ end
136
+ end
data/tweetbot.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "tweetbot/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "tweetbot"
7
+ s.version = Tweetbot::VERSION
8
+ s.authors = ["coreyhaines"]
9
+ s.email = ["coreyhaines@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Tweetbot makes writing twitter bots twivial!}
12
+ s.description = %q{Using tweetbot, you can easily create twitter bots that respond to key phrases that people say on the twitters}
13
+
14
+ s.rubyforge_project = "tweetbot"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "timecop"
24
+ s.add_runtime_dependency "twitter"
25
+ s.add_runtime_dependency "tweetstream"
26
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tweetbot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - coreyhaines
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-13 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &2153278820 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *2153278820
25
+ - !ruby/object:Gem::Dependency
26
+ name: timecop
27
+ requirement: &2153278400 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2153278400
36
+ - !ruby/object:Gem::Dependency
37
+ name: twitter
38
+ requirement: &2153277980 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *2153277980
47
+ - !ruby/object:Gem::Dependency
48
+ name: tweetstream
49
+ requirement: &2153277540 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *2153277540
58
+ description: Using tweetbot, you can easily create twitter bots that respond to key
59
+ phrases that people say on the twitters
60
+ email:
61
+ - coreyhaines@gmail.com
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - .gitignore
67
+ - .rspec
68
+ - .rvmrc
69
+ - Gemfile
70
+ - README
71
+ - Rakefile
72
+ - example/run_bot.rb
73
+ - example/twitter_auth.rb
74
+ - lib/tweetbot.rb
75
+ - lib/tweetbot/bot.rb
76
+ - lib/tweetbot/talk.rb
77
+ - lib/tweetbot/version.rb
78
+ - spec/tweetbot_config_spec.rb
79
+ - spec/tweetbot_spec.rb
80
+ - tweetbot.gemspec
81
+ homepage: ''
82
+ licenses: []
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project: tweetbot
101
+ rubygems_version: 1.8.10
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: Tweetbot makes writing twitter bots twivial!
105
+ test_files:
106
+ - spec/tweetbot_config_spec.rb
107
+ - spec/tweetbot_spec.rb