ruby-hackernews 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +114 -0
- data/Rakefile +34 -0
- data/lib/HNAPI/domain/comment/comment.rb +55 -0
- data/lib/HNAPI/domain/entry/comments_info.rb +12 -0
- data/lib/HNAPI/domain/entry/entry.rb +57 -0
- data/lib/HNAPI/domain/entry/link_info.rb +14 -0
- data/lib/HNAPI/domain/entry/time_info.rb +20 -0
- data/lib/HNAPI/domain/entry/user_info.rb +12 -0
- data/lib/HNAPI/domain/entry/voting_info.rb +14 -0
- data/lib/HNAPI/domain/user.rb +18 -0
- data/lib/HNAPI/services/comment_service.rb +62 -0
- data/lib/HNAPI/services/configuration_service.rb +28 -0
- data/lib/HNAPI/services/entry_service.rb +52 -0
- data/lib/HNAPI/services/login_service.rb +26 -0
- data/lib/HNAPI/services/mechanize_context.rb +28 -0
- data/lib/HNAPI/services/not_authenticated_error.rb +8 -0
- data/lib/HNAPI/services/parsers/comments_info_parser.rb +16 -0
- data/lib/HNAPI/services/parsers/entry_page_parser.rb +24 -0
- data/lib/HNAPI/services/parsers/entry_parser.rb +19 -0
- data/lib/HNAPI/services/parsers/link_info_parser.rb +17 -0
- data/lib/HNAPI/services/parsers/time_info_parser.rb +15 -0
- data/lib/HNAPI/services/parsers/user_info_parser.rb +13 -0
- data/lib/HNAPI/services/parsers/voting_info_parser.rb +15 -0
- data/lib/HNAPI/services/voting_service.rb +11 -0
- data/lib/ruby-hackernews.rb +8 -0
- metadata +122 -0
data/README.rdoc
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
|
2
|
+
= ruby-hackernews
|
3
|
+
|
4
|
+
An API over Hacker News
|
5
|
+
|
6
|
+
== Requirements
|
7
|
+
|
8
|
+
mechanize (>= 1.0.0)
|
9
|
+
require_all (>= 1.1.0)
|
10
|
+
|
11
|
+
== Installation
|
12
|
+
|
13
|
+
gem install ruby-hackernews
|
14
|
+
|
15
|
+
then, in your script:
|
16
|
+
|
17
|
+
require 'ruby-hackernews'
|
18
|
+
|
19
|
+
before using it.
|
20
|
+
|
21
|
+
== Entries
|
22
|
+
|
23
|
+
You can get entries on the main page with:
|
24
|
+
|
25
|
+
Entry.all # returns an array of entries
|
26
|
+
|
27
|
+
You can provide a number of pages:
|
28
|
+
|
29
|
+
Entry.all(3) # will return the entries on the first 3 pages
|
30
|
+
|
31
|
+
There are methods for getting specific entry types:
|
32
|
+
|
33
|
+
Entry.questions # gets the first page of questions (ask NH)
|
34
|
+
Entry.newest # get the first page of new links (new)
|
35
|
+
Entry.jobs # get the forst page of job offest (jobs)
|
36
|
+
|
37
|
+
Each Entry instance has the following data:
|
38
|
+
|
39
|
+
entry = Entry.all.first #get the top entry on the mainpage
|
40
|
+
|
41
|
+
entry.number # entry's position on HN
|
42
|
+
|
43
|
+
entry.link.title # the link's name on HN
|
44
|
+
entry.link.href # the actual link
|
45
|
+
entry.link.site # the referring's site, if any
|
46
|
+
|
47
|
+
entry.voting.score # the entry's score on HN
|
48
|
+
|
49
|
+
entry.user.name # submitter's user name
|
50
|
+
|
51
|
+
entry.time # elapsed time from submission
|
52
|
+
|
53
|
+
After you've logged in (see below) you can do the following
|
54
|
+
|
55
|
+
entry.upvote #votes the entry
|
56
|
+
entry.write_comment("mycomment") #adds a comment to the entry
|
57
|
+
Entry.submit("mytitle", "myurl") #submit a new link
|
58
|
+
Entry.submit("myquestion") #submit a new question (ask HN)
|
59
|
+
|
60
|
+
== Comments
|
61
|
+
|
62
|
+
You get an entry's comments with:
|
63
|
+
|
64
|
+
entry.comments
|
65
|
+
|
66
|
+
You can also get the newest comments on HN with:
|
67
|
+
|
68
|
+
Comments.newest
|
69
|
+
Comments.newest(3) # get the first 3 pages of new comments
|
70
|
+
|
71
|
+
Each Comment instance has the following data:
|
72
|
+
|
73
|
+
comment = Entry.all.first.comments.first # gets the first comment of the first entry on HN's main page
|
74
|
+
|
75
|
+
comment.text # comment's body
|
76
|
+
comment.user.name # poster's user name on HN
|
77
|
+
comment.voting.score # comment's score
|
78
|
+
|
79
|
+
Comments are enumerable and threaded, so you can do like:
|
80
|
+
|
81
|
+
comment[2][0] # gets the third reply to this comment, then the first reply to the reply
|
82
|
+
comment.first # get the first reply to this comment
|
83
|
+
comment.select do |c| # gets all the comment replies which text contains "test"
|
84
|
+
text ~= /test/
|
85
|
+
end
|
86
|
+
comment.parent # gets the comment's parent (nil if no parent)
|
87
|
+
|
88
|
+
Once you're logged in (see below), you can do the following:
|
89
|
+
|
90
|
+
comment.upvote
|
91
|
+
comment.downvote
|
92
|
+
comment.reply("mycomment")
|
93
|
+
|
94
|
+
== Logging in
|
95
|
+
|
96
|
+
You define a user with:
|
97
|
+
|
98
|
+
user = User.new("username")
|
99
|
+
|
100
|
+
Then, you log in with:
|
101
|
+
|
102
|
+
user.login("password")
|
103
|
+
|
104
|
+
You can log out with:
|
105
|
+
|
106
|
+
user.logout
|
107
|
+
|
108
|
+
You have to log out before logging in with a different user.
|
109
|
+
|
110
|
+
== TO DO
|
111
|
+
|
112
|
+
Get user info (submission, comments...)
|
113
|
+
Create account
|
114
|
+
Change user info/settings
|
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/clean'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/testtask'
|
9
|
+
|
10
|
+
spec = Gem::Specification.new do |s|
|
11
|
+
s.name = 'ruby-hackernews'
|
12
|
+
s.version = '1.0.0'
|
13
|
+
s.add_dependency('require_all', '>= 1.1.0')
|
14
|
+
s.add_dependency('mechanize', '>= 1.0.0')
|
15
|
+
s.has_rdoc = false
|
16
|
+
s.homepage = "http://github.com/bolthar/ruby-hackernews"
|
17
|
+
s.summary = 'An interface to Hacker News'
|
18
|
+
s.description = s.summary
|
19
|
+
s.author = 'Andrea Dallera'
|
20
|
+
s.email = 'andrea@andreadallera.com'
|
21
|
+
s.files = %w(README.rdoc Rakefile) + Dir.glob("{lib}/**/*")
|
22
|
+
s.require_path = "lib"
|
23
|
+
end
|
24
|
+
|
25
|
+
Rake::GemPackageTask.new(spec) do |p|
|
26
|
+
p.gem_spec = spec
|
27
|
+
p.need_tar = true
|
28
|
+
p.need_zip = true
|
29
|
+
end
|
30
|
+
|
31
|
+
Spec::Rake::SpecTask.new do |t|
|
32
|
+
t.warning = true
|
33
|
+
t.spec_files = FileList['spec/**/*.rb']
|
34
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
|
2
|
+
class Comment
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :text
|
6
|
+
attr_reader :voting
|
7
|
+
attr_reader :user
|
8
|
+
|
9
|
+
attr_accessor :parent
|
10
|
+
attr_reader :children
|
11
|
+
|
12
|
+
def initialize(text, voting, user_info, reply_link)
|
13
|
+
@text = text
|
14
|
+
@voting = voting
|
15
|
+
@user = user_info
|
16
|
+
@reply_link = reply_link
|
17
|
+
@children = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def <<(comment)
|
21
|
+
comment.parent = self
|
22
|
+
@children << comment
|
23
|
+
end
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
@children.each(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def <=>(other_comment)
|
30
|
+
return other_comment.voting.score <=> @voting.score
|
31
|
+
end
|
32
|
+
|
33
|
+
def method_missing(method, *args, &block)
|
34
|
+
@children.send(method, *args, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.newest(pages = 1)
|
38
|
+
return CommentService.new.get_new_comments(pages)
|
39
|
+
end
|
40
|
+
|
41
|
+
def reply(text)
|
42
|
+
return false unless @reply_link
|
43
|
+
CommentService.new.write_comment(@reply_link, text)
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
|
47
|
+
def upvote
|
48
|
+
VotingService.new.vote(@voting.upvote)
|
49
|
+
end
|
50
|
+
|
51
|
+
def downvote
|
52
|
+
VotingService.new.vote(@voting.downvote)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
class Entry
|
3
|
+
|
4
|
+
attr_reader :number
|
5
|
+
attr_reader :link
|
6
|
+
attr_reader :voting
|
7
|
+
attr_reader :user
|
8
|
+
|
9
|
+
def initialize(number, link, voting, user, comments, time)
|
10
|
+
@number = number
|
11
|
+
@link = link
|
12
|
+
@voting = voting
|
13
|
+
@user = user
|
14
|
+
@time = time
|
15
|
+
@comments_info = comments
|
16
|
+
end
|
17
|
+
|
18
|
+
def comments
|
19
|
+
unless @comments
|
20
|
+
@comments = CommentService.new.get_comments(@comments_info.page)
|
21
|
+
end
|
22
|
+
return @comments
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.all(pages = 1)
|
26
|
+
return EntryService.new.get_entries(pages)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.newest(pages = 1)
|
30
|
+
return EntryService.new.get_new_entries(pages)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.questions(pages = 1)
|
34
|
+
return EntryService.new.get_questions(pages)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.jobs(pages = 1)
|
38
|
+
return EntryService.new.get_jobs(pages)
|
39
|
+
end
|
40
|
+
|
41
|
+
def time
|
42
|
+
return @time.time
|
43
|
+
end
|
44
|
+
|
45
|
+
def write_comment(text)
|
46
|
+
return CommentService.new.write_comment(@comments_info.page, text)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.submit(*args)
|
50
|
+
return EntryService.new.submit(*args)
|
51
|
+
end
|
52
|
+
|
53
|
+
def upvote
|
54
|
+
return VotingService.new.vote(@voting.upvote)
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
class TimeInfo
|
3
|
+
|
4
|
+
SECOND = 1
|
5
|
+
MINUTE = 60 * SECOND
|
6
|
+
HOUR = 60 * MINUTE
|
7
|
+
DAY = 24 * HOUR
|
8
|
+
|
9
|
+
def time
|
10
|
+
return Time.at(@unit_of_measure * @value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(value, unit_of_measure)
|
14
|
+
descriptor = unit_of_measure[unit_of_measure.length - 1].chr == "s" ? unit_of_measure[0..unit_of_measure.length - 2] : unit_of_measure
|
15
|
+
@value = value
|
16
|
+
@unit_of_measure = self.class.const_get(descriptor.upcase)
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
class CommentService
|
3
|
+
include MechanizeContext
|
4
|
+
|
5
|
+
def get_comments(page_url)
|
6
|
+
comments = []
|
7
|
+
last = comments
|
8
|
+
current_level = -1
|
9
|
+
page = agent.get(page_url)
|
10
|
+
page.search("//table")[3].search("table/tr").select do |tr|
|
11
|
+
tr.search("span.comment").inner_html != "[deleted]"
|
12
|
+
end.each do |tr|
|
13
|
+
comment = parse_comment(tr)
|
14
|
+
level = tr.search("img[@src='http://ycombinator.com/images/s.gif']").first['width'].to_i / 40
|
15
|
+
difference = current_level - level
|
16
|
+
target = last
|
17
|
+
(difference + 1).times do
|
18
|
+
target = target.parent || comments
|
19
|
+
end
|
20
|
+
target << comment
|
21
|
+
last = comment
|
22
|
+
current_level = level
|
23
|
+
end
|
24
|
+
return comments
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_new_comments(pages = 1)
|
28
|
+
parser = EntryPageParser.new(agent.get(ConfigurationService.comments_url))
|
29
|
+
comments = []
|
30
|
+
pages.times do
|
31
|
+
lines = parser.get_lines
|
32
|
+
lines.each do |line|
|
33
|
+
comments << parse_comment(line)
|
34
|
+
end
|
35
|
+
next_url = parser.get_next_url || break
|
36
|
+
parser = EntryPageParser.new(agent.get(next_url))
|
37
|
+
end
|
38
|
+
return comments
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse_comment(element)
|
42
|
+
text = ""
|
43
|
+
element.search("span.comment").first.children.each do |ch|
|
44
|
+
text = ch.inner_html.gsub(/<.{1,2}>/,"")
|
45
|
+
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
|
+
def write_comment(page_url, comment)
|
55
|
+
require_authentication
|
56
|
+
form = agent.get(page_url).forms.first
|
57
|
+
form.text = comment
|
58
|
+
form.submit
|
59
|
+
return true
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
class ConfigurationService
|
3
|
+
|
4
|
+
def self.base_url
|
5
|
+
return "http://news.ycombinator.com/"
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.new_url
|
9
|
+
return File.join(self.base_url, "newest")
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.ask_url
|
13
|
+
return File.join(self.base_url, "ask")
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.jobs_url
|
17
|
+
return File.join(self.base_url, "jobs")
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.comments_url
|
21
|
+
return File.join(self.base_url, "newcomments")
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.submit_url
|
25
|
+
return File.join(self.base_url, "submit")
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
class EntryService
|
3
|
+
include MechanizeContext
|
4
|
+
|
5
|
+
def get_entries(pages = 1, url = ConfigurationService.base_url)
|
6
|
+
parser = EntryPageParser.new(agent.get(url))
|
7
|
+
entry_infos = []
|
8
|
+
pages.times do
|
9
|
+
lines = parser.get_lines
|
10
|
+
(lines.length / 2).times do
|
11
|
+
entry_infos << EntryParser.new(lines.shift, lines.shift).parse
|
12
|
+
end
|
13
|
+
next_url = parser.get_next_url || break
|
14
|
+
parser = EntryPageParser.new(agent.get(next_url))
|
15
|
+
end
|
16
|
+
return entry_infos
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_new_entries(pages = 1)
|
20
|
+
return get_entries(pages, ConfigurationService.new_url)
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_questions(pages = 1)
|
24
|
+
return get_entries(pages, ConfigurationService.ask_url)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_jobs(pages = 1)
|
28
|
+
return get_entries(pages, ConfigurationService.jobs_url)
|
29
|
+
end
|
30
|
+
|
31
|
+
def submit(*args)
|
32
|
+
require_authentication
|
33
|
+
form = agent.get(ConfigurationService.submit_url).forms.first
|
34
|
+
submit_link(form, args[0], args[1]) if args.length == 2
|
35
|
+
submit_question(form, args[0]) if args.length == 1
|
36
|
+
return true
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def submit_link(form, title, url)
|
41
|
+
form.t = title
|
42
|
+
form.u = url
|
43
|
+
form.submit
|
44
|
+
end
|
45
|
+
|
46
|
+
def submit_question(form, text)
|
47
|
+
form.x = text
|
48
|
+
form.submit
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
class LoginService
|
3
|
+
include MechanizeContext
|
4
|
+
|
5
|
+
def login(username, password)
|
6
|
+
raise "You are logged in already - logout first." if authenticated?
|
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
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module MechanizeContext
|
3
|
+
|
4
|
+
@@contexts = {}
|
5
|
+
|
6
|
+
def self.agent=(key)
|
7
|
+
@@default = key
|
8
|
+
end
|
9
|
+
|
10
|
+
def agent
|
11
|
+
@@default ||= :default
|
12
|
+
@@contexts[@@default] = Mechanize.new unless @@contexts[@@default]
|
13
|
+
return @@contexts[@@default]
|
14
|
+
end
|
15
|
+
|
16
|
+
def [](key)
|
17
|
+
return @@contexts[key]
|
18
|
+
end
|
19
|
+
|
20
|
+
def require_authentication
|
21
|
+
raise NotAuthenticatedError unless authenticated?
|
22
|
+
end
|
23
|
+
|
24
|
+
def authenticated?(key = :default)
|
25
|
+
return @@contexts[key].cookie_jar.jar.any?
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CommentsInfoParser
|
2
|
+
|
3
|
+
def initialize(comments_element)
|
4
|
+
@element = comments_element.search("a")[1]
|
5
|
+
end
|
6
|
+
|
7
|
+
def parse
|
8
|
+
comments_info = nil
|
9
|
+
if @element
|
10
|
+
comments = @element.inner_html.split[0].to_i
|
11
|
+
comments_page = @element['href']
|
12
|
+
comments_info = CommentsInfo.new(comments, comments_page)
|
13
|
+
end
|
14
|
+
return comments_info
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
class EntryPageParser
|
3
|
+
|
4
|
+
def initialize(page)
|
5
|
+
@page = page
|
6
|
+
end
|
7
|
+
|
8
|
+
def get_lines
|
9
|
+
lines = @page.search("//table")[2].search("tr").select do |tr|
|
10
|
+
tr['style'] !~ /height/ &&
|
11
|
+
tr.children.first.attributes.count != 0
|
12
|
+
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
|
+
|
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
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
class EntryParser
|
3
|
+
|
4
|
+
def initialize(first_line, second_line)
|
5
|
+
@first_line = first_line
|
6
|
+
@second_line = second_line
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
number = @first_line.search("[@class='title']")[0].inner_html.sub(".","").to_i
|
11
|
+
link = LinkInfoParser.new(@first_line.search("[@class='title']")[1]).parse
|
12
|
+
voting = VotingInfoParser.new(@first_line.search("td/center/a"), @second_line.search("[@class='subtext']")[0]).parse
|
13
|
+
user = UserInfoParser.new(@second_line.search("[@class='subtext']")[0]).parse
|
14
|
+
comments = CommentsInfoParser.new(@second_line.search("[@class='subtext']")[0]).parse
|
15
|
+
time = TimeInfoParser.new(@second_line.search("[@class='subtext']").children[3]).parse
|
16
|
+
return Entry.new(number, link, voting, user, comments, time)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
class LinkInfoParser
|
3
|
+
|
4
|
+
def initialize(link_element)
|
5
|
+
@element = link_element
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse
|
9
|
+
link = @element.search("a")[0]['href']
|
10
|
+
title = @element.search("a")[0].inner_html
|
11
|
+
site_element = @element.search("span")
|
12
|
+
site = site_element.inner_html.sub("(","").sub(")","").strip if site_element.any?
|
13
|
+
return LinkInfo.new(title, link, site)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
class TimeInfoParser
|
3
|
+
|
4
|
+
def initialize(time_element)
|
5
|
+
@element = time_element
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse
|
9
|
+
value = @element.text.strip.split[0].to_i
|
10
|
+
unit_of_measure = @element.text.strip.split[1]
|
11
|
+
return TimeInfo.new(value, unit_of_measure)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
class UserInfoParser
|
3
|
+
|
4
|
+
def initialize(user_element)
|
5
|
+
@element = user_element
|
6
|
+
end
|
7
|
+
|
8
|
+
def parse
|
9
|
+
user_name = @element.search("a")[0].inner_html
|
10
|
+
user_page = @element.search("a")[0]['href']
|
11
|
+
return UserInfo.new(user_name, user_page)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
class VotingInfoParser
|
3
|
+
|
4
|
+
def initialize(voting_element, score_element)
|
5
|
+
@voting_element = voting_element
|
6
|
+
@score_element = score_element
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
upvote = @voting_element[0]['href'] if @voting_element[0]
|
11
|
+
downvote = @voting_element[1]['href'] if @voting_element[1]
|
12
|
+
score = @score_element.search("span")[0].inner_html.split[0].to_i
|
13
|
+
return VotingInfo.new(score, upvote, downvote)
|
14
|
+
end
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby-hackernews
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andrea Dallera
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-08 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: require_all
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 19
|
30
|
+
segments:
|
31
|
+
- 1
|
32
|
+
- 1
|
33
|
+
- 0
|
34
|
+
version: 1.1.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: mechanize
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 23
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 0
|
49
|
+
- 0
|
50
|
+
version: 1.0.0
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
description: An interface to Hacker News
|
54
|
+
email: andrea@andreadallera.com
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files: []
|
60
|
+
|
61
|
+
files:
|
62
|
+
- README.rdoc
|
63
|
+
- Rakefile
|
64
|
+
- lib/HNAPI/domain/comment/comment.rb
|
65
|
+
- lib/HNAPI/domain/user.rb
|
66
|
+
- lib/HNAPI/domain/entry/link_info.rb
|
67
|
+
- lib/HNAPI/domain/entry/voting_info.rb
|
68
|
+
- lib/HNAPI/domain/entry/time_info.rb
|
69
|
+
- lib/HNAPI/domain/entry/user_info.rb
|
70
|
+
- lib/HNAPI/domain/entry/entry.rb
|
71
|
+
- lib/HNAPI/domain/entry/comments_info.rb
|
72
|
+
- lib/HNAPI/services/parsers/link_info_parser.rb
|
73
|
+
- lib/HNAPI/services/parsers/entry_parser.rb
|
74
|
+
- lib/HNAPI/services/parsers/voting_info_parser.rb
|
75
|
+
- lib/HNAPI/services/parsers/user_info_parser.rb
|
76
|
+
- lib/HNAPI/services/parsers/time_info_parser.rb
|
77
|
+
- lib/HNAPI/services/parsers/entry_page_parser.rb
|
78
|
+
- lib/HNAPI/services/parsers/comments_info_parser.rb
|
79
|
+
- lib/HNAPI/services/voting_service.rb
|
80
|
+
- lib/HNAPI/services/mechanize_context.rb
|
81
|
+
- lib/HNAPI/services/comment_service.rb
|
82
|
+
- lib/HNAPI/services/login_service.rb
|
83
|
+
- lib/HNAPI/services/entry_service.rb
|
84
|
+
- lib/HNAPI/services/not_authenticated_error.rb
|
85
|
+
- lib/HNAPI/services/configuration_service.rb
|
86
|
+
- lib/ruby-hackernews.rb
|
87
|
+
has_rdoc: true
|
88
|
+
homepage: http://github.com/bolthar/ruby-hackernews
|
89
|
+
licenses: []
|
90
|
+
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
hash: 3
|
111
|
+
segments:
|
112
|
+
- 0
|
113
|
+
version: "0"
|
114
|
+
requirements: []
|
115
|
+
|
116
|
+
rubyforge_project:
|
117
|
+
rubygems_version: 1.3.7
|
118
|
+
signing_key:
|
119
|
+
specification_version: 3
|
120
|
+
summary: An interface to Hacker News
|
121
|
+
test_files: []
|
122
|
+
|