geocaching 0.5.0 → 0.6.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.
Files changed (49) hide show
  1. data/README.markdown +47 -21
  2. data/geocaching.gemspec +3 -0
  3. data/lib/geocaching.rb +20 -6
  4. data/lib/geocaching/cache.rb +237 -94
  5. data/lib/geocaching/cache_type.rb +17 -13
  6. data/lib/geocaching/http.rb +12 -16
  7. data/lib/geocaching/log.rb +67 -46
  8. data/lib/geocaching/log_type.rb +17 -13
  9. data/lib/geocaching/my_logs.rb +22 -16
  10. data/lib/geocaching/pocket_query.rb +213 -0
  11. data/lib/geocaching/user.rb +46 -69
  12. data/lib/geocaching/version.rb +3 -1
  13. data/lib/geocaching/watchlist.rb +106 -0
  14. data/spec/cache/ape.rb +4 -0
  15. data/spec/cache/cito.rb +4 -0
  16. data/spec/cache/earthcache.rb +4 -0
  17. data/spec/cache/event.rb +4 -0
  18. data/spec/cache/letterbox.rb +4 -0
  19. data/spec/cache/lfevent.rb +4 -0
  20. data/spec/cache/locationless.rb +4 -0
  21. data/spec/cache/megaevent.rb +4 -0
  22. data/spec/cache/multi.rb +4 -0
  23. data/spec/cache/mystery.rb +4 -0
  24. data/spec/cache/traditional.rb +4 -0
  25. data/spec/cache/virtual.rb +4 -0
  26. data/spec/cache/webcam.rb +4 -0
  27. data/spec/cache/wherigo.rb +4 -0
  28. data/spec/cache_spec.rb +7 -1
  29. data/spec/log/announcement.rb +1 -1
  30. data/spec/log/archive.rb +1 -1
  31. data/spec/log/attended.rb +1 -1
  32. data/spec/log/coords_update.rb +1 -1
  33. data/spec/log/disable.rb +1 -1
  34. data/spec/log/dnf.rb +1 -1
  35. data/spec/log/enable.rb +1 -1
  36. data/spec/log/found.rb +1 -1
  37. data/spec/log/needs_archived.rb +1 -1
  38. data/spec/log/needs_maintenance.rb +1 -1
  39. data/spec/log/note.rb +1 -1
  40. data/spec/log/owner_maintenance.rb +1 -1
  41. data/spec/log/publish.rb +1 -1
  42. data/spec/log/retract.rb +1 -1
  43. data/spec/log/reviewer_note.rb +1 -1
  44. data/spec/log/unarchive.rb +1 -1
  45. data/spec/log/webcam_photo_taken.rb +1 -1
  46. data/spec/log/will_attend.rb +1 -1
  47. data/spec/log_spec.rb +7 -1
  48. metadata +22 -6
  49. data/lib/geocaching/pq.rb +0 -72
@@ -1,48 +1,48 @@
1
+ # encoding: utf-8
2
+
1
3
  module Geocaching
2
- # This class provides access to the logs the user currently logged in
3
- # has written.
4
+ # The {MyLogs} class provides access to the logs the user
5
+ # you’re logged in with has written.
6
+ #
7
+ # This class does caching. If you want to override the cached information,
8
+ # call the {#fetch} method manually.
9
+ #
10
+ # == Usage
4
11
  #
5
12
  # mylogs = Geocaching::MyLogs.fetch
6
13
  # puts "I've written #{mylogs.size} logs."
7
14
  class MyLogs
8
- # Create a new instance and call {fetch} afterwards.
15
+ # Creates a new instance and calls {#fetch} afterwards.
9
16
  #
10
17
  # @return [Geocaching::MyLogs]
11
- # @raise [Geocaching::LoginError] Need to be logged in to fetch own logs
12
- # @raise [Geocaching::TimeoutError] Timeout hit
13
- # @raise [Geocaching::HTTPError] HTTP request failed
14
18
  def self.fetch
15
19
  mylogs = new
16
20
  mylogs.fetch
17
21
  mylogs
18
22
  end
19
23
 
