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.
- data/README.markdown +11 -57
- data/lib/geocaching.rb +13 -86
- data/lib/geocaching/cache.rb +79 -557
- data/lib/geocaching/cache_type.rb +39 -78
- data/lib/geocaching/container_type.rb +45 -0
- data/lib/geocaching/errors.rb +7 -0
- data/lib/geocaching/log.rb +49 -180
- data/lib/geocaching/log_type.rb +51 -81
- data/lib/geocaching/parsers/cache_gpx.rb +42 -0
- data/lib/geocaching/parsers/cache_gpx_additional_waypoint.rb +100 -0
- data/lib/geocaching/parsers/cache_gpx_cache_waypoint.rb +234 -0
- data/lib/geocaching/parsers/cache_simple.rb +219 -0
- data/lib/geocaching/parsers/log.rb +100 -0
- data/lib/geocaching/parsers/trackable.rb +107 -0
- data/lib/geocaching/session.rb +119 -0
- data/lib/geocaching/session/caches.rb +61 -0
- data/lib/geocaching/session/logs.rb +17 -0
- data/lib/geocaching/session/trackables.rb +26 -0
- data/lib/geocaching/trackable.rb +77 -0
- data/lib/geocaching/trackable_type.rb +17 -0
- data/lib/geocaching/user.rb +10 -202
- data/lib/geocaching/waypoint.rb +51 -0
- data/lib/geocaching/waypoint_type.rb +41 -0
- metadata +21 -25
- data/lib/geocaching/http.rb +0 -248
- data/lib/geocaching/my_logs.rb +0 -147
- data/lib/geocaching/version.rb +0 -5
- data/lib/geocaching/watchlist.rb +0 -93
@@ -0,0 +1,100 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
module Geocaching
|
7
|
+
module Parsers
|
8
|
+
class Log
|
9
|
+
def initialize(doc)
|
10
|
+
@doc = doc
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse
|
14
|
+
logs = []
|
15
|
+
|
16
|
+
@doc.xpath("//xmlns:CacheLogDataSet/xmlns:CacheLog").each do |cache_log_node|
|
17
|
+
log = Geocaching::Log.new
|
18
|
+
|
19
|
+
private_methods.each do |method|
|
20
|
+
if method.to_s =~ /^parse_([a-z_]+)$/
|
21
|
+
value = send(method, cache_log_node)
|
22
|
+
log.send("#{$1}=", value) unless value.nil?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
logs << log
|
27
|
+
end
|
28
|
+
|
29
|
+
logs
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parse_logged_at(cache_log_node)
|
35
|
+
if node = cache_log_node.xpath("./xmlns:DateLogged").first
|
36
|
+
begin
|
37
|
+
time = Time.parse(node.text)
|
38
|
+
Time.mktime(time.year, time.month, time.day)
|
39
|
+
rescue ArgumentError
|
40
|
+
raise ParseError, "Got an invalid log date from API"
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise ParseError, "Could not parse log date from XML"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse_created_at(cache_log_node)
|
48
|
+
if node = cache_log_node.xpath("./xmlns:Created").first
|
49
|
+
begin
|
50
|
+
time = Time.parse(node.text)
|
51
|
+
Time.mktime(time.year, time.month, time.day)
|
52
|
+
rescue ArgumentError
|
53
|
+
raise ParseError, "Got an invalid creation date from API"
|
54
|
+
end
|
55
|
+
else
|
56
|
+
raise ParseError, "Could not parse creation date from XML"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_type(cache_log_node)
|
61
|
+
if node = cache_log_node.xpath("./xmlns:LogTypeName").first
|
62
|
+
if log_type = LogType.from_name(node.text)
|
63
|
+
log_type
|
64
|
+
else
|
65
|
+
raise ParseError, "Got an unknown log type from API"
|
66
|
+
end
|
67
|
+
else
|
68
|
+
raise ParseError, "Could not parse log type from XML"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_user(cache_log_node)
|
73
|
+
user_guid_node = cache_log_node.xpath("./xmlns:UserGUID").first
|
74
|
+
user_name_node = cache_log_node.xpath("./xmlns:UserName").first
|
75
|
+
|
76
|
+
if user_guid_node and user_name_node
|
77
|
+
User.new(:guid => user_guid_node.text, :name => user_name_node.text)
|
78
|
+
else
|
79
|
+
raise ParseError, "Could not parse user from XML"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def parse_guid(cache_log_node)
|
84
|
+
if node = cache_log_node.xpath("./xmlns:GUID").first
|
85
|
+
node.text
|
86
|
+
else
|
87
|
+
raise ParseError, "Could not parse GUID from XML"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def parse_message(cache_log_node)
|
92
|
+
if node = cache_log_node.xpath("./xmlns:Note").first
|
93
|
+
node.text
|
94
|
+
else
|
95
|
+
raise ParseError, "Could not parse message from XML"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "nokogiri"
|
4
|
+
require "time"
|
5
|
+
|
6
|
+
module Geocaching
|
7
|
+
module Parsers
|
8
|
+
class Trackable
|
9
|
+
def initialize(doc)
|
10
|
+
@doc = doc
|
11
|
+
end
|
12
|
+
|
13
|
+
def parse
|
14
|
+
trackables = []
|
15
|
+
|
16
|
+
@doc.xpath("//xmlns:TravelBugDataSet/xmlns:TravelBug").each do |travel_bug_node|
|
17
|
+
trackable = Geocaching::Trackable.new
|
18
|
+
|
19
|
+
private_methods.each do |method|
|
20
|
+
if method.to_s =~ /^parse_([a-z_]+)$/
|
21
|
+
value = send(method, travel_bug_node)
|
22
|
+
trackable.send("#{$1}=", value) unless value.nil?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
trackables << trackable
|
27
|
+
end
|
28
|
+
|
29
|
+
trackables
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def parse_code(travel_bug_node)
|
35
|
+
if node = travel_bug_node.xpath("./xmlns:TrackingCode").first
|
36
|
+
node.text
|
37
|
+
else
|
38
|
+
raise ParseError, "Could not parse trackable code from XML"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def parse_guid(travel_bug_node)
|
43
|
+
if node = travel_bug_node.xpath("./xmlns:GUID").first
|
44
|
+
node.text
|
45
|
+
else
|
46
|
+
raise ParseError, "Could not parse trackable GUID from XML"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_name(travel_bug_node)
|
51
|
+
if node = travel_bug_node.xpath("./xmlns:Name").first
|
52
|
+
node.text
|
53
|
+
else
|
54
|
+
raise ParseError, "Could not parse trackable name from XML"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse_goal(travel_bug_node)
|
59
|
+
if node = travel_bug_node.xpath("./xmlns:Goal").first
|
60
|
+
node.text
|
61
|
+
else
|
62
|
+
raise ParseError, "Could not parse trackable goal from XML"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def parse_description(travel_bug_node)
|
67
|
+
if node = travel_bug_node.xpath("./xmlns:Description").first
|
68
|
+
node.text
|
69
|
+
else
|
70
|
+
raise ParseError, "Could not parse trackable description from XML"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_current_cache(travel_bug_node)
|
75
|
+
if node = travel_bug_node.xpath("./xmlns:CurrentCacheCode").first
|
76
|
+
Cache.new(:code => node.text) unless node.text.empty?
|
77
|
+
else
|
78
|
+
raise ParseError, "Could not parse trackable's current cache from XML"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def parse_current_user(travel_bug_node)
|
83
|
+
if node = travel_bug_node.xpath("./xmlns:CurrentHolderUserName").first
|
84
|
+
User.new(:name => node.text) unless node.text.empty?
|
85
|
+
else
|
86
|
+
raise ParseError, "Could not parse trackable's current user from XML"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def parse_owner(travel_bug_node)
|
91
|
+
if node = travel_bug_node.xpath("./xmlns:OwnerUserName").first
|
92
|
+
User.new(:name => node.text)
|
93
|
+
else
|
94
|
+
raise ParseError, "Could not parse trackable owner from XML"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def parse_type(travel_bug_node)
|
99
|
+
if node = travel_bug_node.xpath("./xmlns:TravelBugTypeName").first
|
100
|
+
TrackableType.new(node.text)
|
101
|
+
else
|
102
|
+
raise ParseError, "Could not parse trackable type from XML"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "cgi"
|
4
|
+
require "net/http"
|
5
|
+
|
6
|
+
require "geocaching/session/caches"
|
7
|
+
require "geocaching/session/logs"
|
8
|
+
require "geocaching/session/trackables"
|
9
|
+
|
10
|
+
module Geocaching
|
11
|
+
class Session
|
12
|
+
class Response
|
13
|
+
attr_reader :data, :doc
|
14
|
+
|
15
|
+
def initialize(data)
|
16
|
+
@data = data
|
17
|
+
@doc = Nokogiri::XML.parse(@data, nil, "utf-8")
|
18
|
+
rescue
|
19
|
+
raise ParseError, "Could not parse response XML"
|
20
|
+
end
|
21
|
+
|
22
|
+
def status
|
23
|
+
@status ||= begin
|
24
|
+
if status_code_node = @doc.xpath("//xmlns:StatusDataSet/xmlns:Status/xmlns:StatusCode").first
|
25
|
+
status_code_node.text
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attr_reader :username, :password, :license_key, :session_token
|
32
|
+
attr_accessor :timeout
|
33
|
+
|
34
|
+
API_HOST = "api.groundspeak.com"
|
35
|
+
API_BASE_PATH = "/mango/Services.asmx"
|
36
|
+
|
37
|
+
STATUS_SET_SESSION_TOKEN_XPATH = "//xmlns:StatusDataSet/xmlns:Status/xmlns:SessionGUID"
|
38
|
+
SESSION_TOKEN_XPATH = "//xmlns:SessionDataSet2/xmlns:Session2/xmlns:SessionGuid"
|
39
|
+
|
40
|
+
def self.open(username, password, license_key = nil)
|
41
|
+
session = new(username, password, license_key)
|
42
|
+
session.open
|
43
|
+
session
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(username, password, license_key = nil)
|
47
|
+
@username = username
|
48
|
+
@password = password
|
49
|
+
@license_key = license_key || "e0dc6788-c880-4d3c-8903-3e2230650281"
|
50
|
+
@timeout = 15
|
51
|
+
end
|
52
|
+
|
53
|
+
def open
|
54
|
+
@http ||= Net::HTTP.new(API_HOST, 443)
|
55
|
+
@http.use_ssl = true
|
56
|
+
|
57
|
+
response = request("OpenSession", {
|
58
|
+
"userName" => @username,
|
59
|
+
"password" => @password,
|
60
|
+
"licenseKey" => @license_key
|
61
|
+
})
|
62
|
+
|
63
|
+
if token_node = response.doc.xpath(STATUS_SET_SESSION_TOKEN_XPATH).first
|
64
|
+
@session_token = token_node.text
|
65
|
+
elsif token_node = response.doc.xpath(SESSION_TOKEN_XPATH).first
|
66
|
+
@session_token = token_node.text
|
67
|
+
else
|
68
|
+
raise ParseError, "Could not parse response from OpenSession"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def open?
|
73
|
+
@session_token != nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def close
|
77
|
+
response = request("CloseSession", {
|
78
|
+
"sessionToken" => @session_token,
|
79
|
+
"licenseKey" => @license_key
|
80
|
+
})
|
81
|
+
|
82
|
+
if response.status and response.status == "Ok"
|
83
|
+
@session_token = nil
|
84
|
+
end
|
85
|
+
|
86
|
+
!open?
|
87
|
+
end
|
88
|
+
|
89
|
+
def request(action, params = {})
|
90
|
+
resp, data = nil, nil
|
91
|
+
|
92
|
+
query_string = params.map { |(k, v)|
|
93
|
+
"#{CGI::escape(k.to_s)}=#{CGI::escape(v.to_s)}"
|
94
|
+
}.join("&")
|
95
|
+
|
96
|
+
path = "#{API_BASE_PATH}/#{action}?#{query_string}"
|
97
|
+
|
98
|
+
begin
|
99
|
+
Timeout::timeout(@timeout) do
|
100
|
+
resp, data = @http.get(path)
|
101
|
+
end
|
102
|
+
rescue Timeout::Error
|
103
|
+
raise TimeoutError, "Timeout hit for action #{action}"
|
104
|
+
rescue => e
|
105
|
+
raise HTTPError, e.message
|
106
|
+
end
|
107
|
+
|
108
|
+
unless resp.kind_of?(Net::HTTPSuccess)
|
109
|
+
raise HTTPError, "Got a non-successful HTTP response for action #{action}"
|
110
|
+
end
|
111
|
+
|
112
|
+
Response.new(data)
|
113
|
+
end
|
114
|
+
|
115
|
+
include Geocaching::Session::Caches
|
116
|
+
include Geocaching::Session::Logs
|
117
|
+
include Geocaching::Session::Trackables
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Geocaching
|
4
|
+
class Session
|
5
|
+
module Caches
|
6
|
+
def get_cache_by_code(code, options = {})
|
7
|
+
schema = case options[:format]
|
8
|
+
when :simple
|
9
|
+
"CacheSimpleDataSet.xsd"
|
10
|
+
when :gpx
|
11
|
+
"CacheGPXDataSet.xsd"
|
12
|
+
else
|
13
|
+
"CacheSimpleDataSet.xsd"
|
14
|
+
end
|
15
|
+
|
16
|
+
response = request("GetCacheByCacheCode", {
|
17
|
+
"sessionToken" => session_token,
|
18
|
+
"schemaName" => schema,
|
19
|
+
"cacheCode" => code
|
20
|
+
})
|
21
|
+
|
22
|
+
case schema
|
23
|
+
when "CacheSimpleDataSet.xsd"
|
24
|
+
caches = Cache.from_xml(response.doc)
|
25
|
+
caches.first if caches.size == 1
|
26
|
+
when "CacheGPXDataSet.xsd"
|
27
|
+
Cache.from_gpx(response.doc)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_caches_by_point(latitude, longitude, options = {})
|
32
|
+
response = request("GetCachesByPoint", {
|
33
|
+
"sessionToken" => session_token,
|
34
|
+
"schemaName" => "CacheSimpleDataSet.xsd",
|
35
|
+
"cacheTypeNamesList" => nil,
|
36
|
+
"latitude" => latitude,
|
37
|
+
"longitude" => longitude,
|
38
|
+
"radiusMiles" => (options[:radius] || 10) * 0.621371192,
|
39
|
+
"startPos" => options[:start_pos] || 0,
|
40
|
+
"endPos" => options[:end_pos] || 500
|
41
|
+
})
|
42
|
+
|
43
|
+
Cache.from_xml(response.doc)
|
44
|
+
end
|
45
|
+
|
46
|
+
def get_caches_by_postal_code(postal_code, options = {})
|
47
|
+
response = request("GetCachesByPostalCode", {
|
48
|
+
"sessionToken" => session_token,
|
49
|
+
"schemaName" => "CacheSimpleDataSet.xsd",
|
50
|
+
"cacheTypeNamesList" => nil,
|
51
|
+
"postalCode" => postal_code,
|
52
|
+
"radiusMiles" => (options[:radius] || 10) * 0.621371192,
|
53
|
+
"startPos" => options[:start_pos] || 0,
|
54
|
+
"endPos" => options[:end_pos] || 500
|
55
|
+
})
|
56
|
+
|
57
|
+
Cache.from_xml(response.doc)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Geocaching
|
4
|
+
class Session
|
5
|
+
module Logs
|
6
|
+
def get_logs_for_cache_by_cache_code(cache_code)
|
7
|
+
response = request("GetCacheLogsByCacheCode", {
|
8
|
+
"sessionToken" => session_token,
|
9
|
+
"cacheCode" => cache_code,
|
10
|
+
"cacheLogTypeNames" => nil
|
11
|
+
})
|
12
|
+
|
13
|
+
Log.from_xml(response.doc)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Geocaching
|
4
|
+
class Session
|
5
|
+
module Trackables
|
6
|
+
def get_trackable_by_code(trackable_code)
|
7
|
+
response = request("GetTravelBugByTrackingNumber", {
|
8
|
+
"sessionToken" => session_token,
|
9
|
+
"trackingNumber" => trackable_code
|
10
|
+
})
|
11
|
+
|
12
|
+
trackables = Trackable.from_xml(response.doc)
|
13
|
+
trackables.first if trackables.size == 1
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_trackables_in_cache_by_cache_code(cache_code)
|
17
|
+
response = request("GetTravelBugsByCacheCode", {
|
18
|
+
"sessionToken" => session_token,
|
19
|
+
"cacheCode" => cache_code
|
20
|
+
})
|
21
|
+
|
22
|
+
Trackable.from_xml(response.doc)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "geocaching/parsers/trackable"
|
4
|
+
|
5
|
+
module Geocaching
|
6
|
+
class Trackable
|
7
|
+
attr_accessor :code
|
8
|
+
attr_accessor :guid
|
9
|
+
attr_accessor :name
|
10
|
+
attr_accessor :goal
|
11
|
+
attr_accessor :description
|
12
|
+
attr_reader :type
|
13
|
+
attr_reader :owner
|
14
|
+
attr_reader :current_cache
|
15
|
+
attr_reader :current_user
|
16
|
+
|
17
|
+
def self.from_xml(doc)
|
18
|
+
Parsers::Trackable.new(doc).parse
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(attributes = {})
|
22
|
+
attributes.each do |key, value|
|
23
|
+
if respond_to?("#{key}=")
|
24
|
+
send("#{key}=", value)
|
25
|
+
else
|
26
|
+
raise ArgumentError, "Unknown attribute `#{key}'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def type=(trackable_type)
|
32
|
+
if trackable_type.kind_of?(Geocaching::TrackableType)
|
33
|
+
@type = trackable_type
|
34
|
+
else
|
35
|
+
raise TypeError, "`type' must be an instance of Geocaching::TrackableType"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def owner=(user)
|
40
|
+
if user.kind_of?(Geocaching::User)
|
41
|
+
@owner = user
|
42
|
+
else
|
43
|
+
raise TypeError, "`owner' must be an instance of Geocaching::User"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def current_cache=(cache)
|
48
|
+
if cache.kind_of?(Geocaching::Cache)
|
49
|
+
@current_cache = cache
|
50
|
+
else
|
51
|
+
raise TypeError, "`current_cache' must be an instance of Geocaching::Cache"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def current_user=(user)
|
56
|
+
if user.kind_of?(Geocaching::User)
|
57
|
+
@current_user = user
|
58
|
+
else
|
59
|
+
raise TypeError, "`current_user' must be an instance of Geocaching::User"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_hash
|
64
|
+
{
|
65
|
+
:code => code,
|
66
|
+
:guid => guid,
|
67
|
+
:name => name,
|
68
|
+
:type => type,
|
69
|
+
:goal => goal,
|
70
|
+
:description => description,
|
71
|
+
:owner => owner,
|
72
|
+
:current_cache => current_cache,
|
73
|
+
:current_user => current_user
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|