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.
Files changed (65) hide show
  1. data/README.markdown +6 -0
  2. data/geocaching.gemspec +3 -1
  3. data/lib/geocaching.rb +4 -1
  4. data/lib/geocaching/cache.rb +65 -21
  5. data/lib/geocaching/cache_type.rb +57 -13
  6. data/lib/geocaching/log.rb +34 -2
  7. data/lib/geocaching/log_type.rb +57 -19
  8. data/lib/geocaching/my_logs.rb +150 -0
  9. data/lib/geocaching/user.rb +251 -0
  10. data/lib/geocaching/version.rb +3 -0
  11. data/spec/cache/ape.rb +69 -0
  12. data/spec/cache/cito.rb +12 -0
  13. data/spec/cache/earthcache.rb +12 -0
  14. data/spec/cache/event.rb +12 -0
  15. data/spec/cache/letterbox.rb +12 -0
  16. data/spec/cache/lfevent.rb +12 -0
  17. data/spec/cache/locationless.rb +12 -0
  18. data/spec/cache/megaevent.rb +12 -0
  19. data/spec/cache/multi.rb +27 -15
  20. data/spec/cache/mystery.rb +12 -0
  21. data/spec/cache/traditional.rb +12 -0
  22. data/spec/cache/virtual.rb +12 -0
  23. data/spec/cache/webcam.rb +12 -0
  24. data/spec/cache/wherigo.rb +12 -0
  25. data/spec/cache_spec.rb +3 -2
  26. data/spec/log/announcement.rb +28 -0
  27. data/spec/log/announcement.txt +14 -0
  28. data/spec/log/archive.rb +28 -0
  29. data/spec/log/archive.txt +5 -0
  30. data/spec/log/attended.rb +28 -0
  31. data/spec/log/attended.txt +1 -0
  32. data/spec/log/coords_update.rb +28 -0
  33. data/spec/log/coords_update.txt +9 -0
  34. data/spec/log/disable.rb +28 -0
  35. data/spec/log/disable.txt +3 -0
  36. data/spec/log/dnf.rb +28 -0
  37. data/spec/log/dnf.txt +5 -0
  38. data/spec/log/enable.rb +28 -0
  39. data/spec/log/enable.txt +1 -0
  40. data/spec/log/found.rb +28 -0
  41. data/spec/{log_message.txt → log/found.txt} +0 -0
  42. data/spec/log/needs_archived.rb +28 -0
  43. data/spec/log/needs_archived.txt +1 -0
  44. data/spec/log/needs_maintenance.rb +28 -0
  45. data/spec/log/needs_maintenance.txt +1 -0
  46. data/spec/log/note.rb +28 -0
  47. data/spec/log/note.txt +1 -0
  48. data/spec/log/owner_maintenance.rb +28 -0
  49. data/spec/log/owner_maintenance.txt +1 -0
  50. data/spec/log/publish.rb +28 -0
  51. data/spec/log/publish.txt +1 -0
  52. data/spec/log/retract.rb +28 -0
  53. data/spec/log/retract.txt +4 -0
  54. data/spec/log/reviewer_note.rb +28 -0
  55. data/spec/log/reviewer_note.txt +3 -0
  56. data/spec/log/unarchive.rb +28 -0
  57. data/spec/log/unarchive.txt +1 -0
  58. data/spec/log/webcam_photo_taken.rb +28 -0
  59. data/spec/log/webcam_photo_taken.txt +1 -0
  60. data/spec/log/will_attend.rb +28 -0
  61. data/spec/log/will_attend.txt +1 -0
  62. data/spec/log_spec.rb +7 -17
  63. data/spec/user_spec.rb +48 -0
  64. metadata +44 -6
  65. 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
@@ -0,0 +1,3 @@
1
+ module Geocaching
2
+ VERSION = "0.4.0"
3
+ end
@@ -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