impostor 0.2.1 → 1.0.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/.gemtest +0 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +47 -0
- data/History.txt +8 -0
- data/Manifest.txt +87 -55
- data/README.txt +11 -11
- data/Rakefile +16 -20
- data/lib/impostor/auth.rb +103 -0
- data/lib/impostor/config.rb +171 -0
- data/lib/impostor/errors.rb +67 -0
- data/lib/impostor/phpbb2.rb +202 -0
- data/lib/impostor/phpbb3.rb +199 -0
- data/lib/impostor/post.rb +111 -0
- data/lib/impostor/topic.rb +115 -0
- data/lib/impostor/wwf79.rb +186 -0
- data/lib/impostor/wwf80.rb +190 -0
- data/lib/impostor.rb +108 -5
- data/spec/auth_spec.rb +148 -0
- data/spec/base_spec_helper.rb +12 -0
- data/{test/test_helper.rb → spec/caged_net_http.rb} +8 -17
- data/spec/config_spec.rb +136 -0
- data/spec/fixtures/junk.html +1 -0
- data/{test → spec}/fixtures/phpbb2-get-new_topic-form-good-response.html +0 -0
- data/{test → spec}/fixtures/phpbb2-get-viewtopic-for-new-topic-good-response.html +11 -11
- data/{test → spec}/fixtures/phpbb2-get-viewtopic-for-new-topic-malformed-response.html +0 -0
- data/{test → spec}/fixtures/phpbb2-index.html +0 -0
- data/{test → spec}/fixtures/phpbb2-logged-in.html +0 -0
- data/{test → spec}/fixtures/phpbb2-login.html +0 -0
- data/{test → spec}/fixtures/phpbb2-not-logged-in.html +0 -0
- data/{test → spec}/fixtures/phpbb2-post-new_topic-good-response.html +0 -0
- data/{test → spec}/fixtures/phpbb2-post-reply-good-response.html +0 -0
- data/{test → spec}/fixtures/phpbb2-post-reply-throttled-response.html +0 -0
- data/{test → spec}/fixtures/phpbb2-too-many-posts.html +0 -0
- data/{test → spec}/fixtures/phpbb3-get-new-topic-form-good-response.html +0 -0
- data/{test → spec}/fixtures/phpbb3-get-reply-form-good-response.html +0 -0
- data/{test → spec}/fixtures/phpbb3-logged-in.html +0 -0
- data/{test → spec}/fixtures/phpbb3-login.html +0 -0
- data/{test → spec}/fixtures/phpbb3-not-logged-in.html +0 -0
- data/{test → spec}/fixtures/phpbb3-post-new_topic-good-response.html +0 -0
- data/{test → spec}/fixtures/phpbb3-post-reply-good-response.html +0 -0
- data/spec/fixtures/vcr_cassettes/phpbb2-should-be-overlimit-creating-topic.yml +1308 -0
- data/spec/fixtures/vcr_cassettes/phpbb2-should-create-topic.yml +923 -0
- data/spec/fixtures/vcr_cassettes/phpbb2-should-login.yml +360 -0
- data/spec/fixtures/vcr_cassettes/phpbb2-should-not-create-new-topic.yml +497 -0
- data/spec/fixtures/vcr_cassettes/phpbb2-should-not-login.yml +287 -0
- data/spec/fixtures/vcr_cassettes/phpbb2-should-not-post.yml +497 -0
- data/spec/fixtures/vcr_cassettes/phpbb2-should-overlimit-error-post.yml +1140 -0
- data/spec/fixtures/vcr_cassettes/phpbb2-should-post.yml +751 -0
- data/spec/fixtures/vcr_cassettes/phpbb3-should-be-overlimit-creating-topic.yml +995 -0
- data/spec/fixtures/vcr_cassettes/phpbb3-should-create-topic.yml +675 -0
- data/spec/fixtures/vcr_cassettes/phpbb3-should-login.yml +245 -0
- data/spec/fixtures/vcr_cassettes/phpbb3-should-not-create-new-topic.yml +350 -0
- data/spec/fixtures/vcr_cassettes/phpbb3-should-not-login.yml +253 -0
- data/spec/fixtures/vcr_cassettes/phpbb3-should-not-post.yml +350 -0
- data/spec/fixtures/vcr_cassettes/phpbb3-should-overlimit-error-post.yml +1046 -0
- data/spec/fixtures/vcr_cassettes/phpbb3-should-post.yml +605 -0
- data/{test → spec}/fixtures/wwf79-forum_posts.html +0 -0
- data/{test → spec}/fixtures/wwf79-general-new-topic-error.html +0 -0
- data/{test → spec}/fixtures/wwf79-general-posting-error.html +0 -0
- data/{test → spec}/fixtures/wwf79-good-post-forum_posts.html +1 -1
- data/{test → spec}/fixtures/wwf79-index.html +0 -0
- data/{test → spec}/fixtures/wwf79-logged-in.html +0 -0
- data/{test → spec}/fixtures/wwf79-login.html +0 -0
- data/{test → spec}/fixtures/wwf79-new-topic-forum_posts-response.html +0 -0
- data/{test → spec}/fixtures/wwf79-new-topic-post_message_form.html +0 -0
- data/{test → spec}/fixtures/wwf79-not-logged-in.html +0 -0
- data/{test → spec}/fixtures/wwf79-too-many-posts.html +0 -0
- data/{test → spec}/fixtures/wwf79-too-many-topics.html +0 -0
- data/{test → spec}/fixtures/wwf80-general-posting-error.html +0 -0
- data/{test → spec}/fixtures/wwf80-get-new_topic-form-good-response.html +0 -0
- data/{test → spec}/fixtures/wwf80-get-viewtopic-for-new-topic-good-response.html +0 -0
- data/{test → spec}/fixtures/wwf80-index.html +0 -0
- data/{test → spec}/fixtures/wwf80-logged-in.html +0 -0
- data/{test → spec}/fixtures/wwf80-login.html +0 -0
- data/{test → spec}/fixtures/wwf80-new_reply_form.html +0 -0
- data/{test → spec}/fixtures/wwf80-not-logged-in.html +0 -0
- data/{test → spec}/fixtures/wwf80-post-new_topic-good-response.html +1 -1
- data/{test → spec}/fixtures/wwf80-post-reply-good-response.html +0 -0
- data/{test → spec}/fixtures/wwf80-too-many-posts.html +0 -0
- data/spec/impostor_spec_helper.rb +162 -0
- data/spec/integration/phpbb2_spec.rb +111 -0
- data/spec/integration/phpbb3_spec.rb +109 -0
- data/spec/integration_spec_helper.rb +7 -0
- data/spec/phpbb2_spec.rb +346 -0
- data/spec/phpbb3_spec.rb +332 -0
- data/spec/post_spec.rb +134 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/test_impostor.rb +12 -0
- data/spec/topic_spec.rb +143 -0
- data/spec/wwf79_spec.rb +342 -0
- data/spec/wwf80_spec.rb +339 -0
- metadata +156 -87
- data/lib/www/impostor/phpbb2.rb +0 -258
- data/lib/www/impostor/phpbb3.rb +0 -236
- data/lib/www/impostor/wwf79.rb +0 -254
- data/lib/www/impostor/wwf80.rb +0 -264
- data/lib/www/impostor.rb +0 -269
- data/test/test_github.rb +0 -12
- data/test/test_www_impostor.rb +0 -165
- data/test/test_www_impostor_phpbb2.rb +0 -536
- data/test/test_www_impostor_phpbb3.rb +0 -483
- data/test/test_www_impostor_wwf79.rb +0 -535
- data/test/test_www_impostor_wwf80.rb +0 -535
- data/vendor/plugins/impostor/lib/autotest/discover.rb +0 -3
- data/vendor/plugins/impostor/lib/autotest/impostor.rb +0 -49
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
class Impostor
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# An application error
|
|
5
|
+
|
|
6
|
+
class ImpostorError < RuntimeError
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# The original exception
|
|
10
|
+
|
|
11
|
+
attr_accessor :original_exception
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Creates a new ImpostorError with +message+ and +original_exception+
|
|
15
|
+
|
|
16
|
+
def initialize(e = nil)
|
|
17
|
+
exception = e.nil? || e.is_a?(String) ? StandardError.new(e) : e
|
|
18
|
+
@original_exception = exception
|
|
19
|
+
message = "Impostor error: #{exception.message} (#{exception.class})"
|
|
20
|
+
super message
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# An error for impostor login failure
|
|
27
|
+
|
|
28
|
+
class LoginError < ImpostorError; end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# An error for impostor post failure
|
|
32
|
+
|
|
33
|
+
class PostError < ImpostorError; end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# An error for impostor when a topic failure
|
|
37
|
+
|
|
38
|
+
class TopicError < ImpostorError; end
|
|
39
|
+
|
|
40
|
+
##
|
|
41
|
+
# An error for impostor when the receiving forum rejects the post due to
|
|
42
|
+
# a throttling or spam error but which the user can re-attempt at a later
|
|
43
|
+
# time.
|
|
44
|
+
|
|
45
|
+
class ThrottledError < ImpostorError; end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# An error for misconfiguration
|
|
49
|
+
|
|
50
|
+
class ConfigError < ImpostorError; end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# An error for template methods that need to be implemented
|
|
54
|
+
|
|
55
|
+
class MissingTemplateMethodError < ImpostorError; end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# An exception for when a new topic has been moderated
|
|
59
|
+
|
|
60
|
+
class TopicModerated < ImpostorError; end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# An exception for when a new post has been moderated
|
|
64
|
+
|
|
65
|
+
class PostModerated < ImpostorError; end
|
|
66
|
+
|
|
67
|
+
end
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
##
|
|
2
|
+
# phpBB2 version of the Impostor
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
class Impostor
|
|
6
|
+
|
|
7
|
+
module Phpbb2
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Additional configuration parameters for a Phpbb2 compatible agent:
|
|
11
|
+
#
|
|
12
|
+
# :posting_page
|
|
13
|
+
#
|
|
14
|
+
# Typical configuration parameters
|
|
15
|
+
# { :type => :phpbb2,
|
|
16
|
+
# :app_root => 'http://example.com/forum/',
|
|
17
|
+
# :login_page => 'login.php',
|
|
18
|
+
# :posting_page => 'posting.php',
|
|
19
|
+
# :user_agent => 'Windows IE 7',
|
|
20
|
+
# :username => 'myuser',
|
|
21
|
+
# :password => 'mypasswd' }
|
|
22
|
+
|
|
23
|
+
module Auth
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Checks if the agent is already logged by stored cookie
|
|
27
|
+
|
|
28
|
+
def logged_in?(page)
|
|
29
|
+
mm = page.search( "//a" ).detect{ | a| a.inner_html =~ /Log out \[ #{self.config.username} \]/ } ||
|
|
30
|
+
page.search( "//a" ).detect{ |a| a['href'] =~ /\/login\.php\?mode=logout/ }
|
|
31
|
+
|
|
32
|
+
not mm.nil?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# returns the login form from the login page
|
|
37
|
+
|
|
38
|
+
def get_login_form(page)
|
|
39
|
+
form = page.forms.detect { |form| form.action =~ /login\.php/ }
|
|
40
|
+
raise Impostor::LoginError.new("unknown login page format") unless form
|
|
41
|
+
form
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Sets the user name and pass word on the loing form.
|
|
46
|
+
|
|
47
|
+
def set_username_and_password(form)
|
|
48
|
+
form['username'] = self.config.username
|
|
49
|
+
form['password'] = self.config.password
|
|
50
|
+
form['autologin'] = 'on'
|
|
51
|
+
form['login'] = 'Log in'
|
|
52
|
+
form
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
module Post
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# return a uri used to fetch the reply page based on the forum, topic, and
|
|
61
|
+
# message
|
|
62
|
+
|
|
63
|
+
def get_reply_uri(forum, topic)
|
|
64
|
+
uri = URI.join(self.config.app_root, self.config.config(:posting_page))
|
|
65
|
+
uri.query = "mode=reply&t=#{topic}"
|
|
66
|
+
uri
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# return the form used for posting a message from the reply page
|
|
71
|
+
|
|
72
|
+
def get_post_form(page)
|
|
73
|
+
form = page.forms.detect { |form| form.action =~ /#{Regexp.escape(self.config.config(:posting_page))}/ }
|
|
74
|
+
raise Impostor::PostError.new("unknown reply page format#{page_message(page, ', ')}") unless form
|
|
75
|
+
form
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# set the message to reply with on the reply form
|
|
80
|
+
|
|
81
|
+
def set_message(form, message)
|
|
82
|
+
form.message = message
|
|
83
|
+
form["post"] = "Submit"
|
|
84
|
+
form
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# get post id from the result of posting the message form
|
|
89
|
+
|
|
90
|
+
def get_post_from_result(page)
|
|
91
|
+
message = page_message(page)
|
|
92
|
+
if message =~ /Your message has been entered successfully./
|
|
93
|
+
kv = page.links.collect{ |l| l.uri }.compact.
|
|
94
|
+
collect{ |l| l.query }.compact.
|
|
95
|
+
collect{ |q| q.split('&')}.flatten.
|
|
96
|
+
detect{|kv| kv =~ /^p=/ }
|
|
97
|
+
postid = URI.unescape(kv).split('#').first.split('=').last.to_i
|
|
98
|
+
raise Impostor::PostError.new("Message did not post.") if postid.zero?
|
|
99
|
+
return postid
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
too_many = message =~ /You cannot make another post so soon after your last/
|
|
103
|
+
|
|
104
|
+
if too_many
|
|
105
|
+
raise Impostor::ThrottledError.new("too many posts in too short amount of time")
|
|
106
|
+
else
|
|
107
|
+
raise Impostor::PostError.new("message did not post")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def page_message(page, prepend = '')
|
|
112
|
+
message = page.search("//span[@class='gen']").last || ''
|
|
113
|
+
message = message.text if message.respond_to?(:text)
|
|
114
|
+
prepend = '' if message.empty?
|
|
115
|
+
"#{prepend}#{message}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
module Topic
|
|
121
|
+
|
|
122
|
+
##
|
|
123
|
+
# return a uri used to fetch the new topic page based on the forum, subject,
|
|
124
|
+
# and message
|
|
125
|
+
|
|
126
|
+
def get_new_topic_uri(forum, subject, message)
|
|
127
|
+
uri = URI.join(self.config.app_root, self.config.config(:posting_page))
|
|
128
|
+
uri.query = "mode=newtopic&f=#{forum}"
|
|
129
|
+
uri
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
##
|
|
133
|
+
# Get the the new topic form on the page
|
|
134
|
+
|
|
135
|
+
def get_new_topic_form(page)
|
|
136
|
+
form = page.form('post')
|
|
137
|
+
raise Impostor::TopicError.new("unknown new topic page format") unless form
|
|
138
|
+
form
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
##
|
|
142
|
+
# Set the subject and message on the new topic form
|
|
143
|
+
|
|
144
|
+
def set_subject_and_message(form, subject, message)
|
|
145
|
+
form.subject = subject
|
|
146
|
+
form.message = message
|
|
147
|
+
form["post"] = "Submit"
|
|
148
|
+
form
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
##
|
|
152
|
+
# Validate the result of posting the new topic
|
|
153
|
+
|
|
154
|
+
def validate_new_topic_result(page)
|
|
155
|
+
message = page_message(page)
|
|
156
|
+
if message !~ /Your message has been entered successfully./
|
|
157
|
+
if message =~ /You cannot make another post so soon after your last/
|
|
158
|
+
raise Impostor::ThrottledError.new("too many new topics in too short amount of time")
|
|
159
|
+
else
|
|
160
|
+
raise Impostor::TopicError.new("Topic did not post.")
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
begin
|
|
165
|
+
# <td align="center"><span class="gen">Your message has been entered successfully.<br /><br />Click <a href="viewtopic.php?p=9#9">Here</a> to view your message<br /><br />Click <a href="viewforum.php?f=1">Here</a> to return to the forum</span></td>
|
|
166
|
+
|
|
167
|
+
# TODO the link has the postid specifically for the post, not all
|
|
168
|
+
# forums make it easy to deduce the post id
|
|
169
|
+
link = page.links.detect{ |l| l.href =~ /viewtopic\.php/ }
|
|
170
|
+
link.click
|
|
171
|
+
rescue StandardError => err
|
|
172
|
+
raise Impostor::TopicError.new(err)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
##
|
|
177
|
+
# Get the new topic identifier from the result page
|
|
178
|
+
|
|
179
|
+
def get_topic_from_result(page)
|
|
180
|
+
begin
|
|
181
|
+
link = page.links.detect{ |l| l.href =~ /viewtopic\.php/ }
|
|
182
|
+
kv = link.uri.query.split('&').detect{|kv| kv =~ /^t=/ }
|
|
183
|
+
topicid = URI.unescape(kv).split('#').first.split('=').last.to_i
|
|
184
|
+
rescue StandardError => err
|
|
185
|
+
raise Impostor::TopicError.new(err)
|
|
186
|
+
end
|
|
187
|
+
raise Impostor::TopicError.new("Failed to create topic.") if topicid.zero?
|
|
188
|
+
|
|
189
|
+
topicid
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def page_message(page, prepend = '')
|
|
193
|
+
message = page.search("//span[@class='gen']").last || ''
|
|
194
|
+
message = message.text if message.respond_to?(:text)
|
|
195
|
+
prepend = '' if message.empty?
|
|
196
|
+
"#{prepend}#{message}"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
##
|
|
2
|
+
# phpBB3 version of the Impostor
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
class Impostor
|
|
6
|
+
|
|
7
|
+
module Phpbb3
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Additional configuration parameters for a Phpbb3 compatible agent:
|
|
11
|
+
#
|
|
12
|
+
# :posting_page
|
|
13
|
+
#
|
|
14
|
+
# Typical configuration parameters
|
|
15
|
+
# { :type => :phpbb3,
|
|
16
|
+
# :app_root => 'http://example.com/forum/',
|
|
17
|
+
# :login_page => 'ucp.php?mode=login',
|
|
18
|
+
# :posting_page => 'posting.php',
|
|
19
|
+
# :user_agent => 'Windows IE 7',
|
|
20
|
+
# :username => 'myuser',
|
|
21
|
+
# :password => 'mypasswd' }
|
|
22
|
+
|
|
23
|
+
module Auth
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Checks if the agent is already logged by stored cookie
|
|
27
|
+
|
|
28
|
+
def logged_in?(page)
|
|
29
|
+
mm = page.search( "//a" ).detect{ | a| a.inner_html =~ /Logout \[ #{self.config.username} \]/ } ||
|
|
30
|
+
page.search( "//a" ).detect{ |a| a['href'] =~ /\.\/ucp\.php\?mode=logout/ }
|
|
31
|
+
|
|
32
|
+
not mm.nil?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# returns the login form from the login page
|
|
37
|
+
|
|
38
|
+
def get_login_form(page)
|
|
39
|
+
form = page.forms.detect { |form| form.action =~ /\/ucp\.php\?mode=login/ }
|
|
40
|
+
raise Impostor::LoginError.new("unknown login page format") unless form
|
|
41
|
+
form
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Sets the user name and pass word on the loing form.
|
|
46
|
+
|
|
47
|
+
def set_username_and_password(form)
|
|
48
|
+
form['username'] = self.config.username
|
|
49
|
+
form['password'] = self.config.password
|
|
50
|
+
form['login'] = 'Login'
|
|
51
|
+
form['autologin'] = 'on'
|
|
52
|
+
form
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
module Post
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# return a uri used to fetch the reply page based on the forum, topic, and
|
|
61
|
+
# message
|
|
62
|
+
|
|
63
|
+
def get_reply_uri(forum, topic)
|
|
64
|
+
uri = URI.join(self.config.app_root, self.config.config(:posting_page))
|
|
65
|
+
uri.query = "mode=reply&f=#{forum}&t=#{topic}"
|
|
66
|
+
uri
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
##
|
|
70
|
+
# return the form used for posting a message from the reply page
|
|
71
|
+
|
|
72
|
+
def get_post_form(page)
|
|
73
|
+
form = page.forms.detect { |form| form.action =~ /#{Regexp.escape(self.config.config(:posting_page))}/ }
|
|
74
|
+
raise Impostor::PostError.new("unknown reply page format") unless form
|
|
75
|
+
form
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# set the message to reply with on the reply form
|
|
80
|
+
|
|
81
|
+
def set_message(form, message)
|
|
82
|
+
form.message = message
|
|
83
|
+
form["post"] = "Submit"
|
|
84
|
+
form
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# get post id from the result of posting the message form
|
|
89
|
+
|
|
90
|
+
def get_post_from_result(page)
|
|
91
|
+
error_message = page_error_message(page)
|
|
92
|
+
if error_message =~ /You cannot make another post so soon after your last/
|
|
93
|
+
raise Impostor::ThrottledError.new("too many posts in too short amount of time, #{error_message}")
|
|
94
|
+
elsif !error_message.empty?
|
|
95
|
+
raise Impostor::PostError.new(error_message)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
begin
|
|
99
|
+
kv = page.links.collect{ |l| l.uri }.compact.
|
|
100
|
+
collect{ |l| l.query }.compact.
|
|
101
|
+
collect{ |q| q.split('&')}.flatten.
|
|
102
|
+
detect { |p| p =~ /^p=/ }
|
|
103
|
+
raise StandardError.new("Message did not post.") if kv.nil?
|
|
104
|
+
postid = URI.unescape(kv).split('#').first.split('=').last.to_i
|
|
105
|
+
raise StandardError.new("Message did not post.") if postid.zero?
|
|
106
|
+
postid
|
|
107
|
+
rescue StandardError => err
|
|
108
|
+
raise Impostor::PostError.new(err)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
##
|
|
113
|
+
# Extract the error from a page
|
|
114
|
+
|
|
115
|
+
def page_error_message(page, prepend='')
|
|
116
|
+
message = page.search(".//p[@class='error']").last || ''
|
|
117
|
+
message = message.text if message.respond_to?(:text)
|
|
118
|
+
prepend = '' if message.empty?
|
|
119
|
+
"#{prepend}#{message}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
module Topic
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
# return a uri used to fetch the new topic page based on the forum, subject,
|
|
128
|
+
# and message
|
|
129
|
+
|
|
130
|
+
def get_new_topic_uri(forum, subject, message)
|
|
131
|
+
uri = URI.join(self.config.app_root, self.config.config(:posting_page))
|
|
132
|
+
uri.query = "mode=post&f=#{forum}"
|
|
133
|
+
uri
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
##
|
|
137
|
+
# Get the the new topic form on the page
|
|
138
|
+
|
|
139
|
+
def get_new_topic_form(page)
|
|
140
|
+
form = page.forms.detect { |form| form.action =~ /#{Regexp.escape(self.config.config(:posting_page))}/ }
|
|
141
|
+
raise Impostor::TopicError.new("unknown new topic page format") unless form
|
|
142
|
+
form
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
##
|
|
146
|
+
# Set the subject and message on the new topic form
|
|
147
|
+
|
|
148
|
+
def set_subject_and_message(form, subject, message)
|
|
149
|
+
form.subject = subject
|
|
150
|
+
form.message = message
|
|
151
|
+
form["post"] = "Submit"
|
|
152
|
+
form
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
##
|
|
156
|
+
# Validate the result of posting the new topic
|
|
157
|
+
|
|
158
|
+
def validate_new_topic_result(page)
|
|
159
|
+
error_message = page_error_message(page)
|
|
160
|
+
if error_message =~ /You cannot make another post so soon after your last/
|
|
161
|
+
raise Impostor::ThrottledError.new("too many posts in too short amount of time, #{error_message}")
|
|
162
|
+
elsif !error_message.empty?
|
|
163
|
+
raise Impostor::PostError.new(error_message)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
page
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
##
|
|
170
|
+
# Get the new topic identifier from the result page
|
|
171
|
+
|
|
172
|
+
def get_topic_from_result(page)
|
|
173
|
+
moderated = page.body =~ /it will need to be approved by a moderator/i
|
|
174
|
+
raise Impostor::TopicModerated.new("new topic has been moderated") if moderated
|
|
175
|
+
|
|
176
|
+
link = page.links.detect{ |l| l.text =~ /View your submitted message/i }
|
|
177
|
+
link ||= page.links.detect{ |l| l.href =~ /viewtopic\.php/ }
|
|
178
|
+
raise Impostor::TopicError.new("new topic did not post") unless link
|
|
179
|
+
topic = link.uri.query.split('&').detect{|a| a =~ /^t=/}
|
|
180
|
+
raise Impostor::TopicError.new("new topic did not post") unless topic
|
|
181
|
+
topic = topic.split('=').last.to_i
|
|
182
|
+
raise Impostor::TopicError.new("new topic did not post") if topic.zero?
|
|
183
|
+
topic
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
##
|
|
187
|
+
# Extract the error from a page
|
|
188
|
+
|
|
189
|
+
def page_error_message(page, prepend='')
|
|
190
|
+
message = page.search(".//p[@class='error']").last || ''
|
|
191
|
+
message = message.text if message.respond_to?(:text)
|
|
192
|
+
prepend = '' if message.empty?
|
|
193
|
+
"#{prepend}#{message}"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
class Impostor::Post
|
|
2
|
+
|
|
3
|
+
attr_reader :config
|
|
4
|
+
attr_reader :auth
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
# Post is initialized with the auth of the impostor
|
|
8
|
+
|
|
9
|
+
def initialize(config, auth)
|
|
10
|
+
@config = config
|
|
11
|
+
@auth = auth
|
|
12
|
+
self.extend eval("Impostor::#{config.type.to_s.capitalize}::Post")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# post a message to the forum and topic thread
|
|
17
|
+
# post is comprised of the following template methods to allow
|
|
18
|
+
# implementation for specific forum applications
|
|
19
|
+
#
|
|
20
|
+
# * validate_post_input(forum, topic, message)
|
|
21
|
+
# * get_reply_uri(forum, topic)
|
|
22
|
+
# * get_reply_page(uri)
|
|
23
|
+
# * get_post_form(page)
|
|
24
|
+
# * set_message(form, message)
|
|
25
|
+
# * post_message(form)
|
|
26
|
+
# * get_post_from_result(page)
|
|
27
|
+
#
|
|
28
|
+
# A hash of results is returned, having keys to the :forum, :topic, new :post
|
|
29
|
+
# id, :message, and :result
|
|
30
|
+
|
|
31
|
+
def post(forum, topic, message)
|
|
32
|
+
self.validate_post_input(forum, topic, message)
|
|
33
|
+
self.auth.login_with_raises
|
|
34
|
+
uri = self.get_reply_uri(forum, topic)
|
|
35
|
+
page = get_reply_page(uri)
|
|
36
|
+
form = get_post_form(page)
|
|
37
|
+
set_message(form, message)
|
|
38
|
+
page = post_message(form)
|
|
39
|
+
post = get_post_from_result(page)
|
|
40
|
+
|
|
41
|
+
{ :forum => forum,
|
|
42
|
+
:topic => topic,
|
|
43
|
+
:post => post,
|
|
44
|
+
:message => message,
|
|
45
|
+
:result => true }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# validate the inputs forum, topic, and message
|
|
50
|
+
|
|
51
|
+
def validate_post_input(forum, topic, message)
|
|
52
|
+
raise Impostor::PostError.new("forum not set") unless forum
|
|
53
|
+
raise Impostor::PostError.new("topic not set") unless topic
|
|
54
|
+
raise Impostor::PostError.new("message not set") unless message
|
|
55
|
+
true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
##
|
|
59
|
+
# return a uri used to fetch the reply page based on the forum, topic, and
|
|
60
|
+
# message
|
|
61
|
+
|
|
62
|
+
def get_reply_uri(forum, topic)
|
|
63
|
+
raise Impostor::MissingTemplateMethodError.new("get_reply_uri must be implemented")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# return the reply page that is fetched with the reply uri
|
|
68
|
+
|
|
69
|
+
def get_reply_page(uri)
|
|
70
|
+
begin
|
|
71
|
+
page = self.config.agent.get(uri)
|
|
72
|
+
rescue StandardError => err
|
|
73
|
+
raise Impostor::PostError.new(err)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# return the form used for posting a message from the reply page
|
|
79
|
+
|
|
80
|
+
def get_post_form(page)
|
|
81
|
+
raise Impostor::MissingTemplateMethodError.new("get_post_form must be implemented")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
##
|
|
85
|
+
# set the message to reply with on the reply form
|
|
86
|
+
|
|
87
|
+
def set_message(form, message)
|
|
88
|
+
form.message = message
|
|
89
|
+
form
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
##
|
|
93
|
+
# post the message form
|
|
94
|
+
|
|
95
|
+
def post_message(form)
|
|
96
|
+
begin
|
|
97
|
+
config.sleep_before_post
|
|
98
|
+
form.submit
|
|
99
|
+
rescue StandardError => err
|
|
100
|
+
raise Impostor::PostError.new(err)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# get post id from the result of posting the message form
|
|
106
|
+
|
|
107
|
+
def get_post_from_result(page)
|
|
108
|
+
raise Impostor::MissingTemplateMethodError.new("get_post_from_result must be implemented")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
class Impostor::Topic
|
|
2
|
+
|
|
3
|
+
attr_reader :config
|
|
4
|
+
attr_reader :auth
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
# Topic is initialized with the auth of the impostor
|
|
8
|
+
|
|
9
|
+
def initialize(config, auth)
|
|
10
|
+
@config = config
|
|
11
|
+
@auth = auth
|
|
12
|
+
self.extend eval("Impostor::#{config.type.to_s.capitalize}::Topic")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# create a new topic in the forum with the subject title and initial message
|
|
17
|
+
#
|
|
18
|
+
# * validate_new_topic_input(forum, subject, message)
|
|
19
|
+
# * get_new_topic_uri(forum, subject, message)
|
|
20
|
+
# * get_new_topic_page(uri)
|
|
21
|
+
# * get_new_topic_form(page)
|
|
22
|
+
# * set_subject_and_message(form, subject, message)
|
|
23
|
+
# * post_new_topic(form)
|
|
24
|
+
# * validate_new_topic_result(page)
|
|
25
|
+
# * get_topic_from_result(page)
|
|
26
|
+
|
|
27
|
+
def new_topic(forum, subject, message)
|
|
28
|
+
self.validate_topic_input(forum, subject, message)
|
|
29
|
+
self.auth.login_with_raises
|
|
30
|
+
uri = self.get_new_topic_uri(forum, subject, message)
|
|
31
|
+
page = self.get_new_topic_page(uri)
|
|
32
|
+
form = self.get_new_topic_form(page)
|
|
33
|
+
self.set_subject_and_message(form, subject, message)
|
|
34
|
+
page = self.post_new_topic(form)
|
|
35
|
+
page = self.validate_new_topic_result(page)
|
|
36
|
+
topic = self.get_topic_from_result(page)
|
|
37
|
+
|
|
38
|
+
self.config.add_subject(forum, topic, subject)
|
|
39
|
+
|
|
40
|
+
{ :forum => forum,
|
|
41
|
+
:topic => topic,
|
|
42
|
+
:subject => subject,
|
|
43
|
+
:message => message,
|
|
44
|
+
:result => true }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# validate the inputs forum, topic, and message
|
|
49
|
+
|
|
50
|
+
def validate_topic_input(forum, subject, message)
|
|
51
|
+
raise Impostor::TopicError.new("forum not set") unless forum
|
|
52
|
+
raise Impostor::TopicError.new("subject not set") unless subject
|
|
53
|
+
raise Impostor::TopicError.new("message not set") unless message
|
|
54
|
+
true
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# return a uri used to fetch the new topic page based on the forum, subject,
|
|
59
|
+
# and message
|
|
60
|
+
|
|
61
|
+
def get_new_topic_uri(forum, subject, message)
|
|
62
|
+
raise Impostor::MissingTemplateMethodError.new("get_new_topic_uri must be implemented")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# Get the page that has the form for new topics referenced by the uri
|
|
67
|
+
|
|
68
|
+
def get_new_topic_page(uri)
|
|
69
|
+
begin
|
|
70
|
+
self.config.agent.get(uri)
|
|
71
|
+
rescue StandardError => err
|
|
72
|
+
raise Impostor::TopicError.new(err)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
##
|
|
77
|
+
# Get the the new topic form on the page
|
|
78
|
+
|
|
79
|
+
def get_new_topic_form(page)
|
|
80
|
+
raise Impostor::MissingTemplateMethodError.new("get_new_topic_form must be implemented")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
##
|
|
84
|
+
# Set the subject and message on the new topic form
|
|
85
|
+
|
|
86
|
+
def set_subject_and_message(form, subject, message)
|
|
87
|
+
raise Impostor::MissingTemplateMethodError.new("set_subject_and_message must be implemented")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# Post the new topic that is contained on the form
|
|
92
|
+
|
|
93
|
+
def post_new_topic(form)
|
|
94
|
+
begin
|
|
95
|
+
config.sleep_before_post
|
|
96
|
+
form.submit
|
|
97
|
+
rescue StandardError => err
|
|
98
|
+
raise Impostor::TopicError.new(err)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
##
|
|
103
|
+
# Validate the result of posting the new topic
|
|
104
|
+
|
|
105
|
+
def validate_new_topic_result(page)
|
|
106
|
+
raise Impostor::MissingTemplateMethodError.new("validate_new_topic_result must be implemented")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
# Get the new topic identifier from the result page
|
|
111
|
+
|
|
112
|
+
def get_topic_from_result(page)
|
|
113
|
+
raise Impostor::MissingTemplateMethodError.new("get_topic_from_result must be implemented")
|
|
114
|
+
end
|
|
115
|
+
end
|