twittertype 0.1.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.
- data/README.rdoc +36 -0
- data/lib/profilefactory.rb +25 -0
- data/lib/tweeterprofile.rb +84 -0
- data/lib/twitter_type.rb +11 -0
- data/lib/twitterclient.rb +13 -0
- data/lib/twitterclientwrapper.rb +39 -0
- data/lib/twittertypeinferrer.rb +29 -0
- data/test/functional/twittertypeinferrer_spec.rb +12 -0
- data/test/unit/profilefactory_spec.rb +56 -0
- data/test/unit/tweeterprofile_spec.rb +177 -0
- data/test/unit/twitterclient_spec.rb +12 -0
- data/test/unit/twitterclientwrapper_spec.rb +37 -0
- data/test/unit/twittertypeinferrer_spec.rb +47 -0
- metadata +86 -0
data/README.rdoc
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
= Overview
|
2
|
+
TwitterType is a basic utility that analyses a Twitter user's tweets and
|
3
|
+
determine what type of Tweeter the user is. The set of types identified are:
|
4
|
+
|
5
|
+
[:+chatter+] most tweets are in response to tweets by other Twitter users
|
6
|
+
[:+linker+] most tweets include links to URLs
|
7
|
+
[:+retweeter+] most tweets are forwarding on other people's tweets
|
8
|
+
[:+originator+] most tweets are original content, not containing links, retweets or responses
|
9
|
+
[:+unknown_inconclusive+] no strong trends to base type on.
|
10
|
+
[:+unknown_silent+] no tweets found in last 7 days
|
11
|
+
[:+unknown_protected_user+] user has not granted public access to tweets
|
12
|
+
|
13
|
+
= Usage
|
14
|
+
|
15
|
+
Simply run the twitter_type.rb file passing the Twitter screen name of the person you wish to analyse.
|
16
|
+
The analysis of this persons most tweets from the last 7 days will be displayed.
|
17
|
+
|
18
|
+
For example:
|
19
|
+
|
20
|
+
ruby twitter_type.rb APlusK
|
21
|
+
APlusK: type :linker, #tweets 20, #replies 1, #retweets 5, #links 7
|
22
|
+
|
23
|
+
To analyse multiple Twitter user's, simply pass each on the command line. For example:
|
24
|
+
|
25
|
+
ruby twitter_type.rb APlusK BillGates EvaLongoria
|
26
|
+
APlusK: type :originator, #tweets 20, #replies 1, #retweets 5, #links 6
|
27
|
+
BillGates: type :linker, #tweets 19, #replies 1, #retweets 2, #links 11
|
28
|
+
EvaLongoria: type :linker, #tweets 19, #replies 3, #retweets 1, #links 11
|
29
|
+
|
30
|
+
= Notes
|
31
|
+
|
32
|
+
* The Twitter screen name parameter is case insensitive.
|
33
|
+
* Each tweet will only be classified as one of a link, retweet, reply or original tweet. For example, a retweet containing a link is just a retweet and not both.
|
34
|
+
|
35
|
+
= Copyright
|
36
|
+
Copyright (c) 2010 Andy Marks, released under the MIT license
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'tweeterprofile'
|
2
|
+
|
3
|
+
module TwitterType
|
4
|
+
|
5
|
+
class ProfileFactory
|
6
|
+
|
7
|
+
def initialize(user)
|
8
|
+
raise ArgumentError.new("No user name supplied") if user.nil? or !user.is_a?(String)
|
9
|
+
@user = user
|
10
|
+
end
|
11
|
+
|
12
|
+
def build(tweets)
|
13
|
+
raise ArgumentError.new("No tweets to analyse") if tweets.nil?
|
14
|
+
raise ArgumentError.new("Tweets are not enumerable") if !tweets.is_a?(Enumerable)
|
15
|
+
|
16
|
+
profile = TweeterProfile.new(@user)
|
17
|
+
tweets.each do |tweet|
|
18
|
+
profile.update_from(tweet)
|
19
|
+
end
|
20
|
+
|
21
|
+
return profile
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module TwitterType
|
2
|
+
|
3
|
+
class TweeterProfile
|
4
|
+
attr_accessor :screen_name,
|
5
|
+
:tweet_count,
|
6
|
+
:reply_count,
|
7
|
+
:retweet_count,
|
8
|
+
:link_count,
|
9
|
+
:inferred_type
|
10
|
+
|
11
|
+
def initialize(screen_name)
|
12
|
+
@tweet_count = 0
|
13
|
+
@reply_count = 0
|
14
|
+
@retweet_count = 0
|
15
|
+
@link_count = 0
|
16
|
+
@screen_name = screen_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_from(tweet)
|
20
|
+
begin
|
21
|
+
@tweet_count = @tweet_count + 1
|
22
|
+
|
23
|
+
if tweet.text.slice(0, 1) == '@'
|
24
|
+
@reply_count = @reply_count + 1
|
25
|
+
return
|
26
|
+
end
|
27
|
+
|
28
|
+
@retweet_count = @retweet_count + 1 if tweet.text.slice(0, 2) == 'RT'
|
29
|
+
@link_count = @link_count + 1 if tweet.text.index('http://') != nil
|
30
|
+
rescue NoMethodError => root
|
31
|
+
raise ArgumentError.new("Missing method responses in tweet structure:" + root.to_s)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_s
|
36
|
+
type_to_s = (@inferred_type ? "type " + @inferred_type.inspect + ", " : "")
|
37
|
+
@screen_name + ": " + type_to_s + "#tweets " + @tweet_count.to_s + ", #replies " + @reply_count.to_s + ", #retweets " + @retweet_count.to_s + ", #links " + @link_count.to_s + "\n"
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(other)
|
41
|
+
result = @screen_name == other.screen_name
|
42
|
+
result = result && @tweet_count == other.tweet_count
|
43
|
+
result = result && @reply_count == other.reply_count
|
44
|
+
result = result && @retweet_count == other.retweet_count
|
45
|
+
result = result && @link_count == other.link_count
|
46
|
+
|
47
|
+
return result
|
48
|
+
end
|
49
|
+
|
50
|
+
def infer_type
|
51
|
+
|
52
|
+
attributes = [@retweet_count, @link_count, @reply_count]
|
53
|
+
highest_count = attributes.max
|
54
|
+
|
55
|
+
raise ArgumentError if @tweet_count < highest_count
|
56
|
+
|
57
|
+
@inferred_type = set_type(highest_count)
|
58
|
+
|
59
|
+
return @inferred_type
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def set_type(highest_count)
|
64
|
+
return :unknown_silent if @tweet_count == 0
|
65
|
+
|
66
|
+
non_original_tweet_count = @retweet_count + @link_count + @reply_count
|
67
|
+
original_tweets = @tweet_count - non_original_tweet_count
|
68
|
+
return :originator if original_tweets > highest_count
|
69
|
+
|
70
|
+
return :unknown_inconclusive if equal_highest?(highest_count, @retweet_count, @link_count)
|
71
|
+
return :unknown_inconclusive if equal_highest?(highest_count, @retweet_count, @reply_count)
|
72
|
+
return :unknown_inconclusive if equal_highest?(highest_count, @link_count, @reply_count)
|
73
|
+
|
74
|
+
return :retweeter if highest_count == @retweet_count
|
75
|
+
return :linker if highest_count == @link_count
|
76
|
+
return :chatter if highest_count == @reply_count
|
77
|
+
end
|
78
|
+
|
79
|
+
def equal_highest?(highest_count, first, second)
|
80
|
+
highest_count == first and highest_count == second
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
data/lib/twitter_type.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'twitter'
|
3
|
+
|
4
|
+
module TwitterType
|
5
|
+
|
6
|
+
class TwitterClient
|
7
|
+
def gather_recent_tweets_for (screen_name)
|
8
|
+
raise ArgumentError if screen_name.nil? or screen_name.strip.size == 0
|
9
|
+
|
10
|
+
Twitter::Client.new.timeline_for(:user, :id => screen_name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'twitterclient'
|
3
|
+
|
4
|
+
module TwitterType
|
5
|
+
|
6
|
+
class TwitterClientWrapper < TwitterClient
|
7
|
+
|
8
|
+
attr_reader :client
|
9
|
+
|
10
|
+
def initialize(client = TwitterClient.new)
|
11
|
+
@client = client
|
12
|
+
end
|
13
|
+
|
14
|
+
def gather_recent_tweets_for (screen_name)
|
15
|
+
begin
|
16
|
+
@client.gather_recent_tweets_for(screen_name)
|
17
|
+
rescue Twitter::RESTError => error
|
18
|
+
raise ProtectedUserAccessError.new(error.to_s) if ProtectedUserAccessError.fits(error)
|
19
|
+
raise TwitterClientError.new(error.to_s)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class TwitterClientError < StandardError
|
25
|
+
attr :message
|
26
|
+
|
27
|
+
def initialize(message)
|
28
|
+
@message = message
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class ProtectedUserAccessError < TwitterClientError
|
33
|
+
CODE = "401"
|
34
|
+
|
35
|
+
def self.fits(error)
|
36
|
+
return !error.code.index(CODE).nil?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "twitterclientwrapper"
|
2
|
+
require "profilefactory"
|
3
|
+
|
4
|
+
module TwitterType
|
5
|
+
|
6
|
+
class TypeInferrer
|
7
|
+
attr_writer :client
|
8
|
+
attr_reader :profile
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
@client = TwitterClientWrapper.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def infer(user)
|
15
|
+
begin
|
16
|
+
tweets = @client.gather_recent_tweets_for(user)
|
17
|
+
@profile = ProfileFactory.new(user).build(tweets)
|
18
|
+
@profile.infer_type
|
19
|
+
rescue TwitterType::ProtectedUserAccessError => error
|
20
|
+
@profile = ProfileFactory.new(user).build([])
|
21
|
+
@profile.inferred_type = :unknown_protected_user
|
22
|
+
end
|
23
|
+
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "spec"
|
2
|
+
require "profilefactory"
|
3
|
+
require 'tweeterprofile'
|
4
|
+
|
5
|
+
describe TwitterType::ProfileFactory do
|
6
|
+
before(:each) do
|
7
|
+
@empty_profile = TweeterProfile.new("user")
|
8
|
+
@factory = TwitterType::ProfileFactory.new(@empty_profile.screen_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup_valid_fields_in_tweet
|
12
|
+
mock_valid_tweet = mock()
|
13
|
+
mock_valid_tweet.stub!(:to_user).and_return("user")
|
14
|
+
mock_valid_tweet.stub!(:text).and_return("RT textm http://www.cool.com")
|
15
|
+
mock_valid_tweet.stub!(:id).and_return(12345678)
|
16
|
+
|
17
|
+
return mock_valid_tweet
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should fail construction without a user" do
|
21
|
+
lambda {ProfileFactory.new}.should raise_error(ArgumentError)
|
22
|
+
lambda {ProfileFactory.new(nil)}.should raise_error(ArgumentError)
|
23
|
+
lambda {ProfileFactory.new(1)}.should raise_error(ArgumentError)
|
24
|
+
|
25
|
+
ProfileFactory.new("user")
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should fail if it cannot enumerate tweets" do
|
29
|
+
lambda {@factory.build()}.should raise_error(ArgumentError)
|
30
|
+
lambda {@factory.build(nil)}.should raise_error(ArgumentError)
|
31
|
+
lambda {@factory.build("tweets")}.should raise_error(ArgumentError)
|
32
|
+
lambda {@factory.build([1, 2, 3])}.should raise_error(ArgumentError)
|
33
|
+
|
34
|
+
@factory.build(Array.new())
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return an empty profile if no tweets are supplied" do
|
38
|
+
@factory.build(Array.new()).should == @empty_profile
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should take a tweet with a valid set of fields" do
|
42
|
+
lambda {@factory.build(Array.new(1, "tweet"))}.should raise_error(ArgumentError)
|
43
|
+
|
44
|
+
mock_valid_tweet = setup_valid_fields_in_tweet()
|
45
|
+
|
46
|
+
@factory.build(Array.new(1, mock_valid_tweet))
|
47
|
+
end
|
48
|
+
|
49
|
+
it "needs the tweet text to be a string" do
|
50
|
+
tweet_with_non_string_text = mock()
|
51
|
+
tweet_with_non_string_text.stub!(:to_user).and_return(nil)
|
52
|
+
tweet_with_non_string_text.stub!(:text).and_return(1)
|
53
|
+
|
54
|
+
lambda {@factory.build(Array.new(1, tweet_with_non_string_text))}.should raise_error(ArgumentError)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'spec'
|
2
|
+
require 'tweeterprofile'
|
3
|
+
|
4
|
+
describe TwitterType::TweeterProfile, " before update" do
|
5
|
+
|
6
|
+
before(:each) do
|
7
|
+
@profile = TweeterProfile.new("andy")
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should provide a valid string representation for a newly created profile" do
|
11
|
+
TweeterProfile.new("andy").to_s.should eql("andy: #tweets 0, #replies 0, #retweets 0, #links 0\n")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should be equal to another profile with the same values" do
|
15
|
+
first = TweeterProfile.new("user")
|
16
|
+
first.link_count = 1
|
17
|
+
first.retweet_count = 2
|
18
|
+
first.reply_count = 3
|
19
|
+
first.tweet_count = 4
|
20
|
+
|
21
|
+
second = TweeterProfile.new(first.screen_name)
|
22
|
+
second.link_count = first.link_count
|
23
|
+
second.retweet_count = first.retweet_count
|
24
|
+
second.reply_count = first.reply_count
|
25
|
+
second.tweet_count = first.tweet_count
|
26
|
+
|
27
|
+
first.equal?(second).should == false
|
28
|
+
(first == second).should == true
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should provide a valid string representation for an updated profile" do
|
32
|
+
@profile.tweet_count = 1
|
33
|
+
@profile.reply_count = 2
|
34
|
+
@profile.retweet_count = 3
|
35
|
+
@profile.link_count = 4
|
36
|
+
|
37
|
+
@profile.to_s.should eql("andy: #tweets 1, #replies 2, #retweets 3, #links 4\n")
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
describe TwitterType::TweeterProfile, " being updated" do
|
44
|
+
|
45
|
+
before(:each) do
|
46
|
+
@profile = TweeterProfile.new("andy")
|
47
|
+
end
|
48
|
+
|
49
|
+
Tweet = Struct.new(:text, :to_user)
|
50
|
+
|
51
|
+
it "should fail when asked to update itself from a nil tweet" do
|
52
|
+
lambda {@profile.update_from(nil)}.should raise_error(ArgumentError)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should increase the tweet count for each tweet found" do
|
56
|
+
@profile.tweet_count.should == 0
|
57
|
+
|
58
|
+
@profile.update_from(Tweet.new("text"))
|
59
|
+
@profile.tweet_count.should == 1
|
60
|
+
|
61
|
+
@profile.update_from(Tweet.new("text"))
|
62
|
+
@profile.tweet_count.should == 2
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should increase the reply count for each tweet sent to a user" do
|
67
|
+
@profile.reply_count.should == 0
|
68
|
+
|
69
|
+
@profile.update_from(Tweet.new("@user text"))
|
70
|
+
@profile.reply_count.should == 1
|
71
|
+
|
72
|
+
@profile.update_from(Tweet.new("@user text"))
|
73
|
+
@profile.reply_count.should == 2
|
74
|
+
|
75
|
+
@profile.update_from(Tweet.new("text"))
|
76
|
+
@profile.reply_count.should == 2
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should let a reply take precedence over a retweet" do
|
80
|
+
@profile.reply_count.should == 0
|
81
|
+
@profile.update_from(Tweet.new("@user text"))
|
82
|
+
@profile.reply_count.should == 1
|
83
|
+
@profile.retweet_count.should == 0
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should increase the retweet count for each tweet forwarded from a user" do
|
87
|
+
@profile.retweet_count.should == 0
|
88
|
+
|
89
|
+
@profile.update_from(Tweet.new("RT text"))
|
90
|
+
@profile.retweet_count.should == 1
|
91
|
+
|
92
|
+
@profile.update_from(Tweet.new("RT text"))
|
93
|
+
@profile.retweet_count.should == 2
|
94
|
+
|
95
|
+
@profile.update_from(Tweet.new("text"))
|
96
|
+
@profile.retweet_count.should == 2
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should increase the link count for each tweet mentioning an URL" do
|
100
|
+
@profile.link_count.should == 0
|
101
|
+
|
102
|
+
@profile.update_from(Tweet.new("text http://www.twitter.com", nil))
|
103
|
+
@profile.link_count.should == 1
|
104
|
+
|
105
|
+
@profile.update_from(Tweet.new("text http://www.twitter.com", nil))
|
106
|
+
@profile.link_count.should == 2
|
107
|
+
|
108
|
+
@profile.update_from(Tweet.new("text"))
|
109
|
+
@profile.link_count.should == 2
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
describe TwitterType::TweeterProfile, " inferring a type" do
|
115
|
+
before(:each) do
|
116
|
+
@cut = TweeterProfile.new("user")
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should fail to infer a profile which has less tweets than possible given other attributes" do
|
120
|
+
setup_profile({:retweet_count => 1, :link_count => 0, :reply_count => 0, :tweet_count => 0})
|
121
|
+
lambda{@cut.infer_type}.should raise_error(ArgumentError)
|
122
|
+
|
123
|
+
setup_profile({:retweet_count => 0, :link_count => 1, :reply_count => 0, :tweet_count => 0})
|
124
|
+
lambda{@cut.infer_type}.should raise_error(ArgumentError)
|
125
|
+
|
126
|
+
setup_profile({:retweet_count => 0, :link_count => 0, :reply_count => 1, :tweet_count => 0})
|
127
|
+
lambda{@cut.infer_type}.should raise_error(ArgumentError)
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should infer retweeter from a profile with predominantly retweets" do
|
131
|
+
setup_profile({:retweet_count => 1, :link_count => 0, :reply_count => 0, :tweet_count => 1})
|
132
|
+
@cut.infer_type.should == :retweeter
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should included the inferred type in the string representation" do
|
136
|
+
setup_profile({:retweet_count => 1, :link_count => 0, :reply_count => 0, :tweet_count => 1})
|
137
|
+
@cut.infer_type
|
138
|
+
@cut.to_s.should eql("user: type :retweeter, #tweets 1, #replies 0, #retweets 1, #links 0\n")
|
139
|
+
end
|
140
|
+
|
141
|
+
it "should infer linker from a profile with only links" do
|
142
|
+
setup_profile({:retweet_count => 0, :link_count => 1, :reply_count => 0, :tweet_count => 1})
|
143
|
+
@cut.infer_type.should == :linker
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should infer linker from a profile with predominantly links" do
|
147
|
+
setup_profile({:retweet_count => 0, :link_count => 15, :reply_count => 0, :tweet_count => 20})
|
148
|
+
@cut.infer_type.should == :linker
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should infer chatter from a profile with predominantly replies" do
|
152
|
+
setup_profile({:retweet_count => 0, :link_count => 0, :reply_count => 1, :tweet_count => 1})
|
153
|
+
@cut.infer_type.should == :chatter
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should infer originator from a profile with predominantly original tweets" do
|
157
|
+
setup_profile({:retweet_count => 1, :link_count => 1, :reply_count => 1, :tweet_count => 10})
|
158
|
+
@cut.infer_type.should == :originator
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should infer no clear type from a profile with no clear trends" do
|
162
|
+
setup_profile({:retweet_count => 1, :link_count => 1, :reply_count => 1, :tweet_count => 3})
|
163
|
+
@cut.infer_type.should == :unknown_inconclusive
|
164
|
+
end
|
165
|
+
|
166
|
+
it "should infer no clear type from an empty profile" do
|
167
|
+
setup_profile({:retweet_count => 0, :link_count => 0, :reply_count => 0, :tweet_count => 0})
|
168
|
+
@cut.infer_type.should == :unknown_silent
|
169
|
+
end
|
170
|
+
|
171
|
+
def setup_profile(return_values)
|
172
|
+
@cut.retweet_count = return_values[:retweet_count]
|
173
|
+
@cut.link_count = return_values[:link_count]
|
174
|
+
@cut.reply_count = return_values[:reply_count]
|
175
|
+
@cut.tweet_count = return_values[:tweet_count]
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "spec"
|
2
|
+
require "twitterclient"
|
3
|
+
|
4
|
+
describe TwitterType::TwitterClient do
|
5
|
+
|
6
|
+
it "should fail with an empty or nil user name" do
|
7
|
+
lambda{TwitterClient.new.gather_recent_tweets_for(nil)}.should raise_error(ArgumentError)
|
8
|
+
lambda{TwitterClient.new.gather_recent_tweets_for("")}.should raise_error(ArgumentError)
|
9
|
+
lambda{TwitterClient.new.gather_recent_tweets_for(" ")}.should raise_error(ArgumentError)
|
10
|
+
lambda{TwitterClient.new.gather_recent_tweets_for(' ')}.should raise_error(ArgumentError)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec"
|
2
|
+
require "twitterclientwrapper"
|
3
|
+
|
4
|
+
describe TwitterType::TwitterClientWrapper do
|
5
|
+
|
6
|
+
it "should subclass TwitterClient" do
|
7
|
+
TwitterClientWrapper.superclass.should == TwitterClient
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should allow injection of a TwitterClient" do
|
11
|
+
TwitterClientWrapper.new(TwitterClient.new)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should default the client to a TwitterClient instance" do
|
15
|
+
TwitterClientWrapper.new.client.class.should == TwitterClient
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should turn raise a custom exception when an attempt to access a protected user's tweets is made" do
|
19
|
+
protected_user_access_error = Twitter::RESTError.new
|
20
|
+
protected_user_access_error.code = "401"
|
21
|
+
|
22
|
+
client = mock
|
23
|
+
client.stub!(:gather_recent_tweets_for).with("protected_user").and_raise(protected_user_access_error)
|
24
|
+
|
25
|
+
lambda{TwitterClientWrapper.new(client).gather_recent_tweets_for("protected_user")}.should raise_error(ProtectedUserAccessError)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should turn any other exceptiona raised by client into a generci exception" do
|
29
|
+
access_error = Twitter::RESTError.new
|
30
|
+
access_error.code = "404"
|
31
|
+
|
32
|
+
client = mock
|
33
|
+
client.stub!(:gather_recent_tweets_for).with("dodgy_user").and_raise(access_error)
|
34
|
+
|
35
|
+
lambda{TwitterClientWrapper.new(client).gather_recent_tweets_for("dodgy_user")}.should raise_error(TwitterClientError)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec'
|
2
|
+
require 'twittertypeinferrer'
|
3
|
+
require 'twitterclient'
|
4
|
+
|
5
|
+
include TwitterType
|
6
|
+
|
7
|
+
describe TypeInferrer do
|
8
|
+
|
9
|
+
VALID_TWITTER_USER = "andee_marks"
|
10
|
+
|
11
|
+
Tweet = Struct.new(:text, :to_user)
|
12
|
+
|
13
|
+
before(:each) do
|
14
|
+
@cut = TypeInferrer.new
|
15
|
+
@mock_client = mock()
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should fail gracefully if the Twitter client fails in some way" do
|
19
|
+
@mock_client.stub!(:gather_recent_tweets_for).with(VALID_TWITTER_USER).and_raise(TwitterClientError.new(nil))
|
20
|
+
@cut.client = @mock_client
|
21
|
+
|
22
|
+
@cut.profile.should == nil
|
23
|
+
lambda {@cut.infer(VALID_TWITTER_USER)}.should raise_error(TwitterClientError)
|
24
|
+
@cut.profile.should == nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should use an unknown_unauthorised type for any protected screen name" do
|
28
|
+
@mock_client.stub!(:gather_recent_tweets_for).with(VALID_TWITTER_USER).and_raise(ProtectedUserAccessError.new(nil))
|
29
|
+
@cut.client = @mock_client
|
30
|
+
|
31
|
+
@cut.profile.should == nil
|
32
|
+
@cut.infer(VALID_TWITTER_USER)
|
33
|
+
@cut.profile.inferred_type.should == :unknown_protected_user
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should infer a type for a Twitter User" do
|
38
|
+
tweets = Array.new(1) {|i| Tweet.new("test", "to user")}
|
39
|
+
@mock_client.stub!(:gather_recent_tweets_for).with(VALID_TWITTER_USER).and_return(tweets)
|
40
|
+
|
41
|
+
@cut.client = @mock_client
|
42
|
+
|
43
|
+
@cut.profile.should == nil
|
44
|
+
@cut.infer(VALID_TWITTER_USER)
|
45
|
+
@cut.profile.inferred_type.should_not == :undetermined
|
46
|
+
end
|
47
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: twittertype
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Andy Marks
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-03-01 00:00:00 +11:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: twitter4r
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
- 3
|
30
|
+
- 2
|
31
|
+
version: 0.3.2
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description:
|
35
|
+
email: andy@corvine.org
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- README.rdoc
|
42
|
+
files:
|
43
|
+
- lib/profilefactory.rb
|
44
|
+
- lib/tweeterprofile.rb
|
45
|
+
- lib/twitter_type.rb
|
46
|
+
- lib/twitterclient.rb
|
47
|
+
- lib/twitterclientwrapper.rb
|
48
|
+
- lib/twittertypeinferrer.rb
|
49
|
+
- README.rdoc
|
50
|
+
has_rdoc: true
|
51
|
+
homepage: http://github.com/andeemarks/Twitter-Type
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 1.3.6
|
77
|
+
signing_key:
|
78
|
+
specification_version: 3
|
79
|
+
summary: A utility for analysing Twitter user tweet patterns
|
80
|
+
test_files:
|
81
|
+
- test/functional/twittertypeinferrer_spec.rb
|
82
|
+
- test/unit/profilefactory_spec.rb
|
83
|
+
- test/unit/tweeterprofile_spec.rb
|
84
|
+
- test/unit/twitterclient_spec.rb
|
85
|
+
- test/unit/twitterclientwrapper_spec.rb
|
86
|
+
- test/unit/twittertypeinferrer_spec.rb
|