goatless 0.1.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/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
|
+
|