twittertype 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|