20
- # Fetch logs from geocaching.com.
24
+ # Fetches logs from geocaching.com.
21
25
  #
22
26
  # @return [void]
23
- # @raise [Geocaching::LoginError] Need to be logged in to fetch own logs
24
- # @raise [Geocaching::TimeoutError] Timeout hit
25
- # @raise [Geocaching::HTTPError] HTTP request failed
26
27
  def fetch
27
- raise LoginError, "Need to be logged in to fetch your logs" unless HTTP.loggedin?
28
+ raise LoginError unless HTTP.loggedin?
28
29
 
29
30
  resp, @data = HTTP.get(path)
30
31
  @doc = Nokogiri::HTML.parse(@data)
31
32
  end
32
33
 
33
- # Return whether logs have successfully been fetched from
34
- # geocaching.com.
34
+ # Returns whether logs have successfully been fetched
35
+ # from geocaching.com.
35
36
  #
36
- # @return [Boolean]
37
+ # @return [Boolean] Have logs been fetched?
37
38
  def fetched?
38
39
  @data and @doc
39
40
  end
40
41
 
41
- # Return an array of logs the user you’re logged in with has
42
+ # Returns an array of logs the user you’re logged in with has
42
43
  # written.
43
44
  #
44
45
  # @return [Array<Geocaching::Log>]
45
- # @raise [Geocaching::NotFetchedError] Need to call {fetch} first.
46
46
  def logs
47
47
  @logs ||= begin
48
48
  raise NotFetchedError unless fetched?
@@ -72,10 +72,16 @@ module Geocaching
72
72
 
73
73
  private
74
74
 
75
+ # Returns the HTTP request path.
76
+ #
77
+ # @return [String] HTTP request path
75
78
  def path
76
79
  "/my/logs.aspx?s=1"
77
80
  end
78
81
 
82
+ # Extracts information from a HTML table row node.
83
+ #
84
+ # @return [Hash] Extracted information
79
85
  def extract_info_from_row(row)
80
86
  info = {}
81
87
 
