GoogleReaderApiUniq 0.3.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,48 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "GoogleReaderApiUniq"
8
+ s.version = "0.3.7"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Toon Willems", "Ivan Kasatenko"]
12
+ s.date = "2012-06-05"
13
+ s.description = "a google reader api (unofficial) written in ruby"
14
+ s.email = "sky.31338@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "README.mdown"
17
+ ]
18
+ s.files = [
19
+ "GoogleReaderApiUniq.gemspec",
20
+ "License",
21
+ "README.mdown",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "lib/google-reader-api/api.rb",
25
+ "lib/google-reader-api/cache.rb",
26
+ "lib/google-reader-api/entry.rb",
27
+ "lib/google-reader-api/feed.rb",
28
+ "lib/google-reader-api/google_login.rb",
29
+ "lib/google-reader-api/rss_utils.rb",
30
+ "lib/google-reader-api/subscription_list.rb",
31
+ "lib/google-reader-api/user.rb",
32
+ "lib/google_reader_api.rb"
33
+ ]
34
+ s.homepage = "http://github.com/SkyWriter/GoogleReaderAPI"
35
+ s.require_paths = ["lib"]
36
+ s.rubygems_version = "1.8.24"
37
+ s.summary = "a google reader api (unofficial) written in ruby"
38
+
39
+ if s.respond_to? :specification_version then
40
+ s.specification_version = 3
41
+
42
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
43
+ else
44
+ end
45
+ else
46
+ end
47
+ end
48
+
data/License ADDED
@@ -0,0 +1,21 @@
1
+ the MIT License
2
+
3
+ Copyright (c) 2010 Toon Willems
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.mdown ADDED
@@ -0,0 +1,62 @@
1
+ GoogleReaderAPI
2
+ ===============
3
+
4
+ A Google Reader api. Programmed in ruby. This is an unofficial api.
5
+ Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
6
+
7
+ Usage
8
+ -----
9
+
10
+ If you would like to implement your own methods, you can quite easily use
11
+ the api class provided.
12
+
13
+ api = GoogleReaderApi::Api.new email, pass
14
+
15
+ and then you can perform requests using the `get_link` and `post_link` methods.
16
+
17
+ # this will give the user info.
18
+ api.get_link "api/0/user-info"
19
+
20
+ # this will add a feed
21
+ api.post_link 'api/0/subscription/edit', :s => "feed/#{url}" , :ac => :subscribe
22
+
23
+ The following methods all use the `Api` class, so there is no magic
24
+ involved.
25
+
26
+ # password should be asked by the app using the api
27
+ # you probably don't want to type that in cleartext here
28
+ user = GoogleReaderApi::User.new 'willemstoon@gmail.com' , password
29
+
30
+ you can access your feeds from there
31
+
32
+ user.feeds
33
+
34
+ which will return an array of GoogleReader::Feed objects.
35
+ then you can get the unread items, the read items, ...
36
+
37
+ hn = user.feeds.find {|feed| feed.title =~ /hacker news/i }
38
+ # return 3 unread items (ordered by date)
39
+ hn.unread_items(3)
40
+ # return all the read items
41
+ hn.read_items
42
+ # all the starred items
43
+ hn.starred_items
44
+
45
+ you get the idea.
46
+ you can like items, star items, and mark them as read or unread.
47
+
48
+ hn.all_unread_items.each {|item| item.toggle_read}
49
+
50
+ subscribing to new feeds is also easy
51
+
52
+ user.subscriptions.add "any feed url here"
53
+
54
+ unsubscribing is best done via `remove_if` method
55
+
56
+ user.subscriptions.remove_if {|feed| feed.title =~ /hacker news/i}
57
+
58
+ Todo
59
+ ----
60
+
61
+ * provide nicer convenience methods. (user.subscriptions.feeds is not a good way to access your feeds).
62
+ * labels should be part of the api.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "GoogleReaderApiUniq"
5
+ gemspec.summary = "a google reader api (unofficial) written in ruby"
6
+ gemspec.description = "a google reader api (unofficial) written in ruby"
7
+ gemspec.email = "sky.31338@gmail.com"
8
+ gemspec.homepage = "http://github.com/SkyWriter/GoogleReaderAPI"
9
+ gemspec.authors = ["Toon Willems", "Ivan Kasatenko"]
10
+ end
11
+ rescue LoadError
12
+ puts "Jeweler not available. Install it with: gem install jeweler"
13
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.7
@@ -0,0 +1,98 @@
1
+ module GoogleReaderApi
2
+
3
+ class Api
4
+
5
+ require "cgi"
6
+ require "net/https"
7
+ require "uri"
8
+
9
+ BASE_URL = "http://www.google.com/reader/"
10
+
11
+ # specify either the :email and :password or the :auth token you got in the past
12
+ #
13
+ # [:email] the user's email address for login purposes
14
+ #
15
+ # [:password] the user's password for login purposes
16
+ #
17
+ # [:auth] the auth token you got from a previous authentication request
18
+ # if you provide this you do not need to provide the email and password
19
+ def initialize(options)
20
+ if options[:auth]
21
+ @auth = options[:auth]
22
+ else
23
+ request_auth(options[:email],options[:password])
24
+ end
25
+ @cache = GoogleReaderApi::Cache.new(2)
26
+ end
27
+
28
+ # do a get request to the link
29
+ # args is a hash of values that should be used in the request
30
+ def get_link(link,args={})
31
+ link = BASE_URL + link
32
+ get_request(link,args)
33
+ end
34
+
35
+ def post_link(link,args={})
36
+ link = BASE_URL + link
37
+ post_request(link,args)
38
+ end
39
+
40
+ def cached_unread_count
41
+ @cache['unread-count'] ||= get_link 'api/0/unread-count', :output => :json
42
+ end
43
+
44
+ private
45
+
46
+ # url as a string
47
+ # the post data as a hash
48
+ def post_request(url,args)
49
+ uri = URI.parse(url)
50
+ req = Net::HTTP::Post.new(uri.path)
51
+ req.set_form_data(args)
52
+ request(uri,req)
53
+ end
54
+
55
+ # the url as a string and the args as a hash
56
+ # e.g. :allcomments => true etc...
57
+ def get_request(url,args)
58
+ uri = URI.parse url
59
+
60
+ # ck is the current unix timestamp
61
+ args[:ck] = Time.now.to_i unless args[:ck]
62
+
63
+ req = Net::HTTP::Get.new("#{uri.path}?#{argument_string(args)}")
64
+ request(uri,req)
65
+ end
66
+
67
+ def request(uri,request)
68
+ # add the cookie to the http header
69
+ request.add_field('Authorization',"GoogleLogin auth=#{auth}")
70
+ res = Net::HTTP.start(uri.host,uri.port) do |http|
71
+ http.request(request)
72
+ end
73
+ # TODO: use better exception
74
+ if res.code != '200'
75
+ p res.body
76
+ raise "something went wrong"
77
+ end
78
+ res.body
79
+ end
80
+
81
+ # returns the argumentstring based on the hash it is given
82
+ def argument_string(args)
83
+ args.to_a.map { |v| v.join '=' }.join('&')
84
+ end
85
+
86
+ def auth
87
+ @auth
88
+ end
89
+
90
+ def request_auth(email,password)
91
+ login = GoogleLogin::ClientLogin.new :service => 'reader', :source => 'nudded-greader-0.1'
92
+ login.authenticate email, password
93
+ @auth = login.auth
94
+ end
95
+
96
+ end
97
+
98
+ end
@@ -0,0 +1,30 @@
1
+ module GoogleReaderApi
2
+ class Cache
3
+
4
+ def initialize(time)
5
+ @time = time
6
+ @hash = {}
7
+ end
8
+
9
+ def [](key)
10
+ if cached?(key)
11
+ @hash[key].first
12
+ else
13
+ @hash[key] = nil
14
+ end
15
+ end
16
+
17
+ def []=(key,value)
18
+ @hash[key] = [value,Time.now.to_i]
19
+ end
20
+
21
+ def cached?(key)
22
+ if @hash[key]
23
+ Time.now.to_i - @hash[key][1] < @time
24
+ else
25
+ false
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ module GoogleReaderApi
2
+ class Entry
3
+
4
+ attr_reader :entry
5
+
6
+ def initialize(api,entry)
7
+ @api, @entry = api, entry
8
+ end
9
+
10
+ def toggle_read
11
+ edit_tag 'user/-/state/com.google/read'
12
+ end
13
+
14
+ def toggle_like
15
+ edit_tag 'user/-/state/com.google/like'
16
+ end
17
+
18
+ def toggle_star
19
+ edit_tag 'user/-/state/com.google/starred'
20
+ end
21
+
22
+ def to_s
23
+ "<<Entry: #{@entry.title.content} >>"
24
+ end
25
+
26
+ private
27
+
28
+ def edit_tag(tag_identifier)
29
+ token = @api.get_link("api/0/token")
30
+ @api.post_link "api/0/edit-tag" , :a => tag_identifier ,
31
+ :s => entry.parent.id.content.to_s.scan(/feed\/.*/) ,
32
+ :i => entry.id.content.to_s,
33
+ :T => token
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,76 @@
1
+ module GoogleReaderApi
2
+
3
+ class Feed
4
+
5
+ include GoogleReaderApi::RssUtils
6
+
7
+ attr_reader :url, :title
8
+
9
+ def initialize(hash,api)
10
+ # strip the first 5 characters of the url (they are 'feed/')
11
+ @url = hash['id'][5..-1]
12
+ @title = hash['title']
13
+ # no idea what this is used for
14
+ @sortid = hash['sortid']
15
+ @categories = hash['categories']
16
+ @firstitemmsec = hash['firstitemmsec']
17
+
18
+ @api = api
19
+ end
20
+
21
+ def unsubscribe
22
+ @api.post_link 'api/0/subscription/edit' , :s => "feed/#{url}",
23
+ :ac => :unsubscribe
24
+ end
25
+
26
+ def unread_count
27
+ entry = JSON[@api.cached_unread_count]['unreadcounts'].find {|h| h['id'] == "feed/#{url}"}
28
+ entry ? entry['count'] : 0
29
+ end
30
+
31
+ # return count read items
32
+ def read_items(count=20)
33
+ create_entries get_user_items('read',:n => count)
34
+ end
35
+
36
+ def starred_items(count=20)
37
+ create_entries get_user_items('starred',:n => count)
38
+ end
39
+
40
+ # return the number of specified items. (read or not)
41
+ def items(count = 20)
42
+ create_entries get_feed_items(:n => count)
43
+ end
44
+
45
+ # return all the unread items in an array
46
+ def all_unread_items
47
+ unread_count > 0 ? unread_items(unread_count) : []
48
+ end
49
+
50
+ # will return an array of GoogleReader::Feed::Entry objects.
51
+ # will try to return the amount of unread items you specify. unless there are no more.
52
+ # will return 20 unread items by default.
53
+ def unread_items(count = 20)
54
+ create_entries get_feed_items(:n => count,:xt => 'user/-/state/com.google/read')
55
+ end
56
+
57
+ def inspect
58
+ to_s
59
+ end
60
+
61
+ def to_s
62
+ "<<Feed: #{title} url:#{url}>>"
63
+ end
64
+
65
+ private
66
+
67
+ def get_user_items(state,args={})
68
+ @api.get_link "atom/user/-/state/com.google/#{state}" , args
69
+ end
70
+
71
+ def get_feed_items(args={})
72
+ @api.get_link "atom/feed/#{CGI.escape(url)}" , args
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,139 @@
1
+ require "net/https"
2
+ require "uri"
3
+
4
+ module GoogleLogin
5
+
6
+ # == ClientLogin
7
+ #
8
+ # Use this Class to get an auth-token
9
+ class ClientLogin
10
+
11
+ # Base Exception class
12
+ LoginError = Class.new Exception
13
+
14
+ # All the possible exceptions
15
+ [
16
+ "BadAuthentication",
17
+ "NotVerified",
18
+ "TermsNotAgreed",
19
+ "CaptchaRequired",
20
+ "Unknown",
21
+ "AccountDeleted",
22
+ "AccountDisabled",
23
+ "ServiceDisabled",
24
+ "ServiceUnavailable",
25
+ ].each do |const|
26
+ const_set const, Class.new(LoginError)
27
+ end
28
+
29
+ DEFAULTS = {
30
+ :accountType => 'HOSTED_OR_GOOGLE' ,
31
+ :source => 'companyName-applicationName-versionID',
32
+ :service => 'service-identifier'
33
+ }
34
+
35
+ attr_reader :auth, :sid, :lsid, :captcha_url
36
+
37
+ # specify the :service, :source and optionally :accountType
38
+ #
39
+ # [:service] the service identifier, check the google api documentation.
40
+ #
41
+ # [:source] the name of your application. String should be in the form
42
+ # "companyName-applicationName-versionID".
43
+ #
44
+ # [:accountType] one of the following values:
45
+ # "GOOGLE", "HOSTED", "HOSTED_OR_GOOGLE" (default if none
46
+ # given)
47
+ def initialize(arghash = {})
48
+ @options = DEFAULTS.merge arghash
49
+ end
50
+
51
+ # authenticate a user, which sets the auth, sid and lsid instance_variables
52
+ # if you provide a block, it will be called with a captcha url if google
53
+ # forces you to answer the captcha. Make sure you return the anwer in the block.
54
+ #
55
+ # if no block is given, this will raise a CaptchaRequired error.
56
+ # you can rescue them and show the url via the captcha_url method.
57
+ #
58
+ # you can then call authenticate and as 3rd parameter you provide the
59
+ # captcha answer.
60
+ #
61
+ # all Exceptions this raises are subclasses of ClientLogin::LoginError.
62
+ # so make sure you handle them.
63
+ #
64
+ # This is a list of all the possible errors and their meaning
65
+ # Error code:: Description
66
+ # BadAuthentication:: The login request used a username or password that is not recognized.
67
+ # NotVerified:: The account email address has not been verified. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.
68
+ # TermsNotAgreed:: The user has not agreed to terms. The user will need to access their Google account directly to resolve the issue before logging in using a non-Google application.
69
+ # CaptchaRequired:: A CAPTCHA is required. (A response with this error code will also contain an image URL and a CAPTCHA token.)
70
+ # Unknown:: The error is unknown or unspecified; the request contained invalid input or was malformed.
71
+ # AccountDeleted:: The user account has been deleted.
72
+ # AccountDisabled:: The user account has been disabled.
73
+ # ServiceDisabled:: The user's access to the specified service has been disabled. (The user account may still be valid.)
74
+ # ServiceUnavailable:: The service is not available; try again later.
75
+ def authenticate(username, password, captcha_response = nil)
76
+ @options[:Email], @options[:Passwd] = username, password
77
+ # set logincaptcha, captchatoken will already be set
78
+ @options[:logincaptcha] = captcha_response if captcha_response
79
+
80
+ parse_response perform_request
81
+
82
+ rescue CaptchaRequired
83
+ if block_given?
84
+ @options[:logincaptcha] = yield captcha_url
85
+ retry
86
+ else
87
+ raise CaptchaRequired
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def perform_request
94
+ request = Net::HTTP::Post.new '/accounts/ClientLogin'
95
+ request.form_data = @options
96
+
97
+ https = Net::HTTP.new 'www.google.com', 443
98
+ https.use_ssl = true
99
+
100
+ https.request request
101
+ end
102
+
103
+ def parse_body(response_body)
104
+ response_body.scan(/(\w+)=(.+)\n/).each do |key, value|
105
+ instance_variable_set "@#{key.downcase}" , value
106
+ end
107
+ end
108
+
109
+ def parse_response(response)
110
+ if response.code_type == Net::HTTPOK
111
+ parse_body response.body
112
+ else
113
+ handle_error response.body
114
+ end
115
+ end
116
+
117
+
118
+ def handle_error(response_body)
119
+ error_message = response_body.match(/Error=(\w+)\n/)[1].strip
120
+
121
+ if error_message == "CaptchaRequired"
122
+ @options[:logintoken] = response_body.match(/CaptchaToken=(.+)\n/)[1]
123
+ self.captcha_url = response_body.match(/CaptchaUrl=(.+)\n/)[1]
124
+ end
125
+
126
+ raise_error_class error_message
127
+ end
128
+
129
+ def raise_error_class(error_message)
130
+ raise self.class.const_get error_message
131
+ end
132
+
133
+ def captcha_url=(url)
134
+ @captcha_url = "http://www.google.com/accounts/" << url
135
+ end
136
+
137
+ end
138
+
139
+ end
@@ -0,0 +1,14 @@
1
+ module GoogleReaderApi
2
+ module RssUtils
3
+
4
+ require "rss/parser"
5
+ require "rss/atom"
6
+
7
+ private
8
+
9
+ def create_entries(atom_feed)
10
+ RSS::Parser.parse(atom_feed.force_encoding('utf-8')).entries.map {|e| GoogleReaderApi::Entry.new(@api,e) }
11
+ end
12
+
13
+ end
14
+ end
@@ -0,0 +1,74 @@
1
+ module GoogleReaderApi
2
+
3
+ class SubscriptionList
4
+
5
+ require "cgi"
6
+
7
+ include GoogleReaderApi::RssUtils
8
+ include Enumerable
9
+
10
+ def initialize(api)
11
+ @api = api
12
+ update
13
+ end
14
+
15
+ # returns the total unread count
16
+ def total_unread
17
+ inject(0) {|i,j| i+j.unread_count}
18
+ end
19
+
20
+ # returns a hash
21
+ # with following pattern:
22
+ # feed => unread_count
23
+ def unread_count
24
+ hash = {}
25
+ each { |feed| hash[feed] = feed.unread_count }
26
+ hash
27
+ end
28
+
29
+ # yield each feed, if you return true
30
+ # the feed will be removed from your subscriptions
31
+ def remove_if
32
+ each { |feed| feed.unsubscribe if yield feed}
33
+ update
34
+ end
35
+
36
+ # subscribe to the given url
37
+ # google will set the title for you
38
+ def add(url)
39
+ @api.post_link 'api/0/subscription/edit', :s => "feed/#{url}" , :ac => :subscribe
40
+ update
41
+ end
42
+
43
+ # return an array of unread items
44
+ def unread_items
45
+ feeds.map(&:all_unread_items)
46
+ end
47
+
48
+ # will return an array of entries with label
49
+ def items_with_label(label)
50
+ create_entries(@api.get_link "atom/user/-/label/#{CGI::escape label}")
51
+ end
52
+
53
+ def feeds
54
+ @feeds
55
+ end
56
+
57
+ def each
58
+ @feeds.each {|feed| yield feed}
59
+ end
60
+
61
+ def update
62
+ fetch_list
63
+ end
64
+
65
+ private
66
+
67
+ def fetch_list
68
+ json = JSON[@api.get_link 'api/0/subscription/list', :output => :json]['subscriptions']
69
+ @feeds = json.map {|hash| GoogleReaderApi::Feed.new(hash,@api) }
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,34 @@
1
+ module GoogleReaderApi
2
+
3
+ class User
4
+
5
+ require "json"
6
+ # maybe someone would like to access the api for a user
7
+ attr_reader :api
8
+
9
+ # specify either the :email and :password or the :auth token you got in the past
10
+ #
11
+ # [:email] the user's email address for login purposes
12
+ #
13
+ # [:password] the user's password for login purposes
14
+ #
15
+ # [:auth] the auth token you got from a previous authentication request
16
+ # if you provide this you do not need to provide the email and password
17
+ def initialize(options)
18
+ @api = GoogleReaderApi::Api::new options
19
+ end
20
+
21
+ def info
22
+ JSON[api.get_link "api/0/user-info"]
23
+ end
24
+
25
+ def subscriptions
26
+ @subscriptions ||= GoogleReaderApi::SubscriptionList.new @api
27
+ end
28
+
29
+ def feeds
30
+ subscriptions.feeds
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,8 @@
1
+ require "google-reader-api/rss_utils"
2
+ require "google-reader-api/api"
3
+ require "google-reader-api/cache"
4
+ require "google-reader-api/entry"
5
+ require "google-reader-api/feed"
6
+ require "google-reader-api/subscription_list"
7
+ require "google-reader-api/user"
8
+ require "google-reader-api/google_login"
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: GoogleReaderApiUniq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.7
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Toon Willems
9
+ - Ivan Kasatenko
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-06-05 00:00:00.000000000 Z
14
+ dependencies: []
15
+ description: a google reader api (unofficial) written in ruby
16
+ email: sky.31338@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files:
20
+ - README.mdown
21
+ files:
22
+ - GoogleReaderApiUniq.gemspec
23
+ - License
24
+ - README.mdown
25
+ - Rakefile
26
+ - VERSION
27
+ - lib/google-reader-api/api.rb
28
+ - lib/google-reader-api/cache.rb
29
+ - lib/google-reader-api/entry.rb
30
+ - lib/google-reader-api/feed.rb
31
+ - lib/google-reader-api/google_login.rb
32
+ - lib/google-reader-api/rss_utils.rb
33
+ - lib/google-reader-api/subscription_list.rb
34
+ - lib/google-reader-api/user.rb
35
+ - lib/google_reader_api.rb
36
+ homepage: http://github.com/SkyWriter/GoogleReaderAPI
37
+ licenses: []
38
+ post_install_message:
39
+ rdoc_options: []
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 1.8.24
57
+ signing_key:
58
+ specification_version: 3
59
+ summary: a google reader api (unofficial) written in ruby
60
+ test_files: []