google_reader 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ *.xml
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2009 François Beausoleil (francois@teksol.info)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ 'Software'), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.textile ADDED
@@ -0,0 +1,56 @@
1
+ h1. Google Reader Client
2
+
3
+ Access your Google Reader account from Ruby:
4
+
5
+ <pre><code>require "google_reader"
6
+ # Get an instance of a client like this
7
+ client = GoogleReader::Client.authenticate(USERNAME, PASSWORD)
8
+
9
+ # Then, call any of these
10
+ client.unread_items
11
+ client.read_items
12
+ client.broadcast_items
13
+ client.body_link_used_items
14
+ client.item_link_used_items
15
+ client.clicked_through_items
16
+ client.emailed_items
17
+ client.starred_items
18
+
19
+ # To receive an Array of Atom Entries:
20
+ item = client.items.unread
21
+ p item.title
22
+ p item.categories
23
+ p item.id
24
+ p item.published_at
25
+ p item.updated_at
26
+ p item.href
27
+ p item.author_unknown?
28
+ p item.author
29
+ p item.source
30
+ p item.liking_users
31
+ </code></pre>
32
+
33
+ h2. LICENSE
34
+
35
+ (The MIT License)
36
+
37
+ Copyright (c) 2008-2009 François Beausoleil (francois@teksol.info)
38
+
39
+ Permission is hereby granted, free of charge, to any person obtaining
40
+ a copy of this software and associated documentation files (the
41
+ 'Software'), to deal in the Software without restriction, including
42
+ without limitation the rights to use, copy, modify, merge, publish,
43
+ distribute, sublicense, and/or sell copies of the Software, and to
44
+ permit persons to whom the Software is furnished to do so, subject to
45
+ the following conditions:
46
+
47
+ The above copyright notice and this permission notice shall be
48
+ included in all copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
51
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
52
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
53
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
54
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
55
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
56
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "google_reader/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "google_reader"
7
+ s.version = GoogleReader::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["François Beausoleil"]
10
+ s.email = ["francois@teksol.info"]
11
+ s.homepage = "https://github.com/francois/google_reader"
12
+ s.summary = %q{An unofficial Ruby client for Google Reader }
13
+ s.description = %q{Access Google Reader in a quick and simple way, using plain Ruby.}
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.add_dependency "rest-client", "~> 1.6.1"
21
+ s.add_dependency "nokogiri", "~> 1.4.4"
22
+
23
+ s.add_development_dependency "rspec"
24
+ s.add_development_dependency "ruby-debug19"
25
+ end
@@ -0,0 +1,4 @@
1
+ module GoogleReader
2
+ autoload :Client, "google_reader/client"
3
+ autoload :Entry, "google_reader/entry"
4
+ end
@@ -0,0 +1,58 @@
1
+ require "rest_client"
2
+ require "nokogiri"
3
+ require "cgi"
4
+
5
+ module GoogleReader
6
+ class Client
7
+ def self.authenticate(username, password)
8
+ resp = RestClient.post("https://www.google.com/accounts/ClientLogin",
9
+ :Email => username,
10
+ :Passwd => password,
11
+ :service => "reader")
12
+
13
+ dict = resp.split.inject(Hash.new) do |memo, encoded_pair|
14
+ key, value = encoded_pair.split("=").map {|val| CGI.unescape(val)}
15
+ memo[key] = value
16
+ memo
17
+ end
18
+
19
+ token = dict["Auth"]
20
+ new("Authorization" => "GoogleLogin auth=#{token}", "Accept" => "application/xml")
21
+ end
22
+
23
+ attr_reader :headers
24
+
25
+ def initialize(headers)
26
+ @headers = headers
27
+ end
28
+
29
+ BASE_URL = "http://www.google.com/reader/atom/user/-/".freeze
30
+ STATE_URL = BASE_URL + "state/com.google/".freeze
31
+
32
+ %w(
33
+ read
34
+ broadcast
35
+ starred
36
+ subscriptions
37
+ tracking-emailed
38
+ tracking-item-link-used
39
+ tracking-body-link-used).each do |suffix|
40
+
41
+ method_name = suffix.gsub("-", "_") + "_items"
42
+ define_method(method_name) do |count=20|
43
+ content = RestClient.get(STATE_URL + suffix + "?n=#{CGI.escape(count.to_s)}", headers)
44
+ parse_atom_feed(content)
45
+ end
46
+ end
47
+
48
+ def unread_items
49
+ content = RestClient.get(STATE_URL + suffix + "?n=#{CGI.escape(count.to_s)}&xt=#{CGI.escape("state/com.google/read")}", headers)
50
+ parse_atom_feed(content)
51
+ end
52
+
53
+ def parse_atom_feed(feed)
54
+ doc = Nokogiri::XML(feed)
55
+ doc.search("entry").map {|entry| Entry.new(entry)}
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,87 @@
1
+ require "ostruct"
2
+ require "time"
3
+
4
+ module GoogleReader
5
+ class Entry
6
+ def initialize(entry)
7
+ @entry = entry
8
+ end
9
+
10
+ def id
11
+ id_node.text
12
+ end
13
+
14
+ def original_id
15
+ oid_node = id_node.attribute_with_ns("original-id", GOOGLE_ATOM_NAMESPACE)
16
+ oid_node ? oid_node.text : id
17
+ end
18
+
19
+ def title
20
+ unhtml(@entry.search("title").first.text)
21
+ end
22
+
23
+ def categories
24
+ @entry.search("category").reject do |node|
25
+ node["scheme"] == "http://www.google.com/reader/"
26
+ end.map do |node|
27
+ node["label"] || node["term"]
28
+ end
29
+ end
30
+
31
+ def published_at
32
+ Time.parse(@entry.search("published").first.text)
33
+ end
34
+
35
+ def updated_at
36
+ node = @entry.search("updated").first
37
+ node ? Time.parse(@entry.search("updated").first.text) : published_at
38
+ end
39
+
40
+ def href
41
+ @entry.search("link[rel=alternate]").reject do |node|
42
+ node["href"].to_s.empty?
43
+ end.detect do |node|
44
+ node["type"] == "text/html"
45
+ end["href"]
46
+ end
47
+
48
+ def has_known_author?
49
+ node = @entry.search("author").first
50
+ return false unless node
51
+ attr = node.attribute_with_ns("unknown-author", GOOGLE_ATOM_NAMESPACE)
52
+ return true unless attr
53
+ attr.text != "true"
54
+ end
55
+
56
+ def author
57
+ @entry.search("author name").first.text
58
+ end
59
+
60
+ def source
61
+ node = @entry.search("source").first
62
+ OpenStruct.new(:title => unhtml(node.search("title").first.text),
63
+ :href => node.search("link[rel=alternate]").first["href"],
64
+ :id => node.search("id").first.text,
65
+ :stream_id => node.attribute_with_ns("stream-id", GOOGLE_ATOM_NAMESPACE).text)
66
+ end
67
+
68
+ def liking_users
69
+ # NOTE: CSS namespaces don't work all that well: must use XPath here
70
+ @entry.search("./gr:likingUser", "gr" => GOOGLE_ATOM_NAMESPACE).map do |node|
71
+ node.text
72
+ end
73
+ end
74
+
75
+ def id_node
76
+ nodes = @entry.search("id")
77
+ return nil if nodes.empty?
78
+ nodes.first
79
+ end
80
+
81
+ def unhtml(text)
82
+ text.gsub("&lt;", "<").gsub("&gt;", ">").gsub("&amp;", "&").gsub("&quot;", '"')
83
+ end
84
+
85
+ GOOGLE_ATOM_NAMESPACE = "http://www.google.com/schemas/reader/atom/".freeze
86
+ end
87
+ end
@@ -0,0 +1,3 @@
1
+ module GoogleReader
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,99 @@
1
+ require "spec_helper"
2
+
3
+ describe GoogleReader::Client, "#authenticate" do
4
+ it "should immediately authenticate using Google's client login" do
5
+ RestClient.should_receive(:post).and_return("Auth=abc")
6
+ GoogleReader::Client.authenticate("abc", "123")
7
+ end
8
+
9
+ it "should use the provided username and password during the authentication process" do
10
+ RestClient.should_receive(:post).with("https://www.google.com/accounts/ClientLogin", :Email => "user", :Passwd => "pass", :service => "reader").and_return("Auth=abc")
11
+ GoogleReader::Client.authenticate("user", "pass")
12
+ end
13
+
14
+ it "should instantiate a new GoogleReader::Client instance with the correct authentication header" do
15
+ RestClient.stub(:post).and_return("Auth=my-fancy-token")
16
+ GoogleReader::Client.should_receive(:new).with(hash_including("Authorization" => "GoogleLogin auth=my-fancy-token"))
17
+ GoogleReader::Client.authenticate("a", "b")
18
+ end
19
+ end
20
+
21
+ describe GoogleReader::Client do
22
+ subject do
23
+ GoogleReader::Client.new("Authorization" => "GoogleLogin auth=my-fancy-token")
24
+ end
25
+
26
+ let :xml_content do
27
+ File.read(File.dirname(__FILE__) + "/fixtures/entry.xml")
28
+ end
29
+
30
+ it "should fetch the default 20 items from #read_items" do
31
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/read?n=20", anything).and_return(xml_content)
32
+ subject.read_items
33
+ end
34
+
35
+ it "should fetch the requested number of items from #read_items" do
36
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/read?n=59", anything).and_return(xml_content)
37
+ subject.read_items(59)
38
+ end
39
+
40
+ it "should fetch the default 20 items from #broadcast_items" do
41
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/broadcast?n=20", anything).and_return(xml_content)
42
+ subject.broadcast_items
43
+ end
44
+
45
+ it "should fetch the requested number of items from #broadcast_items" do
46
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/broadcast?n=59", anything).and_return(xml_content)
47
+ subject.broadcast_items(59)
48
+ end
49
+
50
+ it "should fetch the default 20 items from #starred_items" do
51
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/starred?n=20", anything).and_return(xml_content)
52
+ subject.starred_items
53
+ end
54
+
55
+ it "should fetch the requested number of items from #starred_items" do
56
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/starred?n=31", anything).and_return(xml_content)
57
+ subject.starred_items(31)
58
+ end
59
+
60
+ it "should fetch the default 20 items from #subscriptions_items" do
61
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/subscriptions?n=20", anything).and_return(xml_content)
62
+ subject.subscriptions_items
63
+ end
64
+
65
+ it "should fetch the requested number of items from #subscriptions_items" do
66
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/subscriptions?n=19", anything).and_return(xml_content)
67
+ subject.subscriptions_items(19)
68
+ end
69
+
70
+ it "should fetch the default 20 items from #tracking_emailed_items" do
71
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-emailed?n=20", anything).and_return(xml_content)
72
+ subject.tracking_emailed_items
73
+ end
74
+
75
+ it "should fetch the requested number of items from #tracking_emailed_items" do
76
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-emailed?n=43", anything).and_return(xml_content)
77
+ subject.tracking_emailed_items(43)
78
+ end
79
+
80
+ it "should fetch the default 20 items from #tracking_item_link_used_items" do
81
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-item-link-used?n=20", anything).and_return(xml_content)
82
+ subject.tracking_item_link_used_items
83
+ end
84
+
85
+ it "should fetch the requested number of items from #tracking_item_link_used_items" do
86
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-item-link-used?n=43", anything).and_return(xml_content)
87
+ subject.tracking_item_link_used_items(43)
88
+ end
89
+
90
+ it "should fetch the default 20 items from #tracking_body_link_used_items" do
91
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-body-link-used?n=20", anything).and_return(xml_content)
92
+ subject.tracking_body_link_used_items
93
+ end
94
+
95
+ it "should fetch the requested number of items from #tracking_body_link_used_items" do
96
+ RestClient.should_receive(:get).with("http://www.google.com/reader/atom/user/-/state/com.google/tracking-body-link-used?n=43", anything).and_return(xml_content)
97
+ subject.tracking_body_link_used_items(43)
98
+ end
99
+ end
@@ -0,0 +1,110 @@
1
+ require "spec_helper"
2
+ require "set"
3
+
4
+ describe GoogleReader::Entry, "where the author is known, and the updated timestamp is different from the published timestamp, and with 3 liking users" do
5
+ let :entry_xml do
6
+ File.read(File.dirname(__FILE__) + "/fixtures/entry.xml")
7
+ end
8
+
9
+ let :entry do
10
+ Nokogiri::XML(entry_xml).root.search("entry").first
11
+ end
12
+
13
+ let :liking_users do
14
+ expected = Set.new
15
+ expected << "06592066937024742113"
16
+ expected << "07355112106994622540"
17
+ expected << "09036194539349082984"
18
+ end
19
+
20
+ subject do
21
+ GoogleReader::Entry.new(entry)
22
+ end
23
+
24
+ it "should HTML unescape the title" do
25
+ subject.title.should == "Find cheapest combination of rooms in hotels & other entries"
26
+ end
27
+
28
+ it "should HTML unescape the source's title" do
29
+ subject.source.title.should == "select * from depesz where 1 < 41 and 1 > 41 and \"public\".sometable = 'asdf';"
30
+ end
31
+
32
+ it { subject.published_at.should == Time.utc(2011, 4, 27, 13, 28, 53) }
33
+ it { subject.updated_at.should == Time.utc(2011, 4, 28, 14, 28, 53) }
34
+ it { subject.href.should == "http://www.depesz.com/index.php/2011/04/27/find-cheapest-combination-of-rooms-in-hotels/" }
35
+ it { subject.should have_known_author }
36
+ it { subject.author.should == "depesz" }
37
+ it { subject.source.id.should == "tag:google.com,2005:reader/feed/http://www.depesz.com/index.php/feed/" }
38
+ it { subject.source.href == "http://www.depesz.com" }
39
+ it { subject.id.should == "tag:google.com,2005:reader/item/d5dee0c34e012ddb" }
40
+ it { subject.original_id.should == "http://www.depesz.com/?p=2149" }
41
+
42
+ it "should ignore Google categories" do
43
+ subject.categories.should_not include("read")
44
+ subject.categories.should_not include("fresh")
45
+ subject.categories.should_not include("tracking-item-link-used")
46
+ end
47
+
48
+ it "should find the entry's categories" do
49
+ subject.categories.should include("Uncategorized")
50
+ subject.categories.should include("combinations")
51
+ subject.categories.should include("cte")
52
+ subject.categories.should include("postgresql")
53
+ subject.categories.should include("recursive")
54
+ subject.categories.should include("stackoverflow")
55
+ subject.categories.should include("with recursive")
56
+ end
57
+
58
+ it "should find liking users" do
59
+ subject.liking_users.to_set.should == liking_users
60
+ end
61
+ end
62
+
63
+ describe GoogleReader::Entry, "where the author is unknown" do
64
+ let :entry_xml do
65
+ File.read(File.dirname(__FILE__) + "/fixtures/entry-with-unknown-author.xml")
66
+ end
67
+
68
+ let :entry do
69
+ Nokogiri::XML(entry_xml).root.search("entry").first
70
+ end
71
+
72
+ subject do
73
+ GoogleReader::Entry.new(entry)
74
+ end
75
+
76
+ it { subject.should_not have_known_author }
77
+ it { subject.author.should == "(author unknown)" }
78
+ end
79
+
80
+ describe GoogleReader::Entry, "where no users have liked the entry" do
81
+ let :entry_xml do
82
+ File.read(File.dirname(__FILE__) + "/fixtures/entry-with-no-likes.xml")
83
+ end
84
+
85
+ let :entry do
86
+ Nokogiri::XML(entry_xml).root.search("entry").first
87
+ end
88
+
89
+ subject do
90
+ GoogleReader::Entry.new(entry)
91
+ end
92
+
93
+ it { subject.liking_users.should be_empty }
94
+ end
95
+
96
+ describe GoogleReader::Entry, "when no original ID is present" do
97
+ let :entry_xml do
98
+ File.read(File.dirname(__FILE__) + "/fixtures/entry-with-no-original-id.xml")
99
+ end
100
+
101
+ let :entry do
102
+ Nokogiri::XML(entry_xml).root.search("entry").first
103
+ end
104
+
105
+ subject do
106
+ GoogleReader::Entry.new(entry)
107
+ end
108
+
109
+ it { subject.original_id.should == subject.id }
110
+ end
@@ -0,0 +1,29 @@
1
+ <?xml version="1.0"?>
2
+ <feed xmlns:idx="urn:atom-extension:indexing" xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr">
3
+ <entry gr:crawl-timestamp-msec="1303995951053">
4
+ <id gr:original-id="http://www.depesz.com/?p=2149">tag:google.com,2005:reader/item/d5dee0c34e012ddb</id>
5
+ <category term="user/10212770223479967035/state/com.google/read" scheme="http://www.google.com/reader/" label="read"/>
6
+ <category term="user/10212770223479967035/state/com.google/tracking-item-link-used" scheme="http://www.google.com/reader/" label="tracking-item-link-used"/>
7
+ <category term="user/10212770223479967035/state/com.google/fresh" scheme="http://www.google.com/reader/" label="fresh"/>
8
+ <category term="Uncategorized"/>
9
+ <category term="combinations"/>
10
+ <category term="cte"/>
11
+ <category term="postgresql"/>
12
+ <category term="recursive"/>
13
+ <category term="stackoverflow"/>
14
+ <category term="with recursive"/>
15
+ <title type="html">Find cheapest combination of rooms in hotels &amp; other entries</title>
16
+ <published>2011-04-27T13:28:53Z</published>
17
+ <updated>2011-04-27T14:28:53Z</updated>
18
+ <link rel="alternate" href="http://www.depesz.com/index.php/2011/04/27/find-cheapest-combination-of-rooms-in-hotels/" type="text/html"/>
19
+ <summary xml:base="http://www.depesz.com/" type="html">Today, on Stack Overflow there was interesting question. Generally, given table that looks like this: room | people | price | hotel 1 | 1 | 200 | A 2 | 2 | 99 | A 3 | 3 | 95 | A 4 | 1 | 90 | B 5 | 6 | 300 [...]&lt;img src="http://feeds.feedburner.com/~r/depesz/~4/5LKjrsu7JnQ" height="1" width="1"&gt;</summary>
20
+ <author gr:unknown-author="true">
21
+ <name>(author unknown)</name>
22
+ </author>
23
+ <source gr:stream-id="feed/http://www.depesz.com/index.php/feed/">
24
+ <id>tag:google.com,2005:reader/feed/http://www.depesz.com/index.php/feed/</id>
25
+ <title type="html">select * from depesz where 1 &lt; 41 and 1 &gt; 41 and &quot;public&quot;.sometable = 'asdf';</title>
26
+ <link rel="alternate" href="http://www.depesz.com" type="text/html"/>
27
+ </source>
28
+ </entry>
29
+ </feed>
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0"?>
2
+ <feed xmlns:idx="urn:atom-extension:indexing" xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr">
3
+ <entry gr:crawl-timestamp-msec="1303995951053">
4
+ <id>tag:google.com,2005:reader/item/d5dee0c34e012ddb</id>
5
+ </entry>
6
+ </feed>
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0"?>
2
+ <feed xmlns:idx="urn:atom-extension:indexing" xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr">
3
+ <entry gr:crawl-timestamp-msec="1303995951053">
4
+ <id gr:original-id="http://www.depesz.com/?p=2149">tag:google.com,2005:reader/item/d5dee0c34e012ddb</id>
5
+ <category term="user/10212770223479967035/state/com.google/read" scheme="http://www.google.com/reader/" label="read"/>
6
+ <category term="user/10212770223479967035/state/com.google/tracking-item-link-used" scheme="http://www.google.com/reader/" label="tracking-item-link-used"/>
7
+ <category term="user/10212770223479967035/state/com.google/fresh" scheme="http://www.google.com/reader/" label="fresh"/>
8
+ <category term="Uncategorized"/>
9
+ <category term="combinations"/>
10
+ <category term="cte"/>
11
+ <category term="postgresql"/>
12
+ <category term="recursive"/>
13
+ <category term="stackoverflow"/>
14
+ <category term="with recursive"/>
15
+ <title type="html">Find cheapest combination of rooms in hotels &amp; other entries</title>
16
+ <published>2011-04-27T13:28:53Z</published>
17
+ <updated>2011-04-27T14:28:53Z</updated>
18
+ <link rel="alternate" href="http://www.depesz.com/index.php/2011/04/27/find-cheapest-combination-of-rooms-in-hotels/" type="text/html"/>
19
+ <summary xml:base="http://www.depesz.com/" type="html">Today, on Stack Overflow there was interesting question. Generally, given table that looks like this: room | people | price | hotel 1 | 1 | 200 | A 2 | 2 | 99 | A 3 | 3 | 95 | A 4 | 1 | 90 | B 5 | 6 | 300 [...]&lt;img src="http://feeds.feedburner.com/~r/depesz/~4/5LKjrsu7JnQ" height="1" width="1"&gt;</summary>
20
+ <author gr:unknown-author="true">
21
+ <name>(author unknown)</name>
22
+ </author>
23
+ <source gr:stream-id="feed/http://www.depesz.com/index.php/feed/">
24
+ <id>tag:google.com,2005:reader/feed/http://www.depesz.com/index.php/feed/</id>
25
+ <title type="html">select * from depesz where 1 &lt; 41 and 1 &gt; 41 and &quot;public&quot;.sometable = 'asdf';</title>
26
+ <link rel="alternate" href="http://www.depesz.com" type="text/html"/>
27
+ </source>
28
+ <gr:likingUser>06592066937024742113</gr:likingUser>
29
+ <gr:likingUser>07355112106994622540</gr:likingUser>
30
+ <gr:likingUser>09036194539349082984</gr:likingUser>
31
+ </entry>
32
+ </feed>
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0"?>
2
+ <feed xmlns:idx="urn:atom-extension:indexing" xmlns:media="http://search.yahoo.com/mrss/" xmlns:gr="http://www.google.com/schemas/reader/atom/" xmlns="http://www.w3.org/2005/Atom" idx:index="no" gr:dir="ltr">
3
+ <entry gr:crawl-timestamp-msec="1303995951053">
4
+ <id gr:original-id="http://www.depesz.com/?p=2149">tag:google.com,2005:reader/item/d5dee0c34e012ddb</id>
5
+ <category term="user/10212770223479967035/state/com.google/read" scheme="http://www.google.com/reader/" label="read"/>
6
+ <category term="user/10212770223479967035/state/com.google/tracking-item-link-used" scheme="http://www.google.com/reader/" label="tracking-item-link-used"/>
7
+ <category term="user/10212770223479967035/state/com.google/fresh" scheme="http://www.google.com/reader/" label="fresh"/>
8
+ <category term="Uncategorized"/>
9
+ <category term="combinations"/>
10
+ <category term="cte"/>
11
+ <category term="postgresql"/>
12
+ <category term="recursive"/>
13
+ <category term="stackoverflow"/>
14
+ <category term="with recursive"/>
15
+ <title type="html">Find cheapest combination of rooms in hotels &amp; other entries</title>
16
+ <published>2011-04-27T13:28:53Z</published>
17
+ <updated>2011-04-28T14:28:53Z</updated>
18
+ <link rel="alternate" href="http://www.depesz.com/index.php/2011/04/27/find-cheapest-combination-of-rooms-in-hotels/" type="text/html"/>
19
+ <summary xml:base="http://www.depesz.com/" type="html">Today, on Stack Overflow there was interesting question. Generally, given table that looks like this: room | people | price | hotel 1 | 1 | 200 | A 2 | 2 | 99 | A 3 | 3 | 95 | A 4 | 1 | 90 | B 5 | 6 | 300 [...]&lt;img src="http://feeds.feedburner.com/~r/depesz/~4/5LKjrsu7JnQ" height="1" width="1"&gt;</summary>
20
+ <author>
21
+ <name>depesz</name>
22
+ </author>
23
+ <gr:likingUser>06592066937024742113</gr:likingUser>
24
+ <gr:likingUser>07355112106994622540</gr:likingUser>
25
+ <gr:likingUser>09036194539349082984</gr:likingUser>
26
+ <source gr:stream-id="feed/http://www.depesz.com/index.php/feed/">
27
+ <id>tag:google.com,2005:reader/feed/http://www.depesz.com/index.php/feed/</id>
28
+ <title type="html">select * from depesz where 1 &lt; 41 and 1 &gt; 41 and &quot;public&quot;.sometable = 'asdf';</title>
29
+ <link rel="alternate" href="http://www.depesz.com" type="text/html"/>
30
+ </source>
31
+ </entry>
32
+ </feed>
@@ -0,0 +1,3 @@
1
+ require "rubygems"
2
+ require "nokogiri"
3
+ require "google_reader"
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: google_reader
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 1.0.0
6
+ platform: ruby
7
+ authors:
8
+ - "Fran\xC3\xA7ois Beausoleil"
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-03 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rest-client
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 1.6.1
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: 1.4.4
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: ruby-debug19
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id004
60
+ description: Access Google Reader in a quick and simple way, using plain Ruby.
61
+ email:
62
+ - francois@teksol.info
63
+ executables: []
64
+
65
+ extensions: []
66
+
67
+ extra_rdoc_files: []
68
+
69
+ files:
70
+ - .gitignore
71
+ - Gemfile
72
+ - LICENSE
73
+ - README.textile
74
+ - autotest/discover.rb
75
+ - google_reader.gemspec
76
+ - lib/google_reader.rb
77
+ - lib/google_reader/client.rb
78
+ - lib/google_reader/entry.rb
79
+ - lib/google_reader/version.rb
80
+ - spec/client_spec.rb
81
+ - spec/entry_spec.rb
82
+ - spec/fixtures/entry-with-no-likes.xml
83
+ - spec/fixtures/entry-with-no-original-id.xml
84
+ - spec/fixtures/entry-with-unknown-author.xml
85
+ - spec/fixtures/entry.xml
86
+ - spec/spec_helper.rb
87
+ has_rdoc: true
88
+ homepage: https://github.com/francois/google_reader
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
+ version: "0"
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: "0"
108
+ requirements: []
109
+
110
+ rubyforge_project:
111
+ rubygems_version: 1.6.2
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: An unofficial Ruby client for Google Reader
115
+ test_files:
116
+ - spec/client_spec.rb
117
+ - spec/entry_spec.rb
118
+ - spec/fixtures/entry-with-no-likes.xml
119
+ - spec/fixtures/entry-with-no-original-id.xml
120
+ - spec/fixtures/entry-with-unknown-author.xml
121
+ - spec/fixtures/entry.xml
122
+ - spec/spec_helper.rb