@@ -0,0 +1,213 @@
1
+ # encoding: utf-8
2
+
3
+ module Geocaching
4
+ # The {PocketQuery} class represents a Pocket Query.
5
+ #
6
+ # == Usage
7
+ #
8
+ # === Downloading a Pocket Query
9
+ #
10
+ # avail_pqs = Geocaching::PocketQuery.ready_for_download
11
+ # pq = avail_pqs.first
12
+ #
13
+ # pq.download
14
+ # pq.parse
15
+ #
16
+ # puts "The PQ contains #{pq.caches.size} caches."
17
+ #
18
+ # === Reading a Pocket Query from file
19
+ #
20
+ # pq = Geocaching::PocketQuery.from_file("123456.gpx")
21
+ # puts "The PQ’s name is #{pq.name}."
22
+ #
23
+ class PocketQuery
24
+ # Reads a Pocket Query from file and parses it. The file may either be a
25
+ # GPX file or a zip file containing the GPX file.
26
+ #
27
+ # @param [String] file File name
28
+ # @return [Geocaching::PocketQuery] Pocket Query
29
+ def self.from_file(file)
30
+ pq = new(:raw => File.read(file))
31
+ pq.parse
32
+ pq
33
+ end
34
+
35
+ # Returns an array of Pocket Queries that are ready for download. You
36
+ # need to be logged in and a Premium Member to use this feature.
37
+ #
38
+ # @return [Array<Geocaching::PocketQuery>]
39
+ # Array of Pocket Queries ready for download
40
+ def self.ready_for_download
41
+ raise LoginError unless HTTP.loggedin?
42
+
43
+ pqs = []
44
+ resp, data = HTTP.get("/pocket/default.aspx")
45
+ doc = Nokogiri::HTML.parse(data)
46
+
47
+ elements = doc.search("table#uxOfflinePQTable")
48
+ raise ParseError unless elements.size == 1
49
+
50
+ elements = elements.first.search("tr")[1..-1]
51
+
52
+ elements.each do |row|
53
+ # Name
54
+ elements = row.search("td:nth-child(3) > a")
55
+ next unless elements.size == 1
56
+ name = elements.first.text.strip
57
+
58
+ # GUID
59
+ if elements.first["href"] =~ /g=([a-f0-9-]{36})/
60
+ guid = $1
61
+ else
62
+ next
63
+ end
64
+
65
+ # Number of results
66
+ elements = row.search("td:nth-child(5)")
67
+ next unless elements.size == 1
68
+ results = elements.first.text.to_i
69
+
70
+ # Last Generated date
71
+ elements = row.search("td:nth-child(6)")
72
+ next unless elements.size == 1
73
+ if elements.first.text =~ /(\d{2})\/(\d{2})\/(\d{4})/
74
+ date = Time.mktime($3, $1, $2)
75
+ end
76
+
77
+ if guid and name and results and date
78
+ pq = PocketQuery.new \
79
+ :guid => guid,
80
+ :name => name,
81
+ :results => results,
82
+ :last_generated_at => date
83
+ pqs << pq
84
+ end
85
+ end
86
+
87
+ pqs
88
+ end
89
+
90
+ class << self
91
+ alias ready ready_for_download
92
+ end
93
+
94
+ # Adds the ”My Finds“ Pocket Query to the Queue.
95
+ #
96
+ # @return [void]
97
+ def self.schedule_my_finds
98
+ resp, data = HTTP.post("/pocket/default.aspx", {
99
+ "ctl00$ContentBody$PQListControl1$btnScheduleNow" => "Yes"
100
+ })
101
+
102
+ !!(data =~ /Your 'My Finds' Pocket Query has been scheduled to run./)
103
+ end
104
+
105
+ # Creates a new instance. The following attributes may be specified
106
+ # as parameters:
107
+ #
108
+ # * +:guid+ — The Pocket Query’s GUID
109
+ #
110
+ # @param [Hash] attributes A hash of attributes
111
+ # @raise [ArgumentError] Trying to set an unknown attribute
112
+ def initialize(attributes = {})
113
+ attributes.each do |key, value|
114
+ if [:guid, :name, :results, :last_generated_at, :raw].include?(key)
115
+ if key == :last_generated_at and not value.kind_of?(Time)
116
+ raise TypeError, \
117
+ "Attribute `last_generated_at' must be an instance of Time"
118
+ end
119
+
120
+ if key == :results and not value.kind_of?(Fixnum)
121
+ raise TypeError, "Attribute `results' must be a number"
122
+ end
123
+
124
+ instance_variable_set("@#{key}", value)
125
+ else
126
+ raise ArgumentError, "Trying to set unknown attribute `#{key}'"
127
+ end
128
+ end
129
+ end
130
+
131
+ # Returns the Pocket Query’s GUID.
132
+ #
133
+ # @return [String] GUID
134
+ def guid
135
+ @guid
136
+ end
137
+
138
+ # Returns the Pocket Query’s name.
139
+ #
140
+ # @return [String] Name
141
+ def name
142
+ @name ||= begin
143
+ @gpx.xpath("/xmlns:gpx/xmlns:name").content if @gpx
144
+ end
145
+ end
146
+
147
+ # Returns the date the Pocket Query was last generated at.
148
+ #
149
+ # @return [Time] Last generated date
150
+ def last_generated_at
151
+ @last_generated_at ||= begin
152
+ if @gpx
153
+ # ...
154
+ end
155
+ end
156
+ end
157
+
158
+ # Returns the number of results inside the Pocket Query.
159
+ #
160
+ # @return [Fixnum] Number of results
161
+ def results
162
+ @results ||= begin
163
+ @caches.size if @caches
164
+ end
165
+ end
166
+
167
+ # Returns the Pocket Query’s raw data. This may be either zipped data
168
+ # or a GPX document.
169
+ #
170
+ # @return [String] Raw data (zip or gpx)
171
+ def raw
172
+ @raw || begin
173
+ raise NotFetchedError, "You have to download the Pocket Query first"
174
+ end
175
+ end
176
+
177
+ # Returns the Nokogiri XML document object for the GPX tree.
178
+ #
179
+ # @return [Nokogiri::XML::Document] Nokogiri XML document
180
+ def gpx
181
+ @gpx || begin
182
+ raise NotFetchedError, "You have to download the Pocket Query first"
183
+ end
184
+ end
185
+
186
+ # Downloads and parses the Pocket Query.
187
+ #
188
+ # @return [void]
189
+ def download
190
+ raise LoginError unless HTTP.loggedin?
191
+
192
+ resp, @raw = HTTP.get("/pocket/downloadpq.ashx?g=#{guid}")
193
+ parse
194
+ end
195
+
196
+ private
197
+
198
+ # Parses the Pocket Query’s GPX document.
199
+ #
200
+ # @return [void]
201
+ def parse
202
+ unless @raw
203
+ raise NotFetchedError, "You have to download the Pocket Query first"
204
+ end
205
+
206
+ if @raw[0..2] == "PK"
207
+ raise "Support for zip Pocket Query isn’t implemented yet"
208
+ else
209
+ @gpx = Nokogiri::XML.parse(@raw)
210
+ end
211
+ end
212
+ end
213
+ end
@@ -1,18 +1,23 @@
1
+ # encoding: utf-8
2
+
1
3
  require "time"
