tweetbot 0.1.3

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.
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