goatless 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/goatless +28 -0
- data/lib/goatless/friend_fetcher.rb +76 -0
- data/lib/goatless/goatless.rb +30 -0
- data/lib/goatless/opml_fetcher.rb +24 -0
- data/lib/goatless/rss_builder.rb +40 -0
- data/lib/goatless/rss_item.rb +14 -0
- data/lib/goatless.rb +7 -0
- metadata +71 -0
data/bin/goatless
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'goatless'
|
5
|
+
|
6
|
+
options = {}
|
7
|
+
optparse = OptionParser.new do |opts|
|
8
|
+
opts.banner = "Usage: goatless.rb -u username -p password -f path"
|
9
|
+
opts.on("-u", "--username [username]", "LJ Username") do |v|
|
10
|
+
options[:username] = v
|
11
|
+
end
|
12
|
+
opts.on("-p", "--password [password]", "LJ Password") do |v|
|
13
|
+
options[:password] = v
|
14
|
+
end
|
15
|
+
opts.on("-f", "--file [filepath]", "File to dump RSS feed") do |v|
|
16
|
+
options[:filename] = v
|
17
|
+
end
|
18
|
+
end
|
19
|
+
optparse.parse!
|
20
|
+
|
21
|
+
|
22
|
+
if [options[:username], options[:password], options[:filename]].include? nil
|
23
|
+
puts optparse
|
24
|
+
exit(-1)
|
25
|
+
end
|
26
|
+
|
27
|
+
goatless = Goatless::Goatless.new(options[:username], options[:password])
|
28
|
+
File.new(options[:filename], "w+").write(goatless.rss)
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Goatless
|
2
|
+
class FriendFetcher
|
3
|
+
attr_accessor :friend_url, :username, :password, :items
|
4
|
+
|
5
|
+
def initialize(friend_url, username, password)
|
6
|
+
puts friend_url
|
7
|
+
@friend_url = friend_url
|
8
|
+
@username = username
|
9
|
+
@password = password
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch
|
13
|
+
@patron_session = Patron::Session.new
|
14
|
+
@patron_session.username = @username
|
15
|
+
@patron_session.password = @password
|
16
|
+
@patron_session.timeout = 10
|
17
|
+
@patron_session.connect_timeout = 30000
|
18
|
+
@patron_session.auth_type = :digest
|
19
|
+
@items = []
|
20
|
+
xml = @patron_session.get("#{@friend_url}?auth=digest").body
|
21
|
+
parse(xml)
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse(xml)
|
25
|
+
xml_doc = Nokogiri::XML::Reader(xml)
|
26
|
+
@storage = PStore.new('goatless.pstore') # Hope this can decrease memory usage
|
27
|
+
|
28
|
+
max_items = 50 # Move this to the CLI flag or config
|
29
|
+
current_item = nil
|
30
|
+
current_author = nil
|
31
|
+
|
32
|
+
xml_doc.each do |node|
|
33
|
+
next if max_items == 0
|
34
|
+
case node.name
|
35
|
+
when 'lj:journal'
|
36
|
+
current_author = node.inner_xml if current_author.nil?
|
37
|
+
when 'item'
|
38
|
+
if current_item.nil?
|
39
|
+
current_item = RssItem.new
|
40
|
+
else
|
41
|
+
current_item.author = current_author
|
42
|
+
if current_item.pub_date > (DateTime.now - 7) # do not add items older then week
|
43
|
+
@storage.transaction do
|
44
|
+
@storage[:items] ||= Array.new
|
45
|
+
unless @storage[:items].include? current_item
|
46
|
+
@storage[:items] << current_item
|
47
|
+
@storage.commit
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
current_item = nil
|
52
|
+
max_items -= 1
|
53
|
+
end
|
54
|
+
when 'link'
|
55
|
+
unless current_item.nil?
|
56
|
+
current_item.permalink = node.inner_xml if current_item.permalink.nil?
|
57
|
+
end
|
58
|
+
when 'pubDate'
|
59
|
+
current_item.pub_date = DateTime.parse(node.inner_xml) if current_item.pub_date.nil?
|
60
|
+
when 'description'
|
61
|
+
unless current_item.nil?
|
62
|
+
if current_item.body.nil?
|
63
|
+
current_item.body = node.inner_xml
|
64
|
+
end
|
65
|
+
end
|
66
|
+
when 'lj:security'
|
67
|
+
current_item.security_type = node.inner_xml if current_item.security_type.nil?
|
68
|
+
when 'title'
|
69
|
+
unless current_item.nil?
|
70
|
+
current_item.title = node.inner_xml if current_item.title.nil?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Goatless
|
2
|
+
class Goatless
|
3
|
+
def initialize(username, password)
|
4
|
+
@username, @password = username, password
|
5
|
+
@threads = []
|
6
|
+
friends_urls = OpmlFetcher.new(@username).fetch
|
7
|
+
friends_urls.each_slice(3) do |rss_urls|
|
8
|
+
@threads << Thread.new {
|
9
|
+
rss_urls.each do |rss_url|
|
10
|
+
friend_fetcher = FriendFetcher.new(rss_url, @username, @password)
|
11
|
+
begin
|
12
|
+
friend_fetcher.fetch
|
13
|
+
rescue Exception => e
|
14
|
+
# Still have a lot's of "connection timed out" here
|
15
|
+
puts e
|
16
|
+
end
|
17
|
+
end
|
18
|
+
}
|
19
|
+
end
|
20
|
+
@threads.each { |t| t.join }
|
21
|
+
end
|
22
|
+
|
23
|
+
def rss
|
24
|
+
@storage = PStore.new('goatless.pstore')
|
25
|
+
@storage.transaction(true) do
|
26
|
+
RssBuilder.new(@storage[:items]).document
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Goatless
|
2
|
+
class OpmlFetcher
|
3
|
+
attr_accessor :rss_urls
|
4
|
+
|
5
|
+
def initialize(username)
|
6
|
+
@url = "http://www.livejournal.com/tools/opml.bml?user=#{username}"
|
7
|
+
# @url = 'http://sdfgh153.ru/sample.opml'
|
8
|
+
end
|
9
|
+
|
10
|
+
def fetch
|
11
|
+
patron_session = Patron::Session.new
|
12
|
+
patron_session.timeout = 30
|
13
|
+
patron_session.connect_timeout = 30000
|
14
|
+
@rss_urls = []
|
15
|
+
|
16
|
+
opml_doc = Nokogiri::XML(patron_session.get(@url).body)
|
17
|
+
opml_doc.elements.xpath("//outline").each do |outline|
|
18
|
+
@rss_urls << outline['xmlUrl']
|
19
|
+
end
|
20
|
+
puts "You have #{@rss_urls.size} friends!"
|
21
|
+
@rss_urls
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Goatless
|
2
|
+
class RssBuilder
|
3
|
+
|
4
|
+
def initialize(items)
|
5
|
+
@items = items
|
6
|
+
end
|
7
|
+
|
8
|
+
def document
|
9
|
+
#TODO: Move number of items in feed into settings
|
10
|
+
items = @items.sort { |x, y| x.pub_date <=> y.pub_date }.last(100).reverse
|
11
|
+
|
12
|
+
builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |rss|
|
13
|
+
rss.rss(:version => '2.0') {
|
14
|
+
rss.channel {
|
15
|
+
rss.title "Friends"
|
16
|
+
rss.generator "Goatless"
|
17
|
+
rss.description "Goatless friends"
|
18
|
+
rss.link "https://github.com/semka/goatless"
|
19
|
+
|
20
|
+
items.each do |i|
|
21
|
+
rss.item {
|
22
|
+
rss.pubDate i.pub_date.strftime("%a, %d %b %Y %H:%M:%S %z") # to RFC 822
|
23
|
+
rss.author i.author
|
24
|
+
rss.link i.permalink
|
25
|
+
if i.public?
|
26
|
+
rss.title i.title
|
27
|
+
rss.description do |d| d << i.body end
|
28
|
+
else
|
29
|
+
rss.title "Private post"
|
30
|
+
rss.description "Private post"
|
31
|
+
end
|
32
|
+
}
|
33
|
+
end
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
builder.to_xml
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Goatless
|
2
|
+
|
3
|
+
class RssItem
|
4
|
+
attr_accessor :pub_date, :xml_element, :security_type, :pub_date, :title, :body, :permalink, :author
|
5
|
+
|
6
|
+
def public?
|
7
|
+
@security_type == 'public'
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(an_rss_item)
|
11
|
+
self.permalink == an_rss_item.permalink && !self.permalink.nil?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/goatless.rb
ADDED
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: goatless
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Semka Novikov
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-19 00:00:00 Z
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description:
|
22
|
+
email: semka.novikov@gmail.com
|
23
|
+
executables:
|
24
|
+
- goatless
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files: []
|
28
|
+
|
29
|
+
files:
|
30
|
+
- bin/goatless
|
31
|
+
- lib/goatless/friend_fetcher.rb
|
32
|
+
- lib/goatless/goatless.rb
|
33
|
+
- lib/goatless/opml_fetcher.rb
|
34
|
+
- lib/goatless/rss_builder.rb
|
35
|
+
- lib/goatless/rss_item.rb
|
36
|
+
- lib/goatless.rb
|
37
|
+
homepage: http://yoursite.example.com
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
hash: 3
|
51
|
+
segments:
|
52
|
+
- 0
|
53
|
+
version: "0"
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.8.5
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: What this thing does
|
70
|
+
test_files: []
|
71
|
+
|