2
4
 
3
5
  module Geocaching
4
- # This class represents an user on geocaching.com.
6
+ # The {User} class represents a user on geocaching.com.
7
+ #
8
+ # This class does caching. If you want to override the cached information,
9
+ # call the {#fetch} method manually.
10
+ #
11
+ # == Usage
5
12
  #
6
13
  # user = Geocaching::User.fetch(:guid => "...")
7
14
  # puts user.name #=> "Jack"
8
15
  class User
9
- # Create a new instance and call the {fetch} method afterwards.
10
- # +:guid+ must be giveb as an attribute.
16
+ # Creates a new instance and calls the {fetch} method afterwards.
17
+ # +:guid+ must be given as an attribute.
11
18
  #
12
19
  # @param [Hash] attributes A hash of attributes
13
20
  # @raise [ArgumentError] No GUID given
14
- # @raise [Geocaching::TimeoutError] Timeout hit
15
- # @raise [Geocaching::HTTPError] HTTP request failed
16
21
  # @return [Geocaching::User]
17
22
  def self.fetch(attributes)
18
23
  user = new(attributes)
@@ -20,10 +25,11 @@ module Geocaching
20
25
  user
21
26
  end
22
27
 
23
- # Create a new instance. The following attributes may be specified
28
+ # Creates a new instance. The following attributes may be specified
24
29
  # as parameters:
25
30
  #
26
31
  # * +:guid+ — The user’s Globally Unique Identifier (GUID)
32
+ # * +:name+ — The user‘s name
27
33
  #
28
34
  # @param [Hash] attributes A hash of attributes
29
35
  # @raise [ArgumentError] Trying to set an unknown attribute
@@ -31,7 +37,7 @@ module Geocaching
31
37
  @data, @doc, @guid = nil, nil, nil
32
38
 
33
39
  attributes.each do |key, value|
34
- if [:guid].include?(key)
40
+ if [:guid, :name].include?(key)
35
41
  instance_variable_set("@#{key}", value)
36
42
  else
37
43
  raise ArgumentError, "Trying to set unknown attribute `#{key}'"
@@ -39,12 +45,10 @@ module Geocaching
39
45
  end
40
46
  end
41
47
 
42
- # Fetch user information from geocaching.com.
48
+ # Fetches user information from geocaching.com.
43
49
  #
44
50
  # @return [void]
45
51
  # @raise [ArgumentError] No GUID given
46
- # @raise [Geocaching::TimeoutError] Timeout hit
47
- # @raise [Geocaching::HTTPError] HTTP request failed
48
52
  def fetch
49
53
  raise ArgumentError, "No GUID given" unless @guid
50
54
 
@@ -52,27 +56,24 @@ module Geocaching
52
56
  @doc = Nokogiri::HTML.parse(@data)
53
57
  end
54
58
 
55
- # Return whether user information have successfully been fetched
59
+ # Returns whether user information have successfully been fetched
56
60
  # from geocaching.com.
57
61
  #
