From d657f25be494c0b17f3d9503f65f40a62e9795b2 Mon Sep 17 00:00:00 2001 From: Stawberri Date: Thu, 15 Jan 2015 02:34:45 -0800 Subject: [PATCH 01/12] Create @user variable Since #prepare already calls twitter.user, it'd be best to just save it! --- lib/twitter_ebooks/bot.rb | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index 69cd12e..88177b6 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 @@ -304,6 +306,16 @@ module Ebooks end end + # Updates @user and calls on_user_update. Make sure it's the right person before you call it. + def update_user_object(new_me = twitter.user) + new_me = twitter.user unless new_me.is_a? Twitter::User + + @user = new_me + @username = user.name + + fire(:user_update) + end + # Configures client and fires startup event def prepare # Sanity check @@ -323,12 +335,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_user_object + # Warn about mismatches unless it was clearly intensional + log "warning: bot expected to be @#{old_name} but connected to @#{username}" unless username == old_name || old_name.empty? fire(:startup) end From 0f56a1a6ebf78f6593141c48e13704a9a76a3c7d Mon Sep 17 00:00:00 2001 From: Stawberri Date: Thu, 15 Jan 2015 02:39:01 -0800 Subject: [PATCH 02/12] Update @user when receiving a user_update event. --- lib/twitter_ebooks/bot.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index 88177b6..9ba1d08 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -260,15 +260,17 @@ module Ebooks fire(:message, ev) elsif ev.respond_to?(:name) - if ev.name == :follow + case ev.name + when :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 + when :favorite, :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) + when :user_update + update_user_object ev.source end elsif ev.is_a? Twitter::Tweet From 023cbb31832ec829d71e70c45b4a822387ec1e6f Mon Sep 17 00:00:00 2001 From: Stawberri Date: Thu, 15 Jan 2015 02:46:09 -0800 Subject: [PATCH 03/12] Restructure receive_event to use case This is much more readable! --- lib/twitter_ebooks/bot.rb | 43 +++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index 9ba1d08..4b78a13 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -249,31 +249,15 @@ 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!" return - end - - if ev.is_a? Twitter::DirectMessage + when Twitter::DirectMessage return if ev.sender.screen_name.downcase == @username.downcase # Don't reply to self log "DM from @#{ev.sender.screen_name}: #{ev.text}" fire(:message, ev) - - elsif ev.respond_to?(:name) - case ev.name - when :follow - return if ev.source.screen_name.downcase == @username.downcase - log "Followed by #{ev.source.screen_name}" - fire(:follow, ev.source) - when :favorite, :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) - when :user_update - update_user_object ev.source - 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 @@ -299,10 +283,21 @@ 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.screen_name.downcase == @username.downcase + log "Followed by #{ev.source.screen_name}" + fire(:follow, ev.source) + when :favorite, :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) + when :user_update + update_user_object ev.source + end + when Twitter::Streaming::DeletedTweet + # Pass else log ev end From 5c6746cc46f5532b3e1c8854b92da699747ca1f4 Mon Sep 17 00:00:00 2001 From: Stawberri Date: Thu, 15 Jan 2015 02:49:16 -0800 Subject: [PATCH 04/12] Renamed update_user_object Just to reduce confusion, to make sure people don't accidentally set @user to someone else in the future. --- lib/twitter_ebooks/bot.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index 4b78a13..646079a 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -294,7 +294,7 @@ module Ebooks 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_user_object ev.source + update_myself ev.source end when Twitter::Streaming::DeletedTweet # Pass @@ -304,7 +304,7 @@ module Ebooks end # Updates @user and calls on_user_update. Make sure it's the right person before you call it. - def update_user_object(new_me = twitter.user) + def update_myself(new_me = twitter.user) new_me = twitter.user unless new_me.is_a? Twitter::User @user = new_me @@ -335,7 +335,7 @@ module Ebooks # Save old name old_name = username # Load user object and actual username - update_user_object + update_myself # Warn about mismatches unless it was clearly intensional log "warning: bot expected to be @#{old_name} but connected to @#{username}" unless username == old_name || old_name.empty? From 442dcb3370d8e8bdfb38e3de08388fdb1d31a8bd Mon Sep 17 00:00:00 2001 From: Stawberri Date: Thu, 15 Jan 2015 03:07:42 -0800 Subject: [PATCH 05/12] Compare ids instead of usernames Non user-editable values are less prone to strange errors, right? --- lib/twitter_ebooks/bot.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index 646079a..d951d40 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -254,12 +254,12 @@ module Ebooks log "Online!" return when Twitter::DirectMessage - return if ev.sender.screen_name.downcase == @username.downcase # Don't reply to self + return if ev.sender.id == @user.id # Don't reply to self log "DM from @#{ev.sender.screen_name}: #{ev.text}" fire(:message, ev) 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) @@ -286,11 +286,11 @@ module Ebooks when Twitter::Streaming::Event case ev.name when :follow - return if ev.source.screen_name.downcase == @username.downcase + 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.screen_name.downcase == @username.downcase # Ignore our own favorites + 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 From c88aefece8087d78d21e7cc6e45d139a26ab5fcf Mon Sep 17 00:00:00 2001 From: Stawberri Date: Thu, 15 Jan 2015 15:51:13 -0800 Subject: [PATCH 06/12] New on_connect event when stream is connected. --- lib/twitter_ebooks/bot.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index d951d40..eb5cf51 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -252,6 +252,7 @@ module Ebooks case ev when Array # Initial array sent on first connection log "Online!" + fire(:connect) return when Twitter::DirectMessage return if ev.sender.id == @user.id # Don't reply to self From 19f670ed424abfeb30f9568d49d36782c38a2ba3 Mon Sep 17 00:00:00 2001 From: Stawberri Date: Fri, 16 Jan 2015 21:36:26 -0800 Subject: [PATCH 07/12] Redundant line removal I can't spell. --- lib/twitter_ebooks/bot.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index eb5cf51..5c2bc54 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -304,13 +304,10 @@ module Ebooks end end - # Updates @user and calls on_user_update. Make sure it's the right person before you call it. + # Updates @user and calls on_user_update. def update_myself(new_me = twitter.user) - new_me = twitter.user unless new_me.is_a? Twitter::User - @user = new_me @username = user.name - fire(:user_update) end @@ -337,7 +334,7 @@ module Ebooks old_name = username # Load user object and actual username update_myself - # Warn about mismatches unless it was clearly intensional + # 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) From 770739d922fe94f8d0b800715bf52ba0a5ab7163 Mon Sep 17 00:00:00 2001 From: Stawberri Date: Fri, 16 Jan 2015 22:13:20 -0800 Subject: [PATCH 08/12] Check ID before updating user --- lib/twitter_ebooks/bot.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index 5c2bc54..e5924fc 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -306,7 +306,7 @@ module Ebooks # Updates @user and calls on_user_update. def update_myself(new_me = twitter.user) - @user = new_me + @user = new_me if @user.nil? || new_me.id == @user.id @username = user.name fire(:user_update) end From b541b2145603639ef1b271b268f958c26ed99cc8 Mon Sep 17 00:00:00 2001 From: Stawberri Date: Fri, 16 Jan 2015 22:49:26 -0800 Subject: [PATCH 09/12] Spec tests updates Also added some new anti-responding-to-self tests. --- spec/bot_spec.rb | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) 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) From fd0529021a33a5f98834e945078d4c209e7d78c9 Mon Sep 17 00:00:00 2001 From: Stawberri Date: Fri, 16 Jan 2015 22:58:46 -0800 Subject: [PATCH 10/12] Fixed a variable name Well, that's embarrassing. --- lib/twitter_ebooks/bot.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index e5924fc..8e32bda 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -307,7 +307,7 @@ module Ebooks # 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.name + @username = user.screen_name fire(:user_update) end From 499056e2e34d8ef1fa0bfac921376af0a45dbeea Mon Sep 17 00:00:00 2001 From: Stawberri Date: Sat, 17 Jan 2015 21:16:57 -0800 Subject: [PATCH 11/12] Send user ID array with on_connect. This would be useful for people who might want to do some follow list processing without using an extra requests (i.e. to compare with a list of followers) --- lib/twitter_ebooks/bot.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index 8e32bda..d8f7098 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -252,7 +252,7 @@ module Ebooks case ev when Array # Initial array sent on first connection log "Online!" - fire(:connect) + fire(:connect, ev) return when Twitter::DirectMessage return if ev.sender.id == @user.id # Don't reply to self From 13e12922da4020207903ff75ff0086cfe8055b49 Mon Sep 17 00:00:00 2001 From: Stawberri Date: Sun, 18 Jan 2015 14:59:13 -0800 Subject: [PATCH 12/12] Log message Now shows a log message when user information is updated --- lib/twitter_ebooks/bot.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/twitter_ebooks/bot.rb b/lib/twitter_ebooks/bot.rb index d8f7098..e724d72 100644 --- a/lib/twitter_ebooks/bot.rb +++ b/lib/twitter_ebooks/bot.rb @@ -308,6 +308,7 @@ module Ebooks 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