diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index b124953..c1b5041 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -144,6 +144,8 @@ module Ebooks attr_accessor :access_token # @return [String] OAuth access secret from `ebooks auth` attr_accessor :access_token_secret + # @return [Twitter::User] Twitter user object of bot + attr_accessor :user # @return [String] Twitter username of bot attr_accessor :username # @return [Array] list of usernames to block on contact @@ -247,31 +249,18 @@ module Ebooks # Receive an event from the twitter stream # @param ev [Object] Twitter streaming event def receive_event(ev) - if ev.is_a? Array # Initial array sent on first connection + case ev + when Array # Initial array sent on first connection log "Online!" + fire(:connect, ev) return - end - - if ev.is_a? Twitter::DirectMessage - return if ev.sender.screen_name.downcase == @username.downcase # Don't reply to self + when Twitter::DirectMessage + return if ev.sender.id == @user.id # Don't reply to self log "DM from @#{ev.sender.screen_name}: #{ev.text}" fire(:message, ev) - - elsif ev.respond_to?(:name) - if ev.name == :follow - return if ev.source.screen_name.downcase == @username.downcase - log "Followed by #{ev.source.screen_name}" - fire(:follow, ev.source) - - elsif ev.name == :favorite || ev.name == :unfavorite - return if ev.source.screen_name.downcase == @username.downcase # Ignore our own favorites - log "@#{ev.source.screen_name} #{ev.name.to_s}d: #{ev.target_object.text}" - fire(ev.name, ev.source, ev.target_object) - end - - elsif ev.is_a? Twitter::Tweet + when Twitter::Tweet return unless ev.text # If it's not a text-containing tweet, ignore it - return if ev.user.screen_name.downcase == @username.downcase # Ignore our own tweets + return if ev.user.id == @user.id # Ignore our own tweets meta = meta(ev) @@ -295,15 +284,34 @@ module Ebooks else fire(:timeline, ev) end - - elsif ev.is_a?(Twitter::Streaming::DeletedTweet) || - ev.is_a?(Twitter::Streaming::Event) - # pass + when Twitter::Streaming::Event + case ev.name + when :follow + return if ev.source.id == @user.id + log "Followed by #{ev.source.screen_name}" + fire(:follow, ev.source) + when :favorite, :unfavorite + return if ev.source.id == @user.id # Ignore our own favorites + log "@#{ev.source.screen_name} #{ev.name.to_s}d: #{ev.target_object.text}" + fire(ev.name, ev.source, ev.target_object) + when :user_update + update_myself ev.source + end + when Twitter::Streaming::DeletedTweet + # Pass else log ev end end + # Updates @user and calls on_user_update. + def update_myself(new_me = twitter.user) + @user = new_me if @user.nil? || new_me.id == @user.id + @username = user.screen_name + log 'User information updated' + fire(:user_update) + end + # Configures client and fires startup event def prepare # Sanity check @@ -323,12 +331,12 @@ module Ebooks exit 1 end - real_name = twitter.user.screen_name - - if real_name != @username - log "connected to @#{real_name}-- please update config to match Twitter account name" - @username = real_name - end + # Save old name + old_name = username + # Load user object and actual username + update_myself + # Warn about mismatches unless it was clearly intentional + log "warning: bot expected to be @#{old_name} but connected to @#{username}" unless username == old_name || old_name.empty? fire(:startup) end diff --git a/spec/bot_spec.rb b/spec/bot_spec.rb index ba07a4d..f3ff393 100644 --- a/spec/bot_spec.rb +++ b/spec/bot_spec.rb @@ -24,8 +24,17 @@ end module Ebooks::Test # Generates a random twitter id - def twitter_id - (rand*10**18).to_i + # Or a non-random one, given a string. + def twitter_id(seed = nil) + if seed.nil? + (rand*10**18).to_i + else + id = 1 + seed.downcase.each_byte do |byte| + id *= byte/10 + end + id + end end # Creates a mock direct message @@ -33,7 +42,7 @@ module Ebooks::Test # @param text DM content def mock_dm(username, text) Twitter::DirectMessage.new(id: twitter_id, - sender: { id: twitter_id, screen_name: username}, + sender: { id: twitter_id(username), screen_name: username}, text: text) end @@ -45,7 +54,7 @@ module Ebooks::Test tweet = Twitter::Tweet.new({ id: twitter_id, in_reply_to_status_id: 'mock-link', - user: { id: twitter_id, screen_name: username }, + user: { id: twitter_id(username), screen_name: username }, text: text, created_at: Time.now.to_s, entities: { @@ -58,14 +67,21 @@ module Ebooks::Test tweet end + # Creates a mock user + def mock_user(username) + Twitter::User.new(id: twitter_id(username), screen_name: username) + end + def twitter_spy(bot) twitter = spy("twitter") allow(twitter).to receive(:update).and_return(mock_tweet(bot.username, "test tweet")) + allow(twitter).to receive(:user).with(no_args).and_return(mock_user(bot.username)) twitter end def simulate(bot, &b) bot.twitter = twitter_spy(bot) + bot.update_myself # Usually called in prepare b.call end @@ -95,6 +111,13 @@ describe Ebooks::Bot do end end + it "ignores its own dms" do + simulate(bot) do + expect(bot).to_not receive(:on_message) + bot.receive_event(mock_dm("Test_Ebooks", "why am I talking to myself")) + end + end + it "responds to mentions" do simulate(bot) do bot.receive_event(mock_tweet("m1sp", "@test_ebooks this is a mention")) @@ -102,6 +125,14 @@ describe Ebooks::Bot do end end + it "ignores its own mentions" do + simulate(bot) do + expect(bot).to_not receive(:on_mention) + expect(bot).to_not receive(:on_timeline) + bot.receive_event(mock_tweet("Test_Ebooks", "@m1sp i think that @test_ebooks is best bot")) + end + end + it "responds to timeline tweets" do simulate(bot) do bot.receive_event(mock_tweet("m1sp", "some excellent tweet")) @@ -109,6 +140,13 @@ describe Ebooks::Bot do end end + it "ignores its own timeline tweets" do + simulate(bot) do + expect(bot).to_not receive(:on_timeline) + bot.receive_event(mock_tweet("Test_Ebooks", "pudding is cute")) + end + end + it "links tweets to conversations correctly" do tweet1 = mock_tweet("m1sp", "tweet 1", id: 1, in_reply_to_status_id: nil)