58
- # @return [Boolean]
62
+ # @return [Boolean] Have user information been fetched?
59
63
  def fetched?
60
64
  @data and @doc
61
65
  end
62
66
 
63
- # Return the user’s Globally Unique Identifier (GUID).
67
+ # Returns the user’s Globally Unique Identifier (GUID).
64
68
  #
65
69
  # @return [String]
66
70
  def guid
67
71
  @guid
68
72
  end
69
73
 
70
- # Return the user’s name.
74
+ # Returns the user’s name.
71
75
  #
72
76
  # @return [String]
73
- # @raise [Geocaching::TimeoutError] Timeout hit
74
- # @raise [Geocaching::HTTPError] HTTP request failed
75
- # @raise [Geocaching::ExtractError] Could not extract name from website
76
77
  def name
77
78
  @name ||= begin
78
79
  raise NotFetchedError unless fetched?
@@ -82,17 +83,14 @@ module Geocaching
82
83
  if elements.size == 1 and elements.first.content =~ /Profile for User|Reviewer: (.+)/
83
84
  HTTP.unescape($1)
84
85
  else
85
- raise ExtractError, "Could not extract name from website"
86
+ raise ParseError, "Could not extract name from website"
86
87
  end
87
88
  end
88
89
  end
89
90
 
90
- # Return the user’s occupation.
91
+ # Returns the user’s occupation.
91
92
  #
92
- # @return [String]
93
- # @raise [Geocaching::TimeoutError] Timeout hit
94
- # @raise [Geocaching::HTTPError] HTTP request failed
95
- # @raise [Geocaching::ExtractError] Could not extract information from website
93
+ # @return [String] Occupation
96
94
  def occupation
97
95
  @occupation ||= begin
98
96
  raise NotFetchedError unless fetched?
@@ -102,17 +100,14 @@ module Geocaching
102
100
  if elements.size == 1
103
101
  HTTP.unescape(elements.first.content)
104
102
  else
105
- raise ExtractError, "Could not extract occupation from website"
103
+ raise ParseError, "Could not extract occupation from website"
106
104
  end
107
105
  end
108
106
  end
109
107
 
110
- # Return the user’s location.
108
+ # Returns the user’s location.
111
109
  #
112
- # @return [String]
113
- # @raise [Geocaching::TimeoutError] Timeout hit
114
- # @raise [Geocaching::HTTPError] HTTP request failed
115
- # @raise [Geocaching::ExtractError] Could not extract information from website
110
+ # @return [String] Location
116
111
  def location
117
112
  @location ||= begin
118
113
  raise NotFetchedError unless fetched?
@@ -122,17 +117,14 @@ module Geocaching
122
117
  if elements.size == 1
123
118
  HTTP.unescape(elements.first.content)
124
119
  else
125
- raise ExtractError, "Could not extract location from website"
120
+ raise ParseError, "Could not extract location from website"
126
121
  end
127
122
  end
128
123
  end
129
124
 
130
- # Return the user’s forum title.
125
+ # Returns the user’s forum title.
131
126
  #
132
- # @return [String]
133
- # @raise [Geocaching::TimeoutError] Timeout hit
134
- # @raise [Geocaching::HTTPError] HTTP request failed
135
- # @raise [Geocaching::ExtractError] Could not extract information from website
127
+ # @return [String] Forum title
136
128
  def forum_title
137
129
  @forum_title ||= begin
138
130
  raise NotFetchedError unless fetched?
@@ -142,17 +134,14 @@ module Geocaching
142
134
  if elements.size == 1
143
135
  HTTP.unescape(elements.first.content)
144
136
  else
145
- raise ExtractError, "Could not extract forum title from website"
137
+ raise ParseError, "Could not extract forum title from website"
146
138
  end
147
139
  end
148
140
  end
149
141
 
150
- # Return the user’s homepage.
142
+ # Returns the user’s homepage.
151
143
  #
152
- # @return [String]
153
- # @raise [Geocaching::TimeoutError] Timeout hit
154
- # @raise [Geocaching::HTTPError] HTTP request failed
155
- # @raise [Geocaching::ExtractError] Could not extract information from website
144
+ # @return [String] Homepage
156
145
  def homepage
