geocaching 0.3.0 → 0.4.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 +6 -0
- data/geocaching.gemspec +3 -1
- data/lib/geocaching.rb +4 -1
- data/lib/geocaching/cache.rb +65 -21
- data/lib/geocaching/cache_type.rb +57 -13
- data/lib/geocaching/log.rb +34 -2
- data/lib/geocaching/log_type.rb +57 -19
- data/lib/geocaching/my_logs.rb +150 -0
- data/lib/geocaching/user.rb +251 -0
- data/lib/geocaching/version.rb +3 -0
- data/spec/cache/ape.rb +69 -0
- data/spec/cache/cito.rb +12 -0
- data/spec/cache/earthcache.rb +12 -0
- data/spec/cache/event.rb +12 -0
- data/spec/cache/letterbox.rb +12 -0
- data/spec/cache/lfevent.rb +12 -0
- data/spec/cache/locationless.rb +12 -0
- data/spec/cache/megaevent.rb +12 -0
- data/spec/cache/multi.rb +27 -15
- data/spec/cache/mystery.rb +12 -0
- data/spec/cache/traditional.rb +12 -0
- data/spec/cache/virtual.rb +12 -0
- data/spec/cache/webcam.rb +12 -0
- data/spec/cache/wherigo.rb +12 -0
- data/spec/cache_spec.rb +3 -2
- data/spec/log/announcement.rb +28 -0
- data/spec/log/announcement.txt +14 -0
- data/spec/log/archive.rb +28 -0
- data/spec/log/archive.txt +5 -0
- data/spec/log/attended.rb +28 -0
- data/spec/log/attended.txt +1 -0
- data/spec/log/coords_update.rb +28 -0
- data/spec/log/coords_update.txt +9 -0
- data/spec/log/disable.rb +28 -0
- data/spec/log/disable.txt +3 -0
- data/spec/log/dnf.rb +28 -0
- data/spec/log/dnf.txt +5 -0
- data/spec/log/enable.rb +28 -0
- data/spec/log/enable.txt +1 -0
- data/spec/log/found.rb +28 -0
- data/spec/{log_message.txt → log/found.txt} +0 -0
- data/spec/log/needs_archived.rb +28 -0
- data/spec/log/needs_archived.txt +1 -0
- data/spec/log/needs_maintenance.rb +28 -0
- data/spec/log/needs_maintenance.txt +1 -0
- data/spec/log/note.rb +28 -0
- data/spec/log/note.txt +1 -0
- data/spec/log/owner_maintenance.rb +28 -0
- data/spec/log/owner_maintenance.txt +1 -0
- data/spec/log/publish.rb +28 -0
- data/spec/log/publish.txt +1 -0
- data/spec/log/retract.rb +28 -0
- data/spec/log/retract.txt +4 -0
- data/spec/log/reviewer_note.rb +28 -0
- data/spec/log/reviewer_note.txt +3 -0
- data/spec/log/unarchive.rb +28 -0
- data/spec/log/unarchive.txt +1 -0
- data/spec/log/webcam_photo_taken.rb +28 -0
- data/spec/log/webcam_photo_taken.txt +1 -0
- data/spec/log/will_attend.rb +28 -0
- data/spec/log/will_attend.txt +1 -0
- data/spec/log_spec.rb +7 -17
- data/spec/user_spec.rb +48 -0
- metadata +44 -6
- data/lib/geocaching/mylogs.rb +0 -63
@@ -0,0 +1,150 @@
|
|
1
|
+
module Geocaching
|
2
|
+
# This class provides access to the logs the user currently logged in
|
3
|
+
# has written.
|
4
|
+
#
|
5
|
+
# mylogs = Geocaching::MyLogs.fetch
|
6
|
+
# puts "I've written #{mylogs.size} logs."
|
7
|
+
class MyLogs
|
8
|
+
# Create a new instance and call {fetch} afterwards.
|
9
|
+
#
|
10
|
+
# @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
|
+
def self.fetch
|
15
|
+
mylogs = new
|
16
|
+
mylogs.fetch
|
17
|
+
mylogs
|
18
|
+
end
|
19
|
+
|
20
|
+
# Fetch logs from geocaching.com.
|
21
|
+
#
|
22
|
+
# @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
|
+
def fetch
|
27
|
+
raise LoginError, "Need to be logged in to fetch your logs" unless HTTP.loggedin?
|
28
|
+
|
29
|
+
resp, @data = HTTP.get(path)
|
30
|
+
@doc = Nokogiri::HTML.parse(@data)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return whether logs have successfully been fetched from
|
34
|
+
# geocaching.com.
|
35
|
+
#
|
36
|
+
# @return [Boolean]
|
37
|
+
def fetched?
|
38
|
+
@data and @doc
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return an array of logs the user you’re logged in with has
|
42
|
+
# written.
|
43
|
+
#
|
44
|
+
# @return [Array<Geocaching::Log>]
|
45
|
+
# @raise [Geocaching::NotFetchedError] Need to call {fetch} first.
|
46
|
+
def logs
|
47
|
+
@logs ||= begin
|
48
|
+
raise NotFetchedError unless fetched?
|
49
|
+
|
50
|
+
rows = @doc.search("table.Table tr")
|
51
|
+
logs = []
|
52
|
+
|
53
|
+
rows.each do |row|
|
54
|
+
if info = extract_info_from_row(row) and info.size == 6
|
55
|
+
cache = Cache.new \
|
56
|
+
:guid => info[:cache_guid],
|
57
|
+
:name => info[:cache_name],
|
58
|
+
:type => info[:cache_type]
|
59
|
+
log = Log.new \
|
60
|
+
:guid => info[:log_guid],
|
61
|
+
:title => info[:log_title],
|
62
|
+
:date => info[:log_date],
|
63
|
+
:cache => cache
|
64
|
+
|
65
|
+
logs << log
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
logs
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def path
|
76
|
+
"/my/logs.aspx?s=1"
|
77
|
+
end
|
78
|
+
|
79
|
+
def extract_info_from_row(row)
|
80
|
+
info = {}
|
81
|
+
|
82
|
+
# Cache GUID
|
83
|
+
elements = row.search("td:nth-child(3) a")
|
84
|
+
if elements.size == 1
|
85
|
+
url = elements.first["href"]
|
86
|
+
if url and url =~ /guid=([a-f0-9-]{36})/
|
87
|
+
info[:cache_guid] = $1
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Cache type
|
92
|
+
elements = row.search("td:nth-child(3) a > img")
|
93
|
+
if elements.size == 1
|
94
|
+
if title = elements.first["title"]
|
95
|
+
if type = CacheType.for_title(title)
|
96
|
+
info[:cache_type] = type
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Cache name
|
102
|
+
elements = row.search("td:nth-child(3)")
|
103
|
+
if elements.size == 1
|
104
|
+
archived = elements.first.search("span.Strike.Warning > a > span > strike > font")
|
105
|
+
if archived.size == 1
|
106
|
+
name = archived.first.content
|
107
|
+
info[:cache_name] = name if name
|
108
|
+
else
|
109
|
+
disabled = elements.first.search("strike > a > span > strike")
|
110
|
+
if disabled.size == 1
|
111
|
+
name = disabled.first.content
|
112
|
+
info[:cache_name] = name if name
|
113
|
+
else
|
114
|
+
enabled = elements.first.search("a > span")
|
115
|
+
if enabled.size == 1
|
116
|
+
name = enabled.first.content
|
117
|
+
info[:cache_name] = name if name
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Log GUID
|
124
|
+
elements = row.search("td:nth-child(5) a")
|
125
|
+
if elements.size == 1
|
126
|
+
url = elements.first["href"]
|
127
|
+
if url and url =~ /LUID=([a-f0-9-]{36})/
|
128
|
+
info[:log_guid] = $1
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Log title
|
133
|
+
elements = row.search("td:first-child img")
|
134
|
+
if elements.size == 1
|
135
|
+
title = elements.first["alt"]
|
136
|
+
info[:log_title] = title if title
|
137
|
+
end
|
138
|
+
|
139
|
+
# Log date
|
140
|
+
elements = row.search("td:nth-child(2)")
|
141
|
+
if elements.size == 1
|
142
|
+
if elements.first.content =~ /(\d{2})\/(\d{2})\/(\d{4})/
|
143
|
+
info[:log_date] = Time.mktime($3, $1, $2)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
info
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
module Geocaching
|
4
|
+
# This class represents an user on geocaching.com.
|
5
|
+
#
|
6
|
+
# user = Geocaching::User.fetch(:guid => "...")
|
7
|
+
# puts user.name #=> "Jack"
|
8
|
+
class User
|
9
|
+
# Create a new instance and call the {fetch} method afterwards.
|
10
|
+
# +:guid+ must be giveb as an attribute.
|
11
|
+
#
|
12
|
+
# @param [Hash] attributes A hash of attributes
|
13
|
+
# @raise [ArgumentError] No GUID given
|
14
|
+
# @raise [Geocaching::TimeoutError] Timeout hit
|
15
|
+
# @raise [Geocaching::HTTPError] HTTP request failed
|
16
|
+
# @return [Geocaching::User]
|
17
|
+
def self.fetch(attributes)
|
18
|
+
user = new(attributes)
|
19
|
+
user.fetch
|
20
|
+
user
|
21
|
+
end
|
22
|
+
|
23
|
+
# Create a new instance. The following attributes may be specified
|
24
|
+
# as parameters:
|
25
|
+
#
|
26
|
+
# * +:guid+ — The user’s Globally Unique Identifier (GUID)
|
27
|
+
#
|
28
|
+
# @param [Hash] attributes A hash of attributes
|
29
|
+
# @raise [ArgumentError] Trying to set an unknown attribute
|
30
|
+
def initialize(attributes = {})
|
31
|
+
@data, @doc, @guid = nil, nil, nil
|
32
|
+
|
33
|
+
attributes.each do |key, value|
|
34
|
+
if [:guid].include?(key)
|
35
|
+
instance_variable_set("@#{key}", value)
|
36
|
+
else
|
37
|
+
raise ArgumentError, "Trying to set unknown attribute `#{key}'"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Fetch user information from geocaching.com.
|
43
|
+
#
|
44
|
+
# @return [void]
|
45
|
+
# @raise [ArgumentError] No GUID given
|
46
|
+
# @raise [Geocaching::TimeoutError] Timeout hit
|
47
|
+
# @raise [Geocaching::HTTPError] HTTP request failed
|
48
|
+
def fetch
|
49
|
+
raise ArgumentError, "No GUID given" unless @guid
|
50
|
+
|
51
|
+
resp, @data = HTTP.get(path)
|
52
|
+
@doc = Nokogiri::HTML.parse(@data)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return whether user information have successfully been fetched
|
56
|
+
# from geocaching.com.
|
57
|
+
#
|
58
|
+
# @return [Boolean]
|
59
|
+
def fetched?
|
60
|
+
@data and @doc
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return the user’s Globally Unique Identifier (GUID).
|
64
|
+
#
|
65
|
+
# @return [String]
|
66
|
+
def guid
|
67
|
+
@guid
|
68
|
+
end
|
69
|
+
|
70
|
+
# Return the user’s name.
|
71
|
+
#
|
72
|
+
# @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
|
+
def name
|
77
|
+
@name ||= begin
|
78
|
+
raise NotFetchedError unless fetched?
|
79
|
+
|
80
|
+
elements = @doc.search("#ctl00_ContentBody_lblUserProfile")
|
81
|
+
|
82
|
+
if elements.size == 1 and elements.first.content =~ /Profile for User|Reviewer: (.+)/
|
83
|
+
HTTP.unescape($1)
|
84
|
+
else
|
85
|
+
raise ExtractError, "Could not extract name from website"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return the user’s occupation.
|
91
|
+
#
|
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
|
96
|
+
def occupation
|
97
|
+
@occupation ||= begin
|
98
|
+
raise NotFetchedError unless fetched?
|
99
|
+
|
100
|
+
elements = @doc.search("#ctl00_ContentBody_ProfilePanel1_lblOccupationTxt")
|
101
|
+
|
102
|
+
if elements.size == 1
|
103
|
+
HTTP.unescape(elements.first.content)
|
104
|
+
else
|
105
|
+
raise ExtractError, "Could not extract occupation from website"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Return the user’s location.
|
111
|
+
#
|
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
|
116
|
+
def location
|
117
|
+
@location ||= begin
|
118
|
+
raise NotFetchedError unless fetched?
|
119
|
+
|
120
|
+
elements = @doc.search("#ctl00_ContentBody_ProfilePanel1_lblLocationTxt")
|
121
|
+
|
122
|
+
if elements.size == 1
|
123
|
+
HTTP.unescape(elements.first.content)
|
124
|
+
else
|
125
|
+
raise ExtractError, "Could not extract location from website"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Return the user’s forum title.
|
131
|
+
#
|
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
|
136
|
+
def forum_title
|
137
|
+
@forum_title ||= begin
|
138
|
+
raise NotFetchedError unless fetched?
|
139
|
+
|
140
|
+
elements = @doc.search("#ctl00_ContentBody_ProfilePanel1_lblForumTitleTxt")
|
141
|
+
|
142
|
+
if elements.size == 1
|
143
|
+
HTTP.unescape(elements.first.content)
|
144
|
+
else
|
145
|
+
raise ExtractError, "Could not extract forum title from website"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return the user’s homepage.
|
151
|
+
#
|
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
|
156
|
+
def homepage
|
157
|
+
@homepage ||= begin
|
158
|
+
raise NotFetchedError unless fetched?
|
159
|
+
|
160
|
+
elements = @doc.search("#ctl00_ContentBody_ProfilePanel1_lnkHomePage")
|
161
|
+
elements.first["href"] if elements.size == 1
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Return the user’s statuses.
|
166
|
+
#
|
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
|
171
|
+
def status
|
172
|
+
@status ||= begin
|
173
|
+
raise NotFetchedError unless fetched?
|
174
|
+
|
175
|
+
elements = @doc.search("#ctl00_ContentBody_ProfilePanel1_lblStatusText")
|
176
|
+
|
177
|
+
if elements.size == 1
|
178
|
+
HTTP.unescape(elements.first.content).split(",").map(&:strip)
|
179
|
+
else
|
180
|
+
raise ExtractError, "Could not extract status from website"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Return the user’s last visit date.
|
186
|
+
#
|
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
|
191
|
+
def last_visit
|
192
|
+
@last_visit ||= begin
|
193
|
+
raise NotFetchedError unless fetched?
|
194
|
+
|
195
|
+
elements = @doc.search("#ctl00_ContentBody_ProfilePanel1_lblLastVisitDate")
|
196
|
+
|
197
|
+
if elements.size == 1
|
198
|
+
Time.parse(elements.first.content)
|
199
|
+
else
|
200
|
+
raise ExtractError, "Could not extract last visit date from website"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Return the user’s member since date.
|
206
|
+
#
|
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
|
211
|
+
def member_since
|
212
|
+
@member_since ||= begin
|
213
|
+
raise NotFetchedError unless fetched?
|
214
|
+
|
215
|
+
elements = @doc.search("#ctl00_ContentBody_ProfilePanel1_lblMemberSinceDate")
|
216
|
+
|
217
|
+
if elements.size == 1
|
218
|
+
Time.parse(elements.first.content)
|
219
|
+
else
|
220
|
+
raise ExtractError, "Could not extract member since date from website"
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Return whether the user is a reviewer.
|
226
|
+
#
|
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
|
231
|
+
def reviewer?
|
232
|
+
status.include?("Reviewer")
|
233
|
+
end
|
234
|
+
|
235
|
+
# Return whether the user is a premium member.
|
236
|
+
#
|
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
|
241
|
+
def premium_member?
|
242
|
+
status.include?("Premium Member")
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def path
|
248
|
+
"/profile/?guid=#{guid}"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
data/spec/cache/ape.rb
CHANGED
@@ -1,2 +1,71 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
describe "Geocaching::Cache for 7d120ae2-855c-430d-9c14-3d7427de4bdd (Project A.P.E. Cache)" do
|
4
|
+
before :all do
|
5
|
+
@cache = Geocaching::Cache.fetch(:guid => "7d120ae2-855c-430d-9c14-3d7427de4bdd")
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should return the correct GC code" do
|
9
|
+
@cache.code.should == "GC1169"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return the correct name" do
|
13
|
+
@cache.name.should == "Mission 9: Tunnel of Light"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return the correct displayed owner name" do
|
17
|
+
@cache.owner_display_name.should == "Project APE (maintained by Moun10Bike)"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should return the correct owner GUID" do
|
21
|
+
@cache.owner.guid == "47975ebd-efc1-40d6-94c1-b379a6221a8f"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return the correct cache type" do
|
25
|
+
@cache.type.to_sym.should == :ape
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return the correct size" do
|
29
|
+
@cache.size.should == :large
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return the correct hidden date" do
|
33
|
+
@cache.hidden_at.should == Time.mktime(2001, 7, 18)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should return the correct difficulty rating" do
|
37
|
+
@cache.difficulty.should == 1
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return the correct terrain rating" do
|
41
|
+
@cache.terrain.should == 3
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return the correct latitude" do
|
45
|
+
@cache.latitude.should == 47.3919
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should return the correct longitude" do
|
49
|
+
@cache.longitude.should == -121.455083
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return the correct location" do
|
53
|
+
@cache.location.should == "Washington, United States"
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return a plausible number of logs" do
|
57
|
+
@cache.logs.size.should >= 3566
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should return cache has not been archived" do
|
61
|
+
@cache.archived?.should == false
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should return cache is not PM-only" do
|
65
|
+
@cache.pmonly?.should == false
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should return cache is not in review" do
|
69
|
+
@cache.in_review?.should == false
|
70
|
+
end
|
71
|
+
end
|