ruby-hackernews 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +4 -0
- data/Rakefile +1 -1
- data/lib/ruby-hackernews/domain/comment/comment.rb +44 -41
- data/lib/ruby-hackernews/domain/entry/comments_info.rb +16 -13
- data/lib/ruby-hackernews/domain/entry/entry.rb +64 -60
- data/lib/ruby-hackernews/domain/entry/link_info.rb +12 -9
- data/lib/ruby-hackernews/domain/entry/time_info.rb +18 -15
- data/lib/ruby-hackernews/domain/entry/user_info.rb +11 -8
- data/lib/ruby-hackernews/domain/entry/voting_info.rb +12 -9
- data/lib/ruby-hackernews/domain/user.rb +31 -27
- data/lib/ruby-hackernews/services/comment_service.rb +54 -51
- data/lib/ruby-hackernews/services/configuration_service.rb +25 -22
- data/lib/ruby-hackernews/services/entry_service.rb +53 -51
- data/lib/ruby-hackernews/services/login_service.rb +23 -20
- data/lib/ruby-hackernews/services/mechanize_context.rb +23 -19
- data/lib/ruby-hackernews/services/not_authenticated_error.rb +8 -5
- data/lib/ruby-hackernews/services/parsers/comments_info_parser.rb +16 -11
- data/lib/ruby-hackernews/services/parsers/entry_page_parser.rb +20 -17
- data/lib/ruby-hackernews/services/parsers/entry_parser.rb +25 -22
- data/lib/ruby-hackernews/services/parsers/link_info_parser.rb +14 -12
- data/lib/ruby-hackernews/services/parsers/time_info_parser.rb +13 -11
- data/lib/ruby-hackernews/services/parsers/user_info_parser.rb +15 -11
- data/lib/ruby-hackernews/services/parsers/voting_info_parser.rb +16 -12
- data/lib/ruby-hackernews/services/signup_service.rb +15 -12
- data/lib/ruby-hackernews/services/user_info_service.rb +21 -18
- data/lib/ruby-hackernews/services/voting_service.rb +10 -7
- metadata +24 -24
@@ -1,62 +1,65 @@
|
|
1
|
+
module RubyHackernews
|
1
2
|
|
2
|
-
class CommentService
|
3
|
-
|
3
|
+
class CommentService
|
4
|
+
include MechanizeContext
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
6
|
+
def get_comments(page_url)
|
7
|
+
comments = []
|
8
|
+
last = comments
|
9
|
+
current_level = -1
|
10
|
+
page = agent.get(page_url)
|
11
|
+
page.search("//table")[3].search("table/tr").select do |tr|
|
12
|
+
tr.search("span.comment").inner_html != "[deleted]"
|
13
|
+
end.each do |tr|
|
14
|
+
comment = parse_comment(tr)
|
15
|
+
level = tr.search("img[@src='http://ycombinator.com/images/s.gif']").first['width'].to_i / 40
|
16
|
+
difference = current_level - level
|
17
|
+
target = last
|
18
|
+
(difference + 1).times do
|
19
|
+
target = target.parent || comments
|
20
|
+
end
|
21
|
+
target << comment
|
22
|
+
last = comment
|
23
|
+
current_level = level
|
19
24
|
end
|
20
|
-
|
21
|
-
last = comment
|
22
|
-
current_level = level
|
25
|
+
return comments
|
23
26
|
end
|
24
|
-
return comments
|
25
|
-
end
|
26
27
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
def get_new_comments(pages = 1, url = ConfigurationService.comments_url)
|
29
|
+
parser = EntryPageParser.new(agent.get(url))
|
30
|
+
comments = []
|
31
|
+
pages.times do
|
32
|
+
lines = parser.get_lines
|
33
|
+
lines.each do |line|
|
34
|
+
comments << parse_comment(line)
|
35
|
+
end
|
36
|
+
next_url = parser.get_next_url || break
|
37
|
+
parser = EntryPageParser.new(agent.get(next_url))
|
34
38
|
end
|
35
|
-
|
36
|
-
parser = EntryPageParser.new(agent.get(next_url))
|
39
|
+
return comments
|
37
40
|
end
|
38
|
-
return comments
|
39
|
-
end
|
40
41
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
def parse_comment(element)
|
43
|
+
text = ""
|
44
|
+
element.search("span.comment").first.children.each do |ch|
|
45
|
+
text = ch.inner_html.gsub(/<.{1,2}>/,"")
|
46
|
+
end
|
47
|
+
header = element.search("span.comhead").first
|
48
|
+
voting = VotingInfoParser.new(element.search("td/center/a"), header).parse
|
49
|
+
user_info = UserInfoParser.new(header).parse
|
50
|
+
reply_link = element.search("td[@class='default']/p//u//a").first
|
51
|
+
reply_url = reply_link['href'] if reply_link
|
52
|
+
return Comment.new(text, voting, user_info, reply_url)
|
45
53
|
end
|
46
|
-
header = element.search("span.comhead").first
|
47
|
-
voting = VotingInfoParser.new(element.search("td/center/a"), header).parse
|
48
|
-
user_info = UserInfoParser.new(header).parse
|
49
|
-
reply_link = element.search("td[@class='default']/p//u//a").first
|
50
|
-
reply_url = reply_link['href'] if reply_link
|
51
|
-
return Comment.new(text, voting, user_info, reply_url)
|
52
|
-
end
|
53
54
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
55
|
+
def write_comment(page_url, comment)
|
56
|
+
require_authentication
|
57
|
+
form = agent.get(page_url).forms.first
|
58
|
+
form.text = comment
|
59
|
+
form.submit
|
60
|
+
return true
|
61
|
+
end
|
61
62
|
|
62
|
-
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -1,32 +1,35 @@
|
|
1
|
+
module RubyHackernews
|
1
2
|
|
2
|
-
class ConfigurationService
|
3
|
+
class ConfigurationService
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
def self.base_url=(url)
|
6
|
+
@base_url = url
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
def self.base_url
|
10
|
+
return @base_url || "http://news.ycombinator.com/"
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
def self.new_url
|
14
|
+
return File.join(self.base_url, "newest")
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
17
|
+
def self.ask_url
|
18
|
+
return File.join(self.base_url, "ask")
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
def self.jobs_url
|
22
|
+
return File.join(self.base_url, "jobs")
|
23
|
+
end
|
23
24
|
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
def self.comments_url
|
26
|
+
return File.join(self.base_url, "newcomments")
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.submit_url
|
30
|
+
return File.join(self.base_url, "submit")
|
31
|
+
end
|
27
32
|
|
28
|
-
def self.submit_url
|
29
|
-
return File.join(self.base_url, "submit")
|
30
33
|
end
|
31
34
|
|
32
|
-
end
|
35
|
+
end
|
@@ -1,65 +1,67 @@
|
|
1
|
+
module RubyHackernews
|
1
2
|
|
2
|
-
class EntryService
|
3
|
-
|
3
|
+
class EntryService
|
4
|
+
include MechanizeContext
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
6
|
+
def get_entries(pages = 1, url = ConfigurationService.base_url)
|
7
|
+
parser = EntryPageParser.new(agent.get(url))
|
8
|
+
entry_infos = []
|
9
|
+
pages.times do
|
10
|
+
lines = parser.get_lines
|
11
|
+
(lines.length / 2).times do
|
12
|
+
entry_infos << EntryParser.new(lines.shift, lines.shift).parse
|
13
|
+
end
|
14
|
+
next_url = parser.get_next_url || break
|
15
|
+
parser = EntryPageParser.new(agent.get(next_url))
|
12
16
|
end
|
13
|
-
|
14
|
-
parser = EntryPageParser.new(agent.get(next_url))
|
17
|
+
return entry_infos
|
15
18
|
end
|
16
|
-
return entry_infos
|
17
|
-
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
def find_by_id(id)
|
21
|
+
page = agent.get(ConfigurationService.base_url + "item?id=#{id}")
|
22
|
+
lines = page.search("table")[2].search("tr")
|
23
|
+
return EntryParser.new(lines[0], lines[1]).parse
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
def get_new_entries(pages = 1)
|
27
|
+
return get_entries(pages, ConfigurationService.new_url)
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
30
|
+
def get_questions(pages = 1)
|
31
|
+
return get_entries(pages, ConfigurationService.ask_url)
|
32
|
+
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
34
|
+
def get_jobs(pages = 1)
|
35
|
+
return get_entries(pages, ConfigurationService.jobs_url)
|
36
|
+
end
|
37
|
+
|
38
|
+
def submit(title, url)
|
39
|
+
require_authentication
|
40
|
+
form = agent.get(ConfigurationService.submit_url).forms.first
|
41
|
+
submit_link(form, title, url)
|
42
|
+
return true
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
def ask(title, text)
|
46
|
+
require_authentication
|
47
|
+
form = agent.get(ConfigurationService.submit_url).forms.first
|
48
|
+
submit_question(form, title, text)
|
49
|
+
return true
|
50
|
+
end
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
52
|
+
private
|
53
|
+
def submit_link(form, title, url)
|
54
|
+
form.t = title
|
55
|
+
form.u = url
|
56
|
+
form.submit
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
59
|
+
def submit_question(form, title, text)
|
60
|
+
form.t = title
|
61
|
+
form.x = text
|
62
|
+
form.submit
|
63
|
+
end
|
64
|
+
|
62
65
|
end
|
63
66
|
|
64
|
-
|
65
67
|
end
|
@@ -1,25 +1,28 @@
|
|
1
|
+
module RubyHackernews
|
1
2
|
|
2
|
-
class LoginService
|
3
|
-
|
3
|
+
class LoginService
|
4
|
+
include MechanizeContext
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
6
|
+
def login(username, password)
|
7
|
+
page = agent.get(ConfigurationService.base_url)
|
8
|
+
login_url = page.search(".pagetop/a").last['href'].sub("/","")
|
9
|
+
login_page = agent.get(ConfigurationService.base_url + login_url)
|
10
|
+
form = login_page.forms.first
|
11
|
+
form.u = username
|
12
|
+
form.p = password
|
13
|
+
page = form.submit
|
14
|
+
return page.title != nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def logout
|
18
|
+
require_authentication
|
19
|
+
page = agent.get(ConfigurationService.base_url)
|
20
|
+
login_url = page.search(".pagetop/a").last['href'].sub("/","")
|
21
|
+
logout_page = agent.get(ConfigurationService.base_url + login_url)
|
22
|
+
agent.cookie_jar.jar.clear
|
23
|
+
return logout_page.search(".pagetop/a").last.inner_html == "login"
|
24
|
+
end
|
15
25
|
|
16
|
-
def logout
|
17
|
-
require_authentication
|
18
|
-
page = agent.get(ConfigurationService.base_url)
|
19
|
-
login_url = page.search(".pagetop/a").last['href'].sub("/","")
|
20
|
-
logout_page = agent.get(ConfigurationService.base_url + login_url)
|
21
|
-
agent.cookie_jar.jar.clear
|
22
|
-
return logout_page.search(".pagetop/a").last.inner_html == "login"
|
23
26
|
end
|
24
27
|
|
25
|
-
end
|
28
|
+
end
|
@@ -1,27 +1,31 @@
|
|
1
|
-
module
|
1
|
+
module RubyHackernews
|
2
2
|
|
3
|
-
|
3
|
+
module MechanizeContext
|
4
4
|
|
5
|
-
|
6
|
-
@@default = key
|
7
|
-
end
|
5
|
+
@@contexts = {}
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
return @@contexts[@@default]
|
13
|
-
end
|
7
|
+
def self.agent=(key)
|
8
|
+
@@default = key
|
9
|
+
end
|
14
10
|
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
def agent
|
12
|
+
@@default ||= :default
|
13
|
+
@@contexts[@@default] = Mechanize.new unless @@contexts[@@default]
|
14
|
+
return @@contexts[@@default]
|
15
|
+
end
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
def [](key)
|
18
|
+
return @@contexts[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def require_authentication
|
22
|
+
raise NotAuthenticatedError unless authenticated?
|
23
|
+
end
|
24
|
+
|
25
|
+
def authenticated?(key = :default)
|
26
|
+
return @@contexts[key] && @@contexts[key].cookie_jar.jar.any?
|
27
|
+
end
|
22
28
|
|
23
|
-
def authenticated?(key = :default)
|
24
|
-
return @@contexts[key] && @@contexts[key].cookie_jar.jar.any?
|
25
29
|
end
|
26
30
|
|
27
|
-
end
|
31
|
+
end
|
@@ -1,8 +1,11 @@
|
|
1
|
+
module RubyHackernews
|
1
2
|
|
2
|
-
class NotAuthenticatedError < StandardError
|
3
|
+
class NotAuthenticatedError < StandardError
|
3
4
|
|
4
|
-
|
5
|
-
|
5
|
+
def message
|
6
|
+
return "You need to authenticate before making this operation"
|
7
|
+
end
|
8
|
+
|
6
9
|
end
|
7
|
-
|
8
|
-
end
|
10
|
+
|
11
|
+
end
|
@@ -1,16 +1,21 @@
|
|
1
|
-
|
1
|
+
module RubyHackernews
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class CommentsInfoParser
|
4
|
+
|
5
|
+
def initialize(comments_element)
|
6
|
+
@element = comments_element.search("a")[1]
|
7
|
+
end
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
9
|
+
def parse
|
10
|
+
comments_info = nil
|
11
|
+
if @element && @element['href'] =~ /id/
|
12
|
+
comments = @element.inner_html.split[0].to_i
|
13
|
+
comments_page = @element['href']
|
14
|
+
comments_info = CommentsInfo.new(comments, comments_page)
|
15
|
+
end
|
16
|
+
return comments_info
|
13
17
|
end
|
14
|
-
|
18
|
+
|
15
19
|
end
|
20
|
+
|
16
21
|
end
|
@@ -1,24 +1,27 @@
|
|
1
|
+
module RubyHackernews
|
1
2
|
|
2
|
-
class EntryPageParser
|
3
|
+
class EntryPageParser
|
3
4
|
|
4
|
-
|
5
|
-
|
6
|
-
|
5
|
+
def initialize(page)
|
6
|
+
@page = page
|
7
|
+
end
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
def get_lines
|
10
|
+
lines = @page.search("//table")[2].search("tr").select do |tr|
|
11
|
+
tr['style'] !~ /height/ &&
|
12
|
+
tr.children.first.attributes.count != 0
|
13
|
+
end
|
14
|
+
more_link = lines.last.search("a").first
|
15
|
+
lines.pop if more_link && more_link.inner_html == "More"
|
16
|
+
return lines
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_next_url
|
20
|
+
more_link = @page.search("//table")[2].search("tr/td/a").select { |node| node.inner_html == "More"}.first
|
21
|
+
return more_link['href'] if more_link
|
22
|
+
return nil
|
12
23
|
end
|
13
|
-
more_link = lines.last.search("a").first
|
14
|
-
lines.pop if more_link && more_link.inner_html == "More"
|
15
|
-
return lines
|
16
|
-
end
|
17
24
|
|
18
|
-
def get_next_url
|
19
|
-
more_link = @page.search("//table")[2].search("tr/td/a").select { |node| node.inner_html == "More"}.first
|
20
|
-
return more_link['href'] if more_link
|
21
|
-
return nil
|
22
25
|
end
|
23
26
|
|
24
|
-
end
|
27
|
+
end
|