157
146
  @homepage ||= begin
158
147
  raise NotFetchedError unless fetched?
@@ -162,12 +151,9 @@ module Geocaching
162
151
  end
163
152
  end
164
153
 
165
- # Return the user’s statuses.
154
+ # Returns the user’s statuses.
166
155
  #
167
- # @return [Array<String>]
168
- # @raise [Geocaching::TimeoutError] Timeout hit
169
- # @raise [Geocaching::HTTPError] HTTP request failed
170
- # @raise [Geocaching::ExtractError] Could not extract information from website
156
+ # @return [Array<String>] Array of statuses
171
157
  def status
172
158
  @status ||= begin
173
159
  raise NotFetchedError unless fetched?
@@ -177,17 +163,14 @@ module Geocaching
177
163
  if elements.size == 1
178
164
  HTTP.unescape(elements.first.content).split(",").map(&:strip)
179
165
  else
180
- raise ExtractError, "Could not extract status from website"
166
+ raise ParseError, "Could not extract status from website"
181
167
  end
182
168
  end
183
169
  end
184
170
 
185
- # Return the user’s last visit date.
171
+ # Returns the user’s last visit date.
186
172
  #
187
- # @return [Time]
188
- # @raise [Geocaching::TimeoutError] Timeout hit
189
- # @raise [Geocaching::HTTPError] HTTP request failed
190
- # @raise [Geocaching::ExtractError] Could not extract information from website
173
+ # @return [Time] Last visit date
191
174
  def last_visit
192
175
  @last_visit ||= begin
193
176
  raise NotFetchedError unless fetched?
@@ -197,17 +180,14 @@ module Geocaching
197
180
  if elements.size == 1
198
181
  Time.parse(elements.first.content)
199
182
  else
200
- raise ExtractError, "Could not extract last visit date from website"
183
+ raise ParseError, "Could not extract last visit date from website"
201
184
  end
202
185
  end
203
186
  end
204
187
 
205
- # Return the user’s member since date.
188
+ # Returns the user’s member since date.
206
189
  #
207
- # @return [Time]
208
- # @raise [Geocaching::TimeoutError] Timeout hit
209
- # @raise [Geocaching::HTTPError] HTTP request failed
210
- # @raise [Geocaching::ExtractError] Could not extract information from website
190
+ # @return [Time] Member since date
211
191
  def member_since
212
192
  @member_since ||= begin
213
193
  raise NotFetchedError unless fetched?
@@ -217,33 +197,30 @@ module Geocaching
217
197
  if elements.size == 1
218
198
  Time.parse(elements.first.content)
219
199
  else
220
- raise ExtractError, "Could not extract member since date from website"
200
+ raise ParseError, "Could not extract member since date from website"
221
201
  end
222
202
  end
223
203
  end
224
204
 
225
- # Return whether the user is a reviewer.
205
+ # Returns whether the user is a reviewer.
226
206
  #
227
- # @return [Boolean]
228
- # @raise [Geocaching::TimeoutError] Timeout hit
229
- # @raise [Geocaching::HTTPError] HTTP request failed
230
- # @raise [Geocaching::ExtractError] Could not extract information from website
207
+ # @return [Boolean] Is user a reviewer?
231
208
  def reviewer?
232
209
  status.include?("Reviewer")
233
210
  end
234
211
 
235
- # Return whether the user is a premium member.
212
+ # Returns whether the user is a Premium Member.
236
213
  #
237
- # @return [Boolean]
238
- # @raise [Geocaching::TimeoutError] Timeout hit
239
- # @raise [Geocaching::HTTPError] HTTP request failed
240
- # @raise [Geocaching::ExtractError] Could not extract information from website
214
+ # @return [Boolean] Is user a Premium Member?
241
215
  def premium_member?
242
216
  status.include?("Premium Member")
243
217
  end
244
218
 
245
219
  private
246
220
 
221
+ # Returns the HTTP request path.
222
+ #
223
+ # @return [String] HTTP request path
247
224
  def path
248
225
  "/profile/?guid=#{guid}"
249
226
  end