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.
@@ -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