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.
- data/README.markdown +47 -21
- data/geocaching.gemspec +3 -0
- data/lib/geocaching.rb +20 -6
- data/lib/geocaching/cache.rb +237 -94
- data/lib/geocaching/cache_type.rb +17 -13
- data/lib/geocaching/http.rb +12 -16
- data/lib/geocaching/log.rb +67 -46
- data/lib/geocaching/log_type.rb +17 -13
- data/lib/geocaching/my_logs.rb +22 -16
- data/lib/geocaching/pocket_query.rb +213 -0
- data/lib/geocaching/user.rb +46 -69
- data/lib/geocaching/version.rb +3 -1
- data/lib/geocaching/watchlist.rb +106 -0
- data/spec/cache/ape.rb +4 -0
- data/spec/cache/cito.rb +4 -0
- data/spec/cache/earthcache.rb +4 -0
- data/spec/cache/event.rb +4 -0
- data/spec/cache/letterbox.rb +4 -0
- data/spec/cache/lfevent.rb +4 -0
- data/spec/cache/locationless.rb +4 -0
- data/spec/cache/megaevent.rb +4 -0
- data/spec/cache/multi.rb +4 -0
- data/spec/cache/mystery.rb +4 -0
- data/spec/cache/traditional.rb +4 -0
- data/spec/cache/virtual.rb +4 -0
- data/spec/cache/webcam.rb +4 -0
- data/spec/cache/wherigo.rb +4 -0
- data/spec/cache_spec.rb +7 -1
- data/spec/log/announcement.rb +1 -1
- data/spec/log/archive.rb +1 -1
- data/spec/log/attended.rb +1 -1
- data/spec/log/coords_update.rb +1 -1
- data/spec/log/disable.rb +1 -1
- data/spec/log/dnf.rb +1 -1
- data/spec/log/enable.rb +1 -1
- data/spec/log/found.rb +1 -1
- data/spec/log/needs_archived.rb +1 -1
- data/spec/log/needs_maintenance.rb +1 -1
- data/spec/log/note.rb +1 -1
- data/spec/log/owner_maintenance.rb +1 -1
- data/spec/log/publish.rb +1 -1
- data/spec/log/retract.rb +1 -1
- data/spec/log/reviewer_note.rb +1 -1
- data/spec/log/unarchive.rb +1 -1
- data/spec/log/webcam_photo_taken.rb +1 -1
- data/spec/log/will_attend.rb +1 -1
- data/spec/log_spec.rb +7 -1
- metadata +22 -6
- data/lib/geocaching/pq.rb +0 -72
data/README.markdown
CHANGED
@@ -1,44 +1,70 @@
|
|
1
1
|
Ruby API for geocaching.com
|
2
2
|
===========================
|
3
3
|
|
4
|
-
This Ruby library provides an API for geocaching.com.
|
5
|
-
doesn’t offer an official API yet, this library parses the website’s
|
6
|
-
HTML code.
|
4
|
+
This Ruby library provides an API for geocaching.com.
|
7
5
|
|
8
|
-
Documentation
|
9
|
-
-------------
|
10
6
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
Example
|
15
|
-
-------
|
7
|
+
Usage
|
8
|
+
-----
|
16
9
|
|
17
10
|
require "geocaching"
|
18
11
|
|
12
|
+
# Logging in is not always necessary, but some information are only
|
13
|
+
# accessible when logged in.
|
19
14
|
Geocaching::HTTP.login("username", "password")
|
20
15
|
|
21
|
-
cache
|
22
|
-
|
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}"
|
23
31
|
|
24
|
-
|
25
|
-
|
26
|
-
puts cache.logs.size #=> 194
|
32
|
+
# Fetch the information for a log by its GUID.
|
33
|
+
log = Geocaching::Log.fetch(:guid => "...")
|
27
34
|
|
28
|
-
|
29
|
-
puts log.
|
30
|
-
puts log.
|
35
|
+
# Print some log information.
|
36
|
+
puts "Username: #{log.user.name}"
|
37
|
+
puts " Words: #{log.message.split.size}"
|
38
|
+
puts " Cache: #{log.cache.name}"
|
31
39
|
|
32
40
|
Geocaching::HTTP.logout
|
33
41
|
|
34
|
-
|
35
|
-
|
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`.
|
55
|
+
|
36
56
|
|
37
57
|
Tests
|
38
58
|
-----
|
39
59
|
|
40
|
-
Tests are written using RSpec.
|
60
|
+
Tests are written using [RSpec](http://relishapp.com/rspec).
|
41
61
|
|
42
62
|
$ export GC_USERNAME="username"
|
43
63
|
$ export GC_PASSWORD="password"
|
44
64
|
$ rake test
|
65
|
+
|
66
|
+
Additional environment variables you may specify are:
|
67
|
+
|
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.
|
data/geocaching.gemspec
CHANGED
data/lib/geocaching.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require "nokogiri"
|
4
|
-
require "geocaching/version"
|
5
4
|
|
6
5
|
# This is a Ruby library to access information on geocaching.com. As
|
7
6
|
# Groundspeak does not provide a public API yet, one needs to parse the
|
8
7
|
# website content. That’s what this library does.
|
9
8
|
#
|
10
|
-
#
|
11
|
-
#
|
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}
|
12
16
|
#
|
13
17
|
# == Usage
|
14
18
|
#
|
@@ -50,8 +54,15 @@ module Geocaching
|
|
50
54
|
class TimeoutError < Error
|
51
55
|
end
|
52
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
|
+
|
53
64
|
# This exception is raised when a method is called that requires
|
54
|
-
# the
|
65
|
+
# the #fetch method to be called first.
|
55
66
|
class NotFetchedError < Error
|
56
67
|
def initialize
|
57
68
|
super "Need to call the #fetch method first"
|
@@ -61,10 +72,10 @@ module Geocaching
|
|
61
72
|
# This exception is raised when information could not be
|
62
73
|
# extracted out of the website’s HTML code. For example,
|
63
74
|
# this may happen if Groundspeak changed their website.
|
64
|
-
class
|
75
|
+
class ParseError < Error
|
65
76
|
end
|
66
77
|
|
67
|
-
# This exception is raised when a HTTP request
|
78
|
+
# This exception is raised when a HTTP request failed.
|
68
79
|
class HTTPError < Error
|
69
80
|
end
|
70
81
|
|
@@ -75,4 +86,7 @@ module Geocaching
|
|
75
86
|
autoload :LogType, "geocaching/log_type"
|
76
87
|
autoload :MyLogs, "geocaching/my_logs"
|
77
88
|
autoload :User, "geocaching/user"
|
89
|
+
autoload :Watchlist, "geocaching/watchlist"
|
90
|
+
autoload :PocketQuery, "geocaching/pocket_query"
|
91
|
+
autoload :VERSION, "geocaching/version"
|
78
92
|
end
|
data/lib/geocaching/cache.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require "time"
|
4
|
+
require "json"
|
4
5
|
|
5
6
|
module Geocaching
|
6
|
-
#
|
7
|
-
# that belong to a cache. It implements the {#fetch_all} method to
|
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
|
8
9
|
# fetch the information of all logs inside the array.
|
9
10
|
class LogsArray < Array
|
10
11
|
# Calls {Geocaching::Log#fetch} for each log inside the array.
|
@@ -16,11 +17,15 @@ module Geocaching
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
19
|
-
#
|
20
|
+
# The {Cache} class represents a cache on geocaching.com. Altough some
|
20
21
|
# information are available without being logged in, most information
|
21
22
|
# will only be accessible after a successful login.
|
22
23
|
#
|
23
|
-
#
|
24
|
+
# This class does caching. That means that multiple calls of, for example,
|
25
|
+
# the {#latitude} method only do one HTTP request. To override the cached
|
26
|
+
# information, call the {#fetch} method manually.
|
27
|
+
#
|
28
|
+
# == Usage
|
24
29
|
#
|
25
30
|
# cache = Geocaching::Cache.fetch(:code => "GCTEST")
|
26
31
|
#
|
@@ -29,7 +34,7 @@ module Geocaching
|
|
29
34
|
# puts cache.archived? #=> false
|
30
35
|
#
|
31
36
|
class Cache
|
32
|
-
#
|
37
|
+
# Creates a new instance and calls the {#fetch} methods afterwards.
|
33
38
|
# One of +:code+ or +:guid+ must be provided as attributes.
|
34
39
|
#
|
35
40
|
# @param [Hash] attributes A hash of attributes, see {#initialize}
|
@@ -42,7 +47,78 @@ module Geocaching
|
|
42
47
|
cache
|
43
48
|
end
|
44
49
|
|
45
|
-
#
|
50
|
+
# Returns an array of caches that are located within the given bounds.
|
51
|
+
# If the number of results exceeds 500 caches, a
|
52
|
+
# {Geocaching::TooManyResultsError} exception is raised.
|
53
|
+
#
|
54
|
+
# @param [Array<Numeric>] northeast
|
55
|
+
# An array containing latitude and longitude of the upper right bound
|
56
|
+
# @param [Array<Numeric>] southwest
|
57
|
+
# An array containing latitude and longitude of the lower left bound
|
58
|
+
# @return [Array<Geocaching::Cache>]
|
59
|
+
# Array of caches within given bounds
|
60
|
+
# @raise [ArgumentError]
|
61
|
+
# Invalid arguments
|
62
|
+
# @raise [Geocaching::TooManyResultsError]
|
63
|
+
# Number of results exceeds 500 caches
|
64
|
+
def self.within_bounds(northeast, southwest)
|
65
|
+
unless northeast.kind_of?(Array) and southwest.kind_of?(Array)
|
66
|
+
raise ArgumentError, "Arguments must be arrays"
|
67
|
+
end
|
68
|
+
|
69
|
+
unless northeast.size == 2 and southwest.size == 2
|
70
|
+
raise ArgumentError, "Arguments must have two elements"
|
71
|
+
end
|
72
|
+
|
73
|
+
unless northeast.map { |a| a.kind_of?(Numeric) }.all? \
|
74
|
+
and southwest.map { |a| a.kind_of?(Numeric) }.all?
|
75
|
+
raise ArgumentError, "Latitude and longitude must be given as numbers"
|
76
|
+
end
|
77
|
+
|
78
|
+
post_data = {
|
79
|
+
"dto" => {
|
80
|
+
"data" => {
|
81
|
+
"c" => 1,
|
82
|
+
"m" => "",
|
83
|
+
"d" => [northeast[0], southwest[0], northeast[1], southwest[1]].join("|")
|
84
|
+
},
|
85
|
+
"ut" => ""
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
headers = {
|
90
|
+
"Content-Type" => "application/json; charset=UTF-8"
|
91
|
+
}
|
92
|
+
|
93
|
+
resp, data = HTTP.post("/map/default.aspx/MapAction",
|
94
|
+
JSON.generate(post_data), headers)
|
95
|
+
|
96
|
+
begin
|
97
|
+
outer = JSON.parse(data)
|
98
|
+
raise ParseError, "Invalid JSON response" unless outer["d"]
|
99
|
+
results = JSON.parse(outer["d"])
|
100
|
+
rescue JSON::JSONError
|
101
|
+
raise ParseError, "Could not parse response JSON data"
|
102
|
+
end
|
103
|
+
|
104
|
+
raise ParseError, "Invalid JSON response" unless results["cs"]
|
105
|
+
raise TooManyResultsError if results["cs"]["count"] == 501
|
106
|
+
|
107
|
+
if results["cs"]["cc"].kind_of?(Array)
|
108
|
+
results["cs"]["cc"].map do |result|
|
109
|
+
Cache.new \
|
110
|
+
:name => result["nn"],
|
111
|
+
:code => result["gc"],
|
112
|
+
:latitude => result["lat"],
|
113
|
+
:longitude => result["lon"],
|
114
|
+
:type => CacheType.for_id(result["ctid"])
|
115
|
+
end
|
116
|
+
else
|
117
|
+
[]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Creates a new instance. The following attributes may be specified
|
46
122
|
# as parameters:
|
47
123
|
#
|
48
124
|
# * +:code+ — The cache’s GC code
|
@@ -54,7 +130,7 @@ module Geocaching
|
|
54
130
|
@data, @doc, @code, @guid = nil, nil, nil, nil
|
55
131
|
|
56
132
|
attributes.each do |key, value|
|
57
|
-
if [:code, :guid, :name, :type].include?(key)
|
133
|
+
if [:code, :guid, :name, :type, :latitude, :longitude].include?(key)
|
58
134
|
if key == :type and not value.kind_of?(CacheType)
|
59
135
|
raise TypeError, "Attribute `type' must be an instance of Geocaching::CacheType"
|
60
136
|
end
|
@@ -66,12 +142,10 @@ module Geocaching
|
|
66
142
|
end
|
67
143
|
end
|
68
144
|
|
69
|
-
#
|
145
|
+
# Fetches cache information from geocaching.com.
|
70
146
|
#
|
71
147
|
# @return [void]
|
72
148
|
# @raise [ArgumentError] Neither code nor GUID are given
|
73
|
-
# @raise [Geocaching::TimeoutError] Timeout hit
|
74
|
-
# @raise [Geocaching::HTTPError] HTTP request failed
|
75
149
|
def fetch
|
76
150
|
raise ArgumentError, "Neither code nor GUID given" unless @code or @guid
|
77
151
|
|
@@ -79,19 +153,17 @@ module Geocaching
|
|
79
153
|
@doc = Nokogiri::HTML.parse(@data)
|
80
154
|
end
|
81
155
|
|
82
|
-
#
|
156
|
+
# Returns whether information have successfully been fetched
|
83
157
|
# from geocaching.com.
|
84
158
|
#
|
85
|
-
# @return [Boolean] Have information been fetched?
|
159
|
+
# @return [Boolean] Have cache information been fetched?
|
86
160
|
def fetched?
|
87
161
|
@data and @doc
|
88
162
|
end
|
89
163
|
|
90
|
-
#
|
164
|
+
# Returns the cache’s code.
|
91
165
|
#
|
92
166
|
# @return [String] Code
|
93
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
94
|
-
# @raise [Geocaching::ExtractError] Could not extract code from website
|
95
167
|
def code
|
96
168
|
@code ||= begin
|
97
169
|
raise NotFetchedError unless fetched?
|
@@ -100,16 +172,14 @@ module Geocaching
|
|
100
172
|
if elements.size == 1 and elements.first.content =~ /(GC[A-Z0-9]+)/
|
101
173
|
HTTP.unescape($1)
|
102
174
|
else
|
103
|
-
raise
|
175
|
+
raise ParseError, "Could not extract code from website"
|
104
176
|
end
|
105
177
|
end
|
106
178
|
end
|
107
179
|
|
108
|
-
#
|
180
|
+
# Returns the cache’s Globally Unique Identifier (GUID).
|
109
181
|
#
|
110
182
|
# @return [String] GUID
|
111
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
112
|
-
# @raise [Geocaching::ExtractError] Could not extract GUID from website
|
113
183
|
def guid
|
114
184
|
@guid ||= begin
|
115
185
|
raise NotFetchedError unless fetched?
|
@@ -122,16 +192,33 @@ module Geocaching
|
|
122
192
|
end
|
123
193
|
|
124
194
|
guid || begin
|
125
|
-
raise
|
195
|
+
raise ParseError, "Could not extract GUID from website"
|
126
196
|
end
|
127
197
|
end
|
128
198
|
end
|
129
199
|
|
130
|
-
#
|
200
|
+
# Returns the cache’s ID (which is not the UUID).
|
201
|
+
#
|
202
|
+
# @return [Fixnum] ID
|
203
|
+
def id
|
204
|
+
@id ||= begin
|
205
|
+
raise NotFetchedError unless fetched?
|
206
|
+
|
207
|
+
elements = @doc.search("div.CacheDetailNavigationWidget li a[href^='/seek/log.aspx']")
|
208
|
+
|
209
|
+
if elements.size > 0 and elements.first["href"] =~ /ID=(\d+)/
|
210
|
+
@id = $1.to_i
|
211
|
+
end
|
212
|
+
|
213
|
+
@id || begin
|
214
|
+
raise ParseError, "Could not extract ID from website"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Returns the cache’s type ID.
|
131
220
|
#
|
132
221
|
# @return [Fixnum] Type ID
|
133
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
134
|
-
# @raise [Geocaching::ExtractError] Could not extract cache type ID from website
|
135
222
|
def type_id
|
136
223
|
@type_id ||= begin
|
137
224
|
raise NotFetchedError unless fetched?
|
@@ -139,25 +226,21 @@ module Geocaching
|
|
139
226
|
if @data =~ /<a.*?title="About Cache Types"><img.*?WptTypes\/(\d+)\.gif".*?<\/a>/
|
140
227
|
$1.to_i
|
141
228
|
else
|
142
|
-
raise
|
229
|
+
raise ParseError, "Could not extract cache type ID from website"
|
143
230
|
end
|
144
231
|
end
|
145
232
|
end
|
146
233
|
|
147
|
-
#
|
234
|
+
# Returns the cache’s type.
|
148
235
|
#
|
149
236
|
# @return [Geocaching::CacheType] Type
|
150
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
151
|
-
# @raise [Geocaching::ExtractError] Could not extract cache type ID from website
|
152
237
|
def type
|
153
238
|
@type ||= CacheType.for_id(type_id)
|
154
239
|
end
|
155
240
|
|
156
|
-
#
|
241
|
+
# Returns the cache’s name.
|
157
242
|
#
|
158
243
|
# @return [String] Name
|
159
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
160
|
-
# @raise [Geocaching::ExtractError] Could not extract name from website"
|
161
244
|
def name
|
162
245
|
@name ||= begin
|
163
246
|
raise NotFetchedError unless fetched?
|
@@ -166,16 +249,14 @@ module Geocaching
|
|
166
249
|
if elements.size == 1
|
167
250
|
HTTP.unescape(elements.first.content)
|
168
251
|
else
|
169
|
-
raise
|
252
|
+
raise ParseError, "Could not extract name from website"
|
170
253
|
end
|
171
254
|
end
|
172
255
|
end
|
173
256
|
|
174
|
-
#
|
257
|
+
# Returns the cache’s owner.
|
175
258
|
#
|
176
|
-
# @return [Geocaching::User]
|
177
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
178
|
-
# @raise [Geocaching::ExtractError] Could not extract info from website"
|
259
|
+
# @return [Geocaching::User] Owner
|
179
260
|
def owner
|
180
261
|
@owner ||= begin
|
181
262
|
raise NotFetchedError unless fetched?
|
@@ -184,26 +265,22 @@ module Geocaching
|
|
184
265
|
@owner_display_name = HTTP.unescape($2)
|
185
266
|
User.new(:guid => $1)
|
186
267
|
else
|
187
|
-
raise
|
268
|
+
raise ParseError, "Could not extract owner from website"
|
188
269
|
end
|
189
270
|
end
|
190
271
|
end
|
191
272
|
|
192
|
-
#
|
273
|
+
# Returns the displayed cache owner name.
|
193
274
|
#
|
194
|
-
# @return [String]
|
195
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
196
|
-
# @raise [Geocaching::ExtractError] Could not extract info from website"
|
275
|
+
# @return [String] Displayed owner name
|
197
276
|
def owner_display_name
|
198
277
|
owner unless @owner_display_name
|
199
278
|
@owner_display_name
|
200
279
|
end
|
201
280
|
|
202
|
-
#
|
281
|
+
# Returns the cache’s difficulty rating.
|
203
282
|
#
|
204
283
|
# @return [Float] Difficulty rating
|
205
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
206
|
-
# @raise [Geocaching::ExtractError] Could not extract difficulty rating from website
|
207
284
|
def difficulty
|
208
285
|
@difficulty ||= begin
|
209
286
|
raise NotFetchedError unless fetched?
|
@@ -211,16 +288,14 @@ module Geocaching
|
|
211
288
|
if @data =~ /<strong>\s*?Difficulty:<\/strong>\s*?<img.*?alt="([\d\.]{1,3}) out of 5" \/>/
|
212
289
|
$1.to_f
|
213
290
|
else
|
214
|
-
raise
|
291
|
+
raise ParseError, "Could not extract difficulty rating from website"
|
215
292
|
end
|
216
293
|
end
|
217
294
|
end
|
218
295
|
|
219
|
-
#
|
296
|
+
# Returns the cache’s terrain rating.
|
220
297
|
#
|
221
298
|
# @return [Float] Terrain rating
|
222
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
223
|
-
# @raise [Geocaching::ExtractError] Could not extract terrain rating from website
|
224
299
|
def terrain
|
225
300
|
@terrain ||= begin
|
226
301
|
raise NotFetchedError unless fetched?
|
@@ -228,16 +303,14 @@ module Geocaching
|
|
228
303
|
if @data =~ /<strong>\s+?Terrain:<\/strong>\s+?<img.*?alt="([\d\.]{1,3}) out of 5" \/>/
|
229
304
|
$1.to_f
|
230
305
|
else
|
231
|
-
raise
|
306
|
+
raise ParseError, "Could not extract terrain rating from website"
|
232
307
|
end
|
233
308
|
end
|
234
309
|
end
|
235
310
|
|
236
|
-
#
|
311
|
+
# Returns the date the cache has been hidden at.
|
237
312
|
#
|
238
313
|
# @return [Time] Hidden date
|
239
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
240
|
-
# @raise [Geocaching::ExtractError] Could not extract hidden date from website
|
241
314
|
def hidden_at
|
242
315
|
@hidden_at ||= begin
|
243
316
|
raise NotFetchedError unless fetched?
|
@@ -245,16 +318,14 @@ module Geocaching
|
|
245
318
|
if @data =~ /<strong>\s*?Hidden\s*?:\s*?<\/strong>\s*?(\d{1,2})\/(\d{1,2})\/(\d{4})/
|
246
319
|
Time.mktime($3, $1, $2)
|
247
320
|
else
|
248
|
-
raise
|
321
|
+
raise ParseError, "Could not extract hidden date from website"
|
249
322
|
end
|
250
323
|
end
|
251
324
|
end
|
252
325
|
|
253
|
-
#
|
326
|
+
# Returns the date the event has been held.
|
254
327
|
#
|
255
328
|
# @return [Time] Event date
|
256
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
257
|
-
# @raise [Geocaching::ExtractError] Could not extract event date from website
|
258
329
|
def event_date
|
259
330
|
@event_date ||= begin
|
260
331
|
raise NotFetchedError unless fetched?
|
@@ -263,16 +334,23 @@ module Geocaching
|
|
263
334
|
if @data =~ /<strong>\s*?Event Date:\s*?<\/strong>\s*?\w+, (\d+) (\w+) (\d{4})/
|
264
335
|
Time.parse([$1, $2, $3].join)
|
265
336
|
else
|
266
|
-
raise
|
337
|
+
raise ParseError, "Could not extract event date from website"
|
267
338
|
end
|
268
339
|
end
|
269
340
|
end
|
270
341
|
|
271
|
-
#
|
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+
|
272
352
|
#
|
273
353
|
# @return [Symbol] Cache container size
|
274
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
275
|
-
# @raise [Geocaching::ExtractError] Could not extract cache container size from website
|
276
354
|
def size
|
277
355
|
@size ||= begin
|
278
356
|
raise NotFetchedError unless fetched?
|
@@ -283,16 +361,14 @@ module Geocaching
|
|
283
361
|
end
|
284
362
|
|
285
363
|
size || begin
|
286
|
-
raise
|
364
|
+
raise ParseError, "Could not extract cache container size from website"
|
287
365
|
end
|
288
366
|
end
|
289
367
|
end
|
290
368
|
|
291
|
-
#
|
369
|
+
# Returns the cache’s latitude.
|
292
370
|
#
|
293
371
|
# @return [Float] Latitude
|
294
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
295
|
-
# @raise [Geocaching::ExtractError] Could not extract latitude from website
|
296
372
|
def latitude
|
297
373
|
@latitude ||= begin
|
298
374
|
raise NotFetchedError unless fetched?
|
@@ -306,16 +382,14 @@ module Geocaching
|
|
306
382
|
end
|
307
383
|
|
308
384
|
latitude || begin
|
309
|
-
raise
|
385
|
+
raise ParseError, "Could not extract latitude from website"
|
310
386
|
end
|
311
387
|
end
|
312
388
|
end
|
313
389
|
|
314
|
-
#
|
390
|
+
# Returns the cache’s longitude.
|
315
391
|
#
|
316
392
|
# @return [Float] Longitude
|
317
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
318
|
-
# @raise [Geocaching::ExtractError] Could not extract longitude from website
|
319
393
|
def longitude
|
320
394
|
@longitude ||= begin
|
321
395
|
raise NotFetchedError unless fetched?
|
@@ -329,16 +403,14 @@ module Geocaching
|
|
329
403
|
end
|
330
404
|
|
331
405
|
longitude || begin
|
332
|
-
raise
|
406
|
+
raise ParseError, "Could not extract longitude from website"
|
333
407
|
end
|
334
408
|
end
|
335
409
|
end
|
336
410
|
|
337
|
-
#
|
411
|
+
# Returns the cache’s location name (State, Country).
|
338
412
|
#
|
339
413
|
# @return [String] Location name
|
340
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
341
|
-
# @raise [Geocaching::ExtractError] Could not extract location from website
|
342
414
|
def location
|
343
415
|
@location ||= begin
|
344
416
|
raise NotFetchedError unless fetched?
|
@@ -353,15 +425,14 @@ module Geocaching
|
|
353
425
|
end
|
354
426
|
|
355
427
|
location || begin
|
356
|
-
raise
|
428
|
+
raise ParseError, "Could not extract location from website"
|
357
429
|
end
|
358
430
|
end
|
359
431
|
end
|
360
432
|
|
361
|
-
#
|
433
|
+
# Returns whether the cache is unpublished or not.
|
362
434
|
#
|
363
435
|
# @return [Boolean] Is cache unpublished?
|
364
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
365
436
|
def unpublished?
|
366
437
|
@is_unpublished ||= begin
|
367
438
|
raise NotFetchedError unless fetched?
|
@@ -369,10 +440,9 @@ module Geocaching
|
|
369
440
|
end
|
370
441
|
end
|
371
442
|
|
372
|
-
#
|
443
|
+
# Returns whether the cache has been archived or not.
|
373
444
|
#
|
374
445
|
# @return [Boolean] Has cache been archived?
|
375
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
376
446
|
def archived?
|
377
447
|
@is_archived ||= begin
|
378
448
|
raise NotFetchedError unless fetched?
|
@@ -380,10 +450,9 @@ module Geocaching
|
|
380
450
|
end
|
381
451
|
end
|
382
452
|
|
383
|
-
#
|
453
|
+
# Returns whether the cache is only viewable to Premium Member only.
|
384
454
|
#
|
385
455
|
# @return [Boolean] Is cache PM-only?
|
386
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
387
456
|
def pmonly?
|
388
457
|
@is_pmonly ||= begin
|
389
458
|
raise NotFetchedError unless fetched?
|
@@ -393,10 +462,9 @@ module Geocaching
|
|
393
462
|
end
|
394
463
|
end
|
395
464
|
|
396
|
-
#
|
465
|
+
# Returns whether the cache is currently in review.
|
397
466
|
#
|
398
467
|
# @return [Boolean] Is cache currently in review?
|
399
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
400
468
|
def in_review?
|
401
469
|
@in_review ||= begin
|
402
470
|
raise NotFetchedError unless fetched?
|
@@ -405,12 +473,9 @@ module Geocaching
|
|
405
473
|
end
|
406
474
|
end
|
407
475
|
|
408
|
-
#
|
409
|
-
# {Geocaching::Log}.
|
476
|
+
# Returns an array of the cache’s logs.
|
410
477
|
#
|
411
478
|
# @return [Geocaching::LogsArray<Geocaching::Log>] Array of logs
|
412
|
-
# @raise [Geocaching::NotFetchedError] Need to call {#fetch} first
|
413
|
-
# @raise [Geocaching::ExtractError] Could not extract logs from website
|
414
479
|
def logs
|
415
480
|
@logs ||= begin
|
416
481
|
raise NotFetchedError unless fetched?
|
@@ -419,36 +484,114 @@ module Geocaching
|
|
419
484
|
tds = @doc.search("table.Table.LogsTable > tr > td")
|
420
485
|
|
421
486
|
if tds.size == 0
|
422
|
-
raise
|
487
|
+
raise ParseError, "Could not extract logs from website"
|
423
488
|
end
|
424
489
|
|
425
490
|
tds.each do |td|
|
426
491
|
strongs = td.search("strong")
|
427
492
|
next unless strongs.size > 0
|
428
493
|
|
429
|
-
imgs
|
430
|
-
|
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']")
|
431
497
|
|
432
|
-
unless imgs.size == 1 and
|
433
|
-
raise
|
498
|
+
unless imgs.size == 1 and user_as.size == 1 and log_as.size == 1
|
499
|
+
raise ParseError, "Could not extract logs from website"
|
434
500
|
end
|
435
501
|
|
436
|
-
|
437
|
-
|
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"
|
438
510
|
|
439
|
-
unless
|
440
|
-
raise
|
511
|
+
unless log_title and user_name and user_guid and log_guid
|
512
|
+
raise ParseError, "Could not extract logs from website"
|
441
513
|
end
|
442
514
|
|
443
|
-
|
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
|
444
523
|
end
|
445
524
|
|
446
525
|
logs
|
447
526
|
end
|
448
527
|
end
|
449
528
|
|
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
|
536
|
+
end
|
537
|
+
|
538
|
+
HTTP.get("/my/watchlist.aspx?w=#{id}")
|
539
|
+
end
|
540
|
+
|
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
|
549
|
+
end
|
550
|
+
|
551
|
+
HTTP.post("/my/watchlist.aspx?ds=1&id=#{id}&action=rem", {
|
552
|
+
"ctl00$ContentBody$btnYes" => "Yes"
|
553
|
+
})
|
554
|
+
end
|
555
|
+
|
556
|
+
# Returns a Hash representation of this class.
|
557
|
+
#
|
558
|
+
# @return [Hash] Hash representing this class
|
559
|
+
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?
|
579
|
+
}
|
580
|
+
|
581
|
+
if [:event, :megaevent, :lfevent].include?(type)
|
582
|
+
hash[:event_date] = event_date
|
583
|
+
else
|
584
|
+
hash[:hidden_at] = hidden_at
|
585
|
+
end
|
586
|
+
|
587
|
+
hash
|
588
|
+
end
|
589
|
+
|
450
590
|
private
|
451
591
|
|
592
|
+
# Returns the HTTP request path.
|
593
|
+
#
|
594
|
+
# @return [String] HTTP request path
|
452
595
|
def path
|
453
596
|
"/seek/cache_details.aspx?log=y&" + (@code ? "wp=#{@code}" : "guid=#{@guid}")
|
454
597
|
end
|