geocaching 0.6.1 → 0.7.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.
@@ -1,57 +1,11 @@
1
- Ruby API for geocaching.com
1
+ Ruby API for Geocaching.com
2
2
  ===========================
3
3
 
4
- This Ruby library provides an API for geocaching.com.
4
+ **ruby-geocaching** is a Ruby library providing an API for Geocaching.com.
5
+ It doesn’t scrape their website but rather uses the same API the
6
+ iPhone and Android geocaching apps use.
5
7
 
6
-
7
- Usage
8
- -----
9
-
10
- require "geocaching"
11
-
12
- # Logging in is not always necessary, but some information are only
13
- # accessible when logged in.
14
- Geocaching::HTTP.login("username", "password")
15
-
16
- # Fetch the information for a cache by the cache’s GC code. You can also
17
- # provide the cache’s GUID instead of the GC code.
18
- cache = Geocaching::Cache.fetch(:code => "...")
19
-
20
- # Print some cache information.
21
- puts " Name: #{cache.name}"
22
- puts "Difficulty: #{cache.difficulty}"
23
- puts " Owner: #{cache.owner.username}"
24
-
25
- # Print the number of logs.
26
- puts " Logs: #{cache.logs.size}"
27
-
28
- # Print the number of users that didn’t find the cache.
29
- dnfs = cache.logs.select { |log| log.type == :dnf }.size
30
- puts " DNFs: #{dnfs}"
31
-
32
- # Fetch the information for a log by its GUID.
33
- log = Geocaching::Log.fetch(:guid => "...")
34
-
35
- # Print some log information.
36
- puts "Username: #{log.user.name}"
37
- puts " Words: #{log.message.split.size}"
38
- puts " Cache: #{log.cache.name}"
39
-
40
- Geocaching::HTTP.logout
41
-
42
- The whole library may raise the following exceptions:
43
-
44
- * `Geocaching::TimeoutError` when a timeout is hit.
45
- * `Geocaching::LoginError` when calling a method that requires being
46
- logged in and you’re not.
47
- * `Geocaching::NotFetchedError` when accessing a method that requires the
48
- `fetch` method to be called first.
49
- * `Geocaching::ExtractError` when information could not be extracted
50
- out of the website’s HTML code. This mostly happens after Groundspeak
51
- changed their website.
52
- * `Geocaching::HTTPError` when a HTTP request failed.
53
-
54
- All exceptions are subclasses of `Geocaching::Error`.
8
+ See the [website](https://nano.github.com/ruby-geocaching) for more information.
55
9
 
56
10
 
57
11
  Tests
@@ -60,11 +14,11 @@ Tests
60
14
  Tests are written using [RSpec](http://relishapp.com/rspec).
61
15
  You need [Bundler](http://gembundler.com/) to run the tests.
62
16
 
63
- $ bundle update
64
- $ GC_USERNAME="username" GC_PASSWORD="password" bundle exec rake test
17
+ $ bundle
18
+ $ bundle exec rake test USERNAME=username PASSWORD=password
19
+
65
20
 
66
- Additional environment variables you may specify are:
21
+ License
22
+ -------
67
23
 
68
- * `GC_TIMEOUT` HTTP timeout in seconds
69
- * `GC_CACHE_TYPES` — A space-separated list of cache types you want to test.
70
- * `GC_LOG_TYPES` — A space-separated list of log types you want to test.
24
+ **ruby-geocaching** is released under the terms of the MIT license.
@@ -1,91 +1,18 @@
1
1
  # encoding: utf-8
2
2
 
3
- require "nokogiri"
3
+ require "geocaching/cache"
4
+ require "geocaching/cache_type"
5
+ require "geocaching/container_type"
6
+ require "geocaching/errors"
7
+ require "geocaching/log"
8
+ require "geocaching/log_type"
9
+ require "geocaching/session"
10
+ require "geocaching/user"
11
+ require "geocaching/trackable"
12
+ require "geocaching/trackable_type"
13
+ require "geocaching/waypoint"
14
+ require "geocaching/waypoint_type"
4
15
 
5
- # This is a Ruby library to access information on geocaching.com. As
6
- # Groundspeak does not provide a public API yet, one needs to parse the
7
- # website content. That’s what this library does.
8
- #
9
- # The whole library may raise the following exceptions:
10
- #
11
- # * {Geocaching::LoginError}
12
- # * {Geocaching::NotFetchedError}
13
- # * {Geocaching::ParseError}
14
- # * {Geocaching::TimeoutError}
15
- # * {Geocaching::HTTPError}
16
- #
17
- # == Usage
18
- #
19
- # To have access to all information, you will need to provide the
20
- # credentials for an account on geocaching.com and log in:
21
- #
22
- # Geocaching::HTTP.login("username", "password")
23
- #
24
- # Make sure to log out when you’re done:
25
- #
26
- # Geocaching::HTTP.logout
27
- #
28
- # You know have access to all information that are available for the
29
- # account you provided.
30
- #
31
- # cache = Geocaching::Cache.fetch(:code => "GCTEST")
32
- # p cache.difficulty #=> 3.5
33
- # p cache.latitude #=> 49.17518
34
- # p cache.pmonly? #=> false
35
- # p cache.logs.size #=> 41
36
- #
37
- # log = Geocaching::Log.new(:guid => "07208985-f7b2-456a-b0a8-bbc26f28b5a9")
38
- # log.fetch
39
- # p log.cache #=> #<Geocaching::Cache:...>
40
- # p log.username #=> Foobar
41
- #
42
16
  module Geocaching
43
- # All exceptions raised by this library are subclasses of this class. It
44
- # easily allows to rescue from all exceptions by catching Geocaching::Error.
45
- class Error < Exception
46
- end
47
-
48
- # This exception is raised when a method that requires being
49
- # logged in is called when not logged in.
50
- class LoginError < Error
51
- end
52
-
53
- # This exception is raised when a timeout is hit.
54
- class TimeoutError < Error
55
- end
56
-
57
- # This exception is raised if a request exceeds 500 results.
58
- class TooManyResultsError < Error
59
- def initialize
60
- super "Your request exceeded 500 results"
61
- end
62
- end
63
-
64
- # This exception is raised when a method is called that requires
65
- # the #fetch method to be called first.
66
- class NotFetchedError < Error
67
- def initialize
68
- super "Need to call the #fetch method first"
69
- end
70
- end
71
-
72
- # This exception is raised when information could not be
73
- # extracted out of the website’s HTML code. For example,
74
- # this may happen if Groundspeak changed their website.
75
- class ParseError < Error
76
- end
77
-
78
- # This exception is raised when a HTTP request failed.
79
- class HTTPError < Error
80
- end
81
-
82
- autoload :Cache, "geocaching/cache"
83
- autoload :CacheType, "geocaching/cache_type"
84
- autoload :HTTP, "geocaching/http"
85
- autoload :Log, "geocaching/log"
86
- autoload :LogType, "geocaching/log_type"
87
- autoload :MyLogs, "geocaching/my_logs"
88
- autoload :User, "geocaching/user"
89
- autoload :VERSION, "geocaching/version"
90
- autoload :Watchlist, "geocaching/watchlist"
17
+ VERSION = "0.7.0"
91
18
  end
@@ -1,590 +1,112 @@
1
1
  # encoding: utf-8
2
2
 
3
- require "time"
4
- require "json"
3
+ require "geocaching/parsers/cache_simple"
4
+ require "geocaching/parsers/cache_gpx"
5
5
 
6
6
  module Geocaching
7
- # The {LogsArray} class is a subclass of +Array+ and is used to store all
8
- # logs that belong to a cache. It implements the {#fetch_all} method to
9
- # fetch the information of all logs inside the array.
10
- class LogsArray < Array
11
- # Calls {Geocaching::Log#fetch} for each log inside the array.
12
- #
13
- # @return [Boolean] Whether all logs could be fetched successfully
14
- def fetch_all
15
- each { |log| log.fetch }
16
- map { |log| log.fetched? }.all?
17
- end
18
- end
19
-
20
- # The {Cache} class represents a cache on geocaching.com. Altough some
21
- # information are available without being logged in, most information
22
- # will only be accessible after a successful login.
23
- #
24
- # == Usage
25
- #
26
- # cache = Geocaching::Cache.fetch(:code => "GCTEST")
27
- #
28
- # puts cache.difficulty #=> 3.5
29
- # puts cache.latitude #=> 49.741541
30
- # puts cache.archived? #=> false
31
- #
32
7
  class Cache
33
- # Creates a new instance and calls the {#fetch} methods afterwards.
34
- # One of +:code+ or +:guid+ must be provided as attributes.
35
- #
36
- # @param [Hash] attributes A hash of attributes, see {#initialize}
37
- # @return [Geocaching::Cache]
38
- # @raise [ArgumentError] Tried to set an unknown attribute
39
- # @raise [ArgumentError] Neither code nor GUID given
40
- def self.fetch(attributes)
41
- cache = new(attributes)
42
- cache.fetch
43
- cache
44
- end
45
-
46
- # Returns an array of caches that are located within the given bounds.
47
- # If the number of results exceeds 500 caches, a
48
- # {Geocaching::TooManyResultsError} exception is raised.
49
- #
50
- # @param [Array<Numeric>] northeast
51
- # An array containing latitude and longitude of the upper right bound
52
- # @param [Array<Numeric>] southwest
53
- # An array containing latitude and longitude of the lower left bound
54
- # @return [Array<Geocaching::Cache>]
55
- # Array of caches within given bounds
56
- # @raise [ArgumentError]
57
- # Invalid arguments
58
- # @raise [Geocaching::TooManyResultsError]
59
- # Number of results exceeds 500 caches
60
- def self.within_bounds(northeast, southwest)
61
- unless northeast.kind_of?(Array) and southwest.kind_of?(Array)
62
- raise ArgumentError, "Arguments must be arrays"
63
- end
64
-
65
- unless northeast.size == 2 and southwest.size == 2
66
- raise ArgumentError, "Arguments must have two elements"
67
- end
68
-
69
- unless northeast.map { |a| a.kind_of?(Numeric) }.all? \
70
- and southwest.map { |a| a.kind_of?(Numeric) }.all?
71
- raise ArgumentError, "Latitude and longitude must be given as numbers"
72
- end
73
-
74
- post_data = {
75
- "dto" => {
76
- "data" => {
77
- "c" => 1,
78
- "m" => "",
79
- "d" => [northeast[0], southwest[0], northeast[1], southwest[1]].join("|")
80
- },
81
- "ut" => ""
82
- }
83
- }
84
-
85
- headers = {
86
- "Content-Type" => "application/json; charset=UTF-8"
87
- }
88
-
89
- resp, data = HTTP.post("/map/default.aspx/MapAction",
90
- JSON.generate(post_data), headers)
91
-
92
- begin
93
- outer = JSON.parse(data)
94
- raise ParseError, "Invalid JSON response" unless outer["d"]
95
- results = JSON.parse(outer["d"])
96
- rescue JSON::JSONError
97
- raise ParseError, "Could not parse response JSON data"
98
- end
99
-
100
- raise ParseError, "Invalid JSON response" unless results["cs"]
101
- raise TooManyResultsError if results["cs"]["count"] == 501
102
-
103
- if results["cs"]["cc"].kind_of?(Array)
104
- results["cs"]["cc"].map do |result|
105
- Cache.new \
106
- :name => result["nn"],
107
- :code => result["gc"],
108
- :latitude => result["lat"],
109
- :longitude => result["lon"],
110
- :type => CacheType.for_id(result["ctid"])
111
- end
112
- else
113
- []
114
- end
8
+ attr_accessor :code
9
+ attr_accessor :guid
10
+ attr_accessor :name
11
+ attr_accessor :latitude
12
+ attr_accessor :longitude
13
+ attr_accessor :difficulty
14
+ attr_accessor :terrain
15
+ attr_accessor :hidden_at
16
+ attr_accessor :placed_by
17
+ attr_reader :owner
18
+ attr_reader :type
19
+ attr_reader :container_type
20
+ attr_accessor :country
21
+ attr_accessor :state
22
+ attr_accessor :is_available
23
+ attr_accessor :is_archived
24
+ attr_accessor :is_premium
25
+ attr_accessor :short_description
26
+ attr_accessor :long_description
27
+ attr_accessor :hint
28
+ attr_accessor :waypoints
29
+
30
+ def self.from_xml(doc)
31
+ Parsers::CacheSimple.new(doc).parse
32
+ end
33
+
34
+ def self.from_gpx(doc)
35
+ Parsers::CacheGPX.new(doc).parse
115
36
  end
116
37
 
117
- # Creates a new instance. The following attributes may be specified
118
- # as parameters:
119
- #
120
- # * +:code+ — The cache’s GC code
121
- # * +:guid+ — The cache’s Globally Unique Identifier
122
- #
123
- # @param [Hash] attributes A hash of attributes
124
- # @raise [ArgumentError] Trying to set an unknown attribute
125
38
  def initialize(attributes = {})
126
- @data, @doc, @code, @guid = nil, nil, nil, nil
39
+ @waypoints = []
127
40
 
128
41
  attributes.each do |key, value|
129
- if [:code, :guid, :name, :type, :latitude, :longitude].include?(key)
130
- if key == :type and not value.kind_of?(CacheType)
131
- raise TypeError, "Attribute `type' must be an instance of Geocaching::CacheType"
132
- end
133
-
134
- instance_variable_set("@#{key}", value)
135
- else
136
- raise ArgumentError, "Trying to set unknown attribute `#{key}'"
137
- end
138
- end
139
- end
140
-
141
- # Fetches cache information from geocaching.com.
142
- #
143
- # @return [void]
144
- # @raise [ArgumentError] Neither code nor GUID are given
145
- def fetch
146
- raise ArgumentError, "Neither code nor GUID given" unless @code or @guid
147
-
148
- base_path = "/seek/cache_details.aspx?log=y&"
149
- resp, @data = HTTP.get(base_path + (@code ? "wp=#{@code}" : "guid=#{@guid}"))
150
- @doc = Nokogiri::HTML.parse(@data)
151
- end
152
-
153
- # Returns whether information have successfully been fetched
154
- # from geocaching.com.
155
- #
156
- # @return [Boolean] Have cache information been fetched?
157
- def fetched?
158
- @data and @doc
159
- end
160
-
161
- # Returns the cache’s code.
162
- #
163
- # @return [String] Code
164
- def code
165
- @code ||= begin
166
- raise NotFetchedError unless fetched?
167
- elements = @doc.search("#ctl00_uxWaypointName.GCCode")
168
-
169
- if elements.size == 1 and elements.first.content =~ /(GC[A-Z0-9]+)/
170
- HTTP.unescape($1)
171
- else
172
- raise ParseError, "Could not extract code from website"
173
- end
174
- end
175
- end
176
-
177
- # Returns the cache’s Globally Unique Identifier (GUID).
178
- #
179
- # @return [String] GUID
180
- def guid
181
- @guid ||= begin
182
- raise NotFetchedError unless fetched?
183
-
184
- elements = @doc.search("#ctl00_ContentBody_lnkPrintFriendly")
185
- guid = nil
186
-
187
- if elements.size == 1 and href = elements.first["href"]
188
- guid = $1 if href =~ /guid=([0-9a-f-]{36})/
189
- end
190
-
191
- guid || begin
192
- raise ParseError, "Could not extract GUID from website"
193
- end
194
- end
195
- end
196
-
197
- # Returns the cache’s ID (which is not the UUID).
198
- #
199
- # @return [Fixnum] ID
200
- def id
201
- @id ||= begin
202
- raise NotFetchedError unless fetched?
203
-
204
- elements = @doc.search("div.CacheDetailNavigationWidget li a[href^='/seek/log.aspx']")
205
-
206
- if elements.size > 0 and elements.first["href"] =~ /ID=(\d+)/
207
- id = $1.to_i
208
- end
209
-
210
- id || begin
211
- raise ParseError, "Could not extract ID from website"
212
- end
213
- end
214
- end
215
-
216
- # Returns the cache’s type ID.
217
- #
218
- # @return [Fixnum] Type ID
219
- def type_id
220
- @type_id ||= begin
221
- raise NotFetchedError unless fetched?
222
-
223
- if @data =~ /<a.*?title="About Cache Types"><img.*?WptTypes\/(\d+)\.gif".*?<\/a>/
224
- $1.to_i
225
- else
226
- raise ParseError, "Could not extract cache type ID from website"
227
- end
228
- end
229
- end
230
-
231
- # Returns the cache’s type.
232
- #
233
- # @return [Geocaching::CacheType] Type
234
- def type
235
- @type ||= CacheType.for_id(type_id)
236
- end
237
-
238
- # Returns the cache’s name.
239
- #
240
- # @return [String] Name
241
- def name
242
- @name ||= begin
243
- raise NotFetchedError unless fetched?
244
- elements = @doc.search("span#ctl00_ContentBody_CacheName")
245
-
246
- if elements.size == 1
247
- HTTP.unescape(elements.first.content)
248
- else
249
- raise ParseError, "Could not extract name from website"
250
- end
251
- end
252
- end
253
-
254
- # Returns the cache’s owner.
255
- #
256
- # @return [Geocaching::User] Owner
257
- def owner
258
- @owner ||= begin
259
- raise NotFetchedError unless fetched?
260
- elements = @doc.search("span.minorCacheDetails a[href*='/profile/?guid=']")
261
-
262
- if elements.size == 1 and elements.first["href"] =~ /guid=([a-f0-9-]{36})/
263
- @owner_display_name = HTTP.unescape(elements.first.content)
264
- User.new(:guid => $1)
42
+ if respond_to?("#{key}=")
43
+ send("#{key}=", value)
265
44
  else
266
- raise ParseError, "Could not extract owner from website"
45
+ raise ArgumentError, "Unknown attribute `#{key}'"
267
46
  end
268
47
  end
269
48
  end
270
49
 
271
- # Returns the displayed cache owner name.
272
- #
273
- # @return [String] Displayed owner name
274
- def owner_display_name
275
- owner unless @owner_display_name
276
- @owner_display_name
277
- end
278
-
279
- # Returns the cache’s difficulty rating.
280
- #
281
- # @return [Float] Difficulty rating
282
- def difficulty
283
- @difficulty ||= begin
284
- raise NotFetchedError unless fetched?
285
- elements = @doc.search("#ctl00_ContentBody_uxLegendScale img")
286
-
287
- if elements.size == 1 and elements.first["alt"] =~ /([\d\.]{1,3}) out of 5/
288
- $1.to_f
289
- else
290
- raise ParseError, "Could not extract difficulty rating from website"
291
- end
292
- end
293
- end
294
-
295
- # Returns the cache’s terrain rating.
296
- #
297
- # @return [Float] Terrain rating
298
- def terrain
299
- @terrain ||= begin
300
- raise NotFetchedError unless fetched?
301
- elements = @doc.search("#ctl00_ContentBody_Localize6 img")
50
+ alias :available? :is_available
51
+ alias :archived? :is_archived
52
+ alias :premium? :is_premium
302
53
 
303
- if elements.size == 1 and elements.first["alt"] =~ /([\d\.]{1,3}) out of 5/
304
- $1.to_f
305
- else
306
- raise ParseError, "Could not extract terrain rating from website"
307
- end
308
- end
309
- end
310
-
311
- # Returns the date the cache has been hidden at.
312
- #
313
- # @return [Time] Hidden date
314
- def hidden_at
315
- @hidden_at ||= begin
316
- raise NotFetchedError unless fetched?
317
-
318
- if @data =~ /<span class="minorCacheDetails">Hidden\s*?:\s*?(\d{1,2})\/(\d{1,2})\/(\d{4})<\/span>/
319
- Time.mktime($3, $1, $2)
320
- else
321
- raise ParseError, "Could not extract hidden date from website"
322
- end
323
- end
324
- end
325
-
326
- # Returns the date the event has been held.
327
- #
328
- # @return [Time] Event date
329
- def event_date
330
- @event_date ||= begin
331
- raise NotFetchedError unless fetched?
332
- return nil unless [:event, :megaevent, :cito, :lfevent].include?(type.to_sym)
333
-
334
- if @data =~ /<span class="minorCacheDetails">\s*?Event Date:\s*?\w+, (\d+) (\w+) (\d{4})<\/span>/
335
- Time.parse([$1, $2, $3].join)
336
- else
337
- raise ParseError, "Could not extract event date from website"
338
- end
339
- end
340
- end
341
-
342
- # Returns the cache’s container size. One of the following symbols
343
- # is returned:
344
- #
345
- # * +:micro+
346
- # * +:small+
347
- # * +:regular+
348
- # * +:large+
349
- # * +:other+
350
- # * +:not_chosen+
351
- # * +:virtual+
352
- #
353
- # @return [Symbol] Cache container size
354
- def size
355
- @size ||= begin
356
- raise NotFetchedError unless fetched?
357
- size = nil
358
-
359
- if @data =~ /<img src="\/images\/icons\/container\/(.*?)\.gif" alt="Size: .*?" \/>/
360
- size = $1.to_sym if %w(micro small regular large other not_chosen virtual).include?($1)
361
- end
362
-
363
- size || begin
364
- raise ParseError, "Could not extract cache container size from website"
365
- end
366
- end
367
- end
368
-
369
- # Returns the cache’s latitude.
370
- #
371
- # @return [Float] Latitude
372
- def latitude
373
- @latitude ||= begin
374
- raise NotFetchedError unless fetched?
375
- return nil if type == :locationless
376
-
377
- latitude = nil
378
- elements = @doc.search("a#ctl00_ContentBody_lnkConversions")
379
-
380
- if elements.size == 1 and href = elements.first["href"]
381
- latitude = $1.to_f if href =~ /lat=(-?[0-9\.]+)/
382
- end
383
-
384
- latitude || begin
385
- raise ParseError, "Could not extract latitude from website"
386
- end
387
- end
388
- end
389
-
390
- # Returns the cache’s longitude.
391
- #
392
- # @return [Float] Longitude
393
- def longitude
394
- @longitude ||= begin
395
- raise NotFetchedError unless fetched?
396
- return nil if type == :locationless
397
-
398
- longitude = nil
399
- elements = @doc.search("a#ctl00_ContentBody_lnkConversions")
400
-
401
- if elements.size == 1 and href = elements.first["href"]
402
- longitude = $1.to_f if href =~ /lon=(-?[0-9\.]+)/
403
- end
404
-
405
- longitude || begin
406
- raise ParseError, "Could not extract longitude from website"
407
- end
408
- end
409
- end
410
-
411
- # Returns the cache’s location name (State, Country).
412
- #
413
- # @return [String] Location name
414
- def location
415
- @location ||= begin
416
- raise NotFetchedError unless fetched?
417
- return nil if type == :locationless
418
-
419
- location = nil
420
- elements = @doc.search("span#ctl00_ContentBody_Location")
421
-
422
- if elements.size == 1
423
- text = @doc.search("span#ctl00_ContentBody_Location").inner_html
424
- location = $1.strip if text =~ /In ([^<]+)/
425
- end
426
-
427
- location || begin
428
- raise ParseError, "Could not extract location from website"
429
- end
430
- end
431
- end
432
-
433
- # Returns whether the cache is unpublished or not.
434
- #
435
- # @return [Boolean] Is cache unpublished?
436
- def unpublished?
437
- @is_unpublished ||= begin
438
- raise NotFetchedError unless fetched?
439
- !!(@data =~ /<h2>Cache is Unpublished<\/h2>/)
440
- end
441
- end
442
-
443
- # Returns whether the cache has been archived or not.
444
- #
445
- # @return [Boolean] Has cache been archived?
446
- def archived?
447
- @is_archived ||= begin
448
- raise NotFetchedError unless fetched?
449
- !!(@data =~ /<li>This cache has been archived/)
450
- end
451
- end
452
-
453
- # Returns whether the cache is only viewable to Premium Member only.
454
- #
455
- # @return [Boolean] Is cache PM-only?
456
- def pmonly?
457
- @is_pmonly ||= begin
458
- raise NotFetchedError unless fetched?
459
-
460
- @doc.search("#ctl00_ContentBody_basicMemberMsg").size == 1 ||
461
- !!(@data =~ /<p class="Warning">This is a Premium Member Only cache\.<\/p>/)
462
- end
463
- end
464
-
465
- # Returns whether the cache is currently in review.
466
- #
467
- # @return [Boolean] Is cache currently in review?
468
- def in_review?
469
- @in_review ||= begin
470
- raise NotFetchedError unless fetched?
471
- [!!@data.match(/<p class="Warning">Sorry, you cannot view this cache listing until it has been published/),
472
- !!@data.match(/<p class="Warning">This cache listing has not been reviewed yet/)].any?
54
+ def hidden_at=(time)
55
+ if time.kind_of?(Time)
56
+ @hidden_at = time
57
+ else
58
+ raise TypeError, "`hidden_at' must be an instance of Time"
473
59
  end
474
60
  end
475
61
 
476
- # Returns an array of the cache’s logs.
477
- #
478
- # @return [Geocaching::LogsArray<Geocaching::Log>] Array of logs
479
- def logs
480
- @logs ||= begin
481
- raise NotFetchedError unless fetched?
482
-
483
- logs = LogsArray.new
484
- tds = @doc.search("table.Table.LogsTable > tr > td")
485
-
486
- if tds.size == 0
487
- raise ParseError, "Could not extract logs from website"
488
- end
489
-
490
- tds.each do |td|
491
- strongs = td.search("strong")
492
- next unless strongs.size > 0
493
-
494
- imgs = strongs.first.search("img")
495
- user_as = strongs.first.search("a[href^='/profile/']")
496
- log_as = td.search("small > a[href^='log.aspx']")
497
-
498
- unless imgs.size == 1 and user_as.size == 1 and log_as.size == 1
499
- raise ParseError, "Could not extract logs from website"
500
- end
501
-
502
- log_title = imgs.first["title"]
503
- user_name = user_as.first.text.strip
504
- user_guid = user_as.first["href"] =~ /guid=([a-f0-9-]{36})/ ? $1 : nil
505
- log_guid = log_as.first["href"] =~ /LUID=([a-f0-9-]{36})/ ? $1 : nil
506
-
507
- # XXX Fix log title as Groundspeak is unable to escape their
508
- # HTML code properly.
509
- log_title = "Didn't find it" if log_title == "Didn"
510
-
511
- unless log_title and user_name and user_guid and log_guid
512
- raise ParseError, "Could not extract logs from website"
513
- end
514
-
515
- user = User.new(:guid => user_guid, :name => user_name)
516
- log = Log.new \
517
- :guid => log_guid,
518
- :title => log_title,
519
- :cache => self,
520
- :user => user
521
-
522
- logs << log
523
- end
524
-
525
- logs
62
+ def owner=(user)
63
+ if user.kind_of?(Geocaching::User)
64
+ @owner = user
65
+ else
66
+ raise TypeError, "`owner' must be an instance of Geocaching::User"
526
67
  end
527
68
  end
528
69
 
529
- # Puts the cache on the user’s watchlist. Obviously, you need to be logged
530
- # in when calling this method.
531
- #
532
- # @return [void]
533
- def watch
534
- unless HTTP.loggedin?
535
- raise LoginError
70
+ def type=(cache_type)
71
+ if cache_type.kind_of?(Geocaching::CacheType)
72
+ @type = cache_type
73
+ else
74
+ raise TypeError, "`type' must be an instance of Geocaching::CacheType"
536
75
  end
537
-
538
- HTTP.get("/my/watchlist.aspx?w=#{id}")
539
76
  end
540
77
 
541
- # Removes the cache from the user’s watchlist. This method doesn’t check
542
- # if the cache you want to remove from the watchlist actually is on your
543
- # watchlist.
544
- #
545
- # @return [void]
546
- def unwatch
547
- unless HTTP.loggedin?
548
- raise LoginError
78
+ def container_type=(container_type)
79
+ if container_type.kind_of?(Geocaching::ContainerType)
80
+ @container_type = container_type
81
+ else
82
+ raise TypeError, "`container_type' must be an instance of Geocaching::CacheType"
549
83
  end
550
-
551
- HTTP.post("/my/watchlist.aspx?ds=1&id=#{id}&action=rem", {
552
- "ctl00$ContentBody$btnYes" => "Yes"
553
- })
554
84
  end
555
85
 
556
- # Returns a hash representing this cache.
557
- #
558
- # @return [Hash] Hash representing this cache
559
86
  def to_hash
560
- hash = {
561
- :id => id,
562
- :code => code,
563
- :guid => guid,
564
- :name => name,
565
- :difficulty => difficulty,
566
- :terrain => terrain,
567
- :latitude => latitude,
568
- :longitude => longitude,
569
- :size => size,
570
- :type => type,
571
- :location => location,
572
- :owner => owner,
573
- :owner_display_name => owner_display_name,
574
- :logs => logs,
575
- :pmonly? => pmonly?,
576
- :archived? => archived?,
577
- :in_review? => in_review?,
578
- :unpublished? => unpublished?
87
+ {
88
+ :code => code,
89
+ :guid => guid,
90
+ :name => name,
91
+ :latitude => latitude,
92
+ :longitude => longitude,
93
+ :difficulty => difficulty,
94
+ :terrain => terrain,
95
+ :container_type => container_type,
96
+ :type => type,
97
+ :hidden_at => hidden_at,
98
+ :placed_by => placed_by,
99
+ :owner => owner,
100
+ :country => country,
101
+ :state => state,
102
+ :is_available => is_available,
103
+ :is_archived => is_archived,
104
+ :is_premium => is_premium,
105
+ :waypoints => waypoints,
106
+ :short_description => short_description,
107
+ :long_description => long_description,
108
+ :hint => hint
579
109
  }
580
-
581
- if [:event, :megaevent, :lfevent, :cito].include?(type.to_sym)
582
- hash[:event_date] = event_date
583
- else
584
- hash[:hidden_at] = hidden_at
585
- end
586
-
587
- hash
588
110
  end
589
111
  end
590
112
  end