impostor 0.2.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|