booker_api 0.0.1

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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/lib/booker/booker.rb +5 -0
  3. data/lib/booker/business_client.rb +22 -0
  4. data/lib/booker/business_rest.rb +130 -0
  5. data/lib/booker/client.rb +212 -0
  6. data/lib/booker/common_rest.rb +49 -0
  7. data/lib/booker/config/booker_country_ids_to_iso_codes.yml +269 -0
  8. data/lib/booker/customer_client.rb +17 -0
  9. data/lib/booker/customer_rest.rb +169 -0
  10. data/lib/booker/errors.rb +24 -0
  11. data/lib/booker/generic_token_store.rb +25 -0
  12. data/lib/booker/helpers/active_support_helper.rb +102 -0
  13. data/lib/booker/helpers/logging_helper.rb +11 -0
  14. data/lib/booker/models/address.rb +12 -0
  15. data/lib/booker/models/appointment.rb +77 -0
  16. data/lib/booker/models/appointment_treatment.rb +51 -0
  17. data/lib/booker/models/available_time.rb +13 -0
  18. data/lib/booker/models/business_type.rb +5 -0
  19. data/lib/booker/models/category.rb +5 -0
  20. data/lib/booker/models/class_instance.rb +25 -0
  21. data/lib/booker/models/country.rb +19 -0
  22. data/lib/booker/models/current_price.rb +5 -0
  23. data/lib/booker/models/customer.rb +52 -0
  24. data/lib/booker/models/customer_2.rb +5 -0
  25. data/lib/booker/models/customer_record_type.rb +5 -0
  26. data/lib/booker/models/discount.rb +5 -0
  27. data/lib/booker/models/dynamic_price.rb +11 -0
  28. data/lib/booker/models/employee.rb +10 -0
  29. data/lib/booker/models/feature_settings.rb +7 -0
  30. data/lib/booker/models/final_total.rb +5 -0
  31. data/lib/booker/models/gender.rb +5 -0
  32. data/lib/booker/models/itinerary_time_slot.rb +7 -0
  33. data/lib/booker/models/itinerary_time_slots_list.rb +7 -0
  34. data/lib/booker/models/location.rb +23 -0
  35. data/lib/booker/models/location_day_schedule.rb +18 -0
  36. data/lib/booker/models/model.rb +150 -0
  37. data/lib/booker/models/multi_service_availability_result.rb +7 -0
  38. data/lib/booker/models/notification_settings.rb +12 -0
  39. data/lib/booker/models/online_booking_settings.rb +23 -0
  40. data/lib/booker/models/original_price.rb +5 -0
  41. data/lib/booker/models/payment_method.rb +5 -0
  42. data/lib/booker/models/preferred_staff_gender.rb +5 -0
  43. data/lib/booker/models/price.rb +8 -0
  44. data/lib/booker/models/receipt_display_price.rb +5 -0
  45. data/lib/booker/models/room.rb +12 -0
  46. data/lib/booker/models/shipping_address.rb +5 -0
  47. data/lib/booker/models/source.rb +5 -0
  48. data/lib/booker/models/spa.rb +5 -0
  49. data/lib/booker/models/spa_employee_availability_search_item.rb +11 -0
  50. data/lib/booker/models/status.rb +5 -0
  51. data/lib/booker/models/sub_category.rb +5 -0
  52. data/lib/booker/models/tag_price.rb +5 -0
  53. data/lib/booker/models/teacher.rb +5 -0
  54. data/lib/booker/models/teacher_2.rb +5 -0
  55. data/lib/booker/models/time_zone.rb +8 -0
  56. data/lib/booker/models/treatment.rb +19 -0
  57. data/lib/booker/models/treatment_time_slot.rb +5 -0
  58. data/lib/booker/models/type.rb +8 -0
  59. data/lib/booker/models/user.rb +73 -0
  60. data/lib/booker/version.rb +3 -0
  61. data/lib/booker_api.rb +97 -0
  62. metadata +216 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 834274a062ffb044410b9ccb07bb89c9c8f8c1ee
4
+ data.tar.gz: e2e8c83d3a7a653ed77da005d63991a58d76533b
5
+ SHA512:
6
+ metadata.gz: dcc528461d36104babb6fadab2ecc7995ad86b770dfe560143c3a8664fb671294ab8952973769f3533fd7241ac2d6e114124d26736a885490d8cfea62e439b71
7
+ data.tar.gz: 6537a29844b3cefefd97a397b65545bdd8dc39f61d881cdda6ad1387363e7186079aa4dbf00e11f1d3989f37987cb64d144f2540abb76432c71f982aec41f12c
@@ -0,0 +1,5 @@
1
+ module Booker
2
+ def self.config
3
+ @config ||= {}
4
+ end
5
+ end
@@ -0,0 +1,22 @@
1
+ module Booker
2
+ class BusinessClient < Client
3
+ include Booker::BusinessREST
4
+
5
+ ACCESS_TOKEN_HTTP_METHOD = :post
6
+ ACCESS_TOKEN_ENDPOINT = '/accountlogin'.freeze
7
+
8
+ attr_accessor :booker_account_name, :booker_username, :booker_password
9
+
10
+ def env_base_url_key; 'BOOKER_BUSINESS_SERVICE_URL'; end
11
+
12
+ def default_base_url; 'https://apicurrent-app.booker.ninja/webservice4/json/BusinessService.svc'; end
13
+
14
+ def access_token_options
15
+ super.merge!(
16
+ 'AccountName' => self.booker_account_name,
17
+ 'UserName' => self.booker_username,
18
+ 'Password' => self.booker_password
19
+ )
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,130 @@
1
+ module Booker
2
+ module BusinessREST
3
+ include CommonREST
4
+
5
+ def get_logged_in_user
6
+ response = get('/user', build_params)
7
+ result = Booker::Models::User.from_hash(response['User'])
8
+ result.LocationID = response['LocationID']
9
+ result.BrandID = response['BrandID']
10
+ result
11
+ end
12
+
13
+ def get_location_day_schedules(booker_location_id:, params: {})
14
+ # Booker requires fromDate and toDate for JSON API, but does not use them when getDefaultDaySchedule is true
15
+ # So datetime used for these fields does not matter
16
+ random_datetime = Booker::Models::Model.time_to_booker_datetime(Time.now)
17
+
18
+ additional_params = {'getDefaultDaySchedule' => true, 'fromDate' => random_datetime, 'toDate' => random_datetime}
19
+ response = get("/location/#{booker_location_id}/schedule", build_params(additional_params, params))
20
+ response['LocationDaySchedules'].map { |sched| Booker::Models::LocationDaySchedule.from_hash(sched) }
21
+ end
22
+
23
+ def find_locations(params: {})
24
+ paginated_request(
25
+ method: :post,
26
+ path: '/locations',
27
+ params: build_params({}, params, true),
28
+ model: Booker::Models::Location
29
+ )
30
+ end
31
+
32
+ def find_employees(booker_location_id:, fetch_all: true, params: {})
33
+ paginated_request(
34
+ method: :post,
35
+ path: '/employees',
36
+ params: build_params({'LocationID' => booker_location_id}, params, true),
37
+ model: Booker::Models::Employee,
38
+ fetch_all: fetch_all
39
+ )
40
+ end
41
+
42
+ def find_treatments(booker_location_id:, fetch_all: true, params: {})
43
+ paginated_request(
44
+ method: :post,
45
+ path: '/treatments',
46
+ params: build_params({'LocationID' => booker_location_id}, params, true),
47
+ model: Booker::Models::Treatment,
48
+ fetch_all: fetch_all
49
+ )
50
+ end
51
+
52
+ def find_customers(booker_location_id:, fetch_all: true, params: {})
53
+ additional_params = {
54
+ 'FilterByExactLocationID' => true,
55
+ 'LocationID' => booker_location_id,
56
+ 'CustomerRecordType' => 1,
57
+ }
58
+
59
+ paginated_request(
60
+ method: :post,
61
+ path: '/customers',
62
+ params: build_params(additional_params, params, true),
63
+ model: Booker::Models::Customer,
64
+ fetch_all: fetch_all
65
+ )
66
+ end
67
+
68
+ def find_appointments(booker_location_id:, start_date:, end_date:, fetch_all: true, params: {})
69
+ additional_params = {
70
+ 'LocationID' => booker_location_id,
71
+ 'FromStartDate' => start_date.to_date,
72
+ 'ToStartDate' => end_date.to_date
73
+ }
74
+
75
+ paginated_request(
76
+ method: :post,
77
+ path: '/appointments',
78
+ params: build_params(additional_params, params, true),
79
+ model: Booker::Models::Appointment,
80
+ fetch_all: fetch_all
81
+ )
82
+ end
83
+
84
+ def create_special(booker_location_id:, start_date:, end_date:, coupon_code:, name:, params: {})
85
+ post('/special', build_params({
86
+ 'LocationID' => booker_location_id,
87
+ 'ApplicableStartDate' => start_date.in_time_zone,
88
+ 'ApplicableEndDate' => end_date.in_time_zone,
89
+ 'CouponCode' => coupon_code,
90
+ 'Name' => name
91
+ }, params))
92
+ end
93
+
94
+ def get_location_notification_settings(booker_location_id:)
95
+ response = get "/location/#{booker_location_id}/notification_settings", build_params
96
+ Booker::Models::NotificationSettings.from_hash response['NotificationSettings']
97
+ end
98
+
99
+ def update_location_notification_settings(booker_location_id:, send_appointment_reminders:)
100
+ put "/location/#{booker_location_id}/notification_settings", build_params({
101
+ NotificationSettings: {
102
+ SendAppointmentReminders: send_appointment_reminders
103
+ }
104
+ })
105
+ end
106
+
107
+ def get_location_feature_settings(booker_location_id:)
108
+ response = get "/location/#{booker_location_id}/feature_settings", build_params
109
+ Booker::Models::FeatureSettings.from_hash response['FeatureSettings']
110
+ end
111
+
112
+ def get_appointment(appointment_id)
113
+ get "/appointment/#{appointment_id}", build_params
114
+ end
115
+
116
+ def cancel_appointment(appointment_id)
117
+ put "/appointment/cancel", build_params({
118
+ 'ID' => appointment_id
119
+ })
120
+ end
121
+
122
+ def place_order(order_id, amount)
123
+ post "/order/#{order_id}/place_order", build_params
124
+ end
125
+
126
+ def take_deposit(order_id)
127
+ post "/order/#{order_id}/deposit", build_params
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,212 @@
1
+ module Booker
2
+ class Client
3
+ attr_accessor :base_url, :client_id, :client_secret, :temp_access_token, :temp_access_token_expires_at,
4
+ :token_store, :token_store_callback_method
5
+
6
+ ACCESS_TOKEN_HTTP_METHOD = :get
7
+ ACCESS_TOKEN_ENDPOINT = '/access_token'.freeze
8
+ TimeZone = 'Eastern Time (US & Canada)'.freeze
9
+
10
+ def initialize(options = {})
11
+ options.each { |key, value| send(:"#{key}=", value) }
12
+ self.base_url ||= get_base_url
13
+ self.client_id ||= ENV['BOOKER_CLIENT_ID']
14
+ self.client_secret ||= ENV['BOOKER_CLIENT_SECRET']
15
+ end
16
+
17
+ def get_base_url
18
+ env_key = try(:env_base_url_key)
19
+
20
+ if env_key.present?
21
+ ENV[env_key] || try(:default_base_url)
22
+ else
23
+ try(:default_base_url)
24
+ end
25
+ end
26
+
27
+ def get(path, params, booker_model=nil)
28
+ booker_resources = get_booker_resources(:get, path, params, nil, booker_model)
29
+
30
+ build_resources(booker_resources, booker_model)
31
+ end
32
+
33
+ def post(path, data, booker_model=nil)
34
+ booker_resources = get_booker_resources(:post, path, nil, data.to_json, booker_model)
35
+
36
+ build_resources(booker_resources, booker_model)
37
+ end
38
+
39
+ def put(path, data, booker_model=nil)
40
+ booker_resources = get_booker_resources(:put, path, nil, data.to_json, booker_model)
41
+
42
+ build_resources(booker_resources, booker_model)
43
+ end
44
+
45
+ def paginated_request(method:, path:, params:, model: nil, fetched: [], fetch_all: true)
46
+ page_size = params['PageSize']
47
+ page_number = params['PageNumber']
48
+
49
+ if page_size.nil? || page_size < 1 || page_number.nil? || page_number < 1 || !params['UsePaging']
50
+ raise ArgumentError, 'params must include valid PageSize, PageNumber and UsePaging'
51
+ end
52
+
53
+ puts "fetching #{path} with #{params.except('access_token')}. #{fetched.length} results so far."
54
+
55
+ results = self.send(method, path, params, model)
56
+
57
+ unless results.is_a?(Array)
58
+ raise StandardError, "Result from paginated request to #{path} with params: #{params} is not a collection"
59
+ end
60
+
61
+ fetched.concat(results)
62
+ results_length = results.length
63
+
64
+ if fetch_all
65
+ if results_length > 0
66
+ # TODO (#111186744): Add logging to see if any pages with less than expected data (as seen in the /appointments endpoint)
67
+ new_params = params.deep_dup
68
+ new_params['PageNumber'] = page_number + 1
69
+ paginated_request(method: method, path: path, params: new_params, model: model, fetched: fetched)
70
+ else
71
+ fetched
72
+ end
73
+ else
74
+ results
75
+ end
76
+ end
77
+
78
+ def get_booker_resources(http_method, path, params=nil, body=nil, booker_model=nil)
79
+ http_options = request_options(params, body)
80
+ url = full_url(path)
81
+ puts "BOOKER REQUEST: #{http_method} #{url} #{http_options}" if ENV['BOOKER_API_DEBUG'] == 'true'
82
+
83
+ # Allow it to retry the first time unless it is an authorization error
84
+ begin
85
+ booker_resources = handle_errors!(url, http_options, HTTParty.send(http_method, url, http_options))
86
+ rescue Booker::Error, Net::ReadTimeout => ex
87
+ if ex.is_a? Booker::InvalidApiCredentials
88
+ raise ex
89
+ else
90
+ sleep 1
91
+ booker_resources = nil
92
+ end
93
+ end
94
+
95
+ return results_from_response(booker_resources, booker_model) if booker_resources.present?
96
+ booker_resources = handle_errors!(url, http_options, HTTParty.send(http_method, url, http_options))
97
+ return results_from_response(booker_resources, booker_model) if booker_resources.present?
98
+ raise Booker::Error.new(url: url, request: http_options, response: booker_resources)
99
+ end
100
+
101
+ def full_url(path)
102
+ uri = URI(path)
103
+ uri.scheme ? path : "#{self.base_url}#{path}"
104
+ end
105
+
106
+ def handle_errors!(url, request, response)
107
+ puts "BOOKER RESPONSE: #{response}" if ENV['BOOKER_API_DEBUG'] == 'true'
108
+
109
+ ex = Booker::Error.new(url: url, request: request, response: response)
110
+ if ex.error.present? || !response.success?
111
+ case ex.error
112
+ when 'invalid_client'
113
+ raise Booker::InvalidApiCredentials.new(url: url, request: request, response: response)
114
+ # when 'invalid access token'
115
+ # get_access_token
116
+ # return nil
117
+ else
118
+ raise ex
119
+ end
120
+ end
121
+
122
+ response
123
+ end
124
+
125
+ def access_token
126
+ (self.temp_access_token && !temp_access_token_expired?) ? self.temp_access_token : get_access_token
127
+ end
128
+
129
+ def access_token_options
130
+ {
131
+ client_id: self.client_id,
132
+ client_secret: self.client_secret
133
+ }
134
+ end
135
+
136
+ def update_token_store
137
+ if self.token_store.present? && self.token_store_callback_method.present?
138
+ self.token_store.send(self.token_store_callback_method, self.temp_access_token, self.temp_access_token_expires_at)
139
+ end
140
+ end
141
+
142
+ def get_access_token
143
+ http_options = access_token_options
144
+ response = raise_invalid_api_credentials_for_empty_resp! { access_token_response(http_options) }
145
+
146
+ self.temp_access_token_expires_at = Time.now + response['expires_in'].to_i.seconds
147
+ self.temp_access_token = response['access_token']
148
+
149
+ update_token_store
150
+
151
+ self.temp_access_token
152
+ end
153
+
154
+ def raise_invalid_api_credentials_for_empty_resp!
155
+ yield
156
+ rescue Booker::Error => ex
157
+ if (response = ex.response).present?
158
+ raise ex
159
+ else
160
+ raise Booker::InvalidApiCredentials.new(url: ex.url, request: ex.request, response: response)
161
+ end
162
+ end
163
+
164
+ def access_token_response(http_options)
165
+ send(self.class::ACCESS_TOKEN_HTTP_METHOD, self.class::ACCESS_TOKEN_ENDPOINT, http_options, nil)
166
+ end
167
+
168
+ private
169
+ def request_options(query=nil, body=nil)
170
+ options = {
171
+ headers: {
172
+ 'Content-Type' => 'application/json; charset=utf-8'
173
+ },
174
+ open_timeout: 120
175
+ }
176
+
177
+ options[:body] = body if body.present?
178
+ options[:query] = query if query.present?
179
+ options
180
+ end
181
+
182
+ def build_resources(resources, booker_model)
183
+ return resources if booker_model.nil?
184
+
185
+ if resources.is_a? Hash
186
+ booker_model.from_hash(resources)
187
+ elsif resources.is_a? Array
188
+ booker_model.from_list(resources)
189
+ else
190
+ resources
191
+ end
192
+ end
193
+
194
+ def temp_access_token_expired?
195
+ self.temp_access_token_expires_at.nil? || self.temp_access_token_expires_at <= Time.now
196
+ end
197
+
198
+ def results_from_response(response, booker_model=nil)
199
+ return response['Results'] unless response['Results'].nil?
200
+
201
+ if booker_model
202
+ model_name = booker_model.to_s.demodulize
203
+ return response[model_name] unless response[model_name].nil?
204
+
205
+ pluralized = model_name.pluralize
206
+ return response[pluralized] unless response[pluralized].nil?
207
+ end
208
+
209
+ response.parsed_response if response
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,49 @@
1
+ module Booker
2
+ module CommonREST
3
+ DEFAULT_PAGINATION_PARAMS = {
4
+ 'UsePaging' => true,
5
+ 'PageSize' => Integer(ENV['BOOKER_DEFAULT_PAGE_SIZE'] || 10),
6
+ 'PageNumber' => 1
7
+ }
8
+
9
+ def get_online_booking_settings(booker_location_id:)
10
+ response = get("/location/#{booker_location_id}/online_booking_settings", build_params)
11
+ Booker::Models::OnlineBookingSettings.from_hash(response['OnlineBookingSettings'])
12
+ end
13
+
14
+ def confirm_appointment(appointment_id:)
15
+ put '/appointment/confirm', build_params('ID' => appointment_id), Booker::Models::Appointment
16
+ end
17
+
18
+ def get_location(booker_location_id:)
19
+ response = get("/location/#{booker_location_id}", build_params)
20
+ Booker::Models::Location.from_hash(response)
21
+ end
22
+
23
+ private
24
+
25
+ def build_params(default_params={}, overrides={}, paginated=false)
26
+ # merged = default_params.merge(overrides)
27
+ # merged.merge({"access_token" => access_token}) unless merged.has_key?("access_token")
28
+ original = {}
29
+ unless overrides.has_key?(:access_token)
30
+ original[:access_token] = access_token
31
+ end
32
+ merged = original.merge(default_params.merge(overrides))
33
+
34
+ merged.each do |k, v|
35
+ if v.is_a?(Time) || v.is_a?(DateTime)
36
+ merged[k] = Booker::Models::Model.time_to_booker_datetime(v)
37
+ elsif v.is_a?(Date)
38
+ merged[k] = Booker::Models::Model.time_to_booker_datetime(v.in_time_zone)
39
+ end
40
+ end
41
+
42
+ if paginated
43
+ DEFAULT_PAGINATION_PARAMS.merge(merged)
44
+ else
45
+ merged
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,269 @@
1
+ ---
2
+ 1: 'US'
3
+ 2: 'CA'
4
+ 3: 'AL'
5
+ 4: 'DZ'
6
+ 5: 'AS'
7
+ 6: 'AD'
8
+ 7: 'AO'
9
+ 8: 'AI'
10
+ 9: 'AG'
11
+ 10: 'AR'
12
+ 11: 'AM'
13
+ 12: 'AW'
14
+ 13: 'AU'
15
+ 14: 'AT'
16
+ 15: 'AZ'
17
+ 16: 'AP'
18
+ 17: 'BS'
19
+ 18: 'BH'
20
+ 19: 'BD'
21
+ 20: 'BB'
22
+ 21: 'BY'
23
+ 22: 'BE'
24
+ 23: 'BZ'
25
+ 24: 'BJ'
26
+ 25: 'BM'
27
+ 26: 'BT'
28
+ 27: 'BO'
29
+ 28: 'BL'
30
+ 29: 'BA'
31
+ 30: 'BW'
32
+ 31: 'BV'
33
+ 32: 'BR'
34
+ 33: 'IO'
35
+ 34: 'BN'
36
+ 35: 'BG'
37
+ 36: 'BF'
38
+ 37: 'BI'
39
+ 38: 'KH'
40
+ 39: 'CM'
41
+ 41: 'IC'
42
+ 42: 'CV'
43
+ 43: 'KY'
44
+ 44: 'CF'
45
+ 45: 'TD'
46
+ 46: 'CD'
47
+ 47: 'CL'
48
+ 48: 'CN'
49
+ 49: 'CX'
50
+ 50: 'CC'
51
+ 51: 'CO'
52
+ 52: 'KM'
53
+ 53: 'ZP'
54
+ 54: 'CK'
55
+ 55: 'CR'
56
+ 56: 'CI'
57
+ 57: 'HR'
58
+ 58: 'CB'
59
+ 59: 'CY'
60
+ 60: 'CZ'
61
+ 61: 'DK'
62
+ 62: 'DJ'
63
+ 63: 'DM'
64
+ 64: 'DO'
65
+ 65: 'TP'
66
+ 66: 'EC'
67
+ 67: 'EG'
68
+ 68: 'SV'
69
+ 69: 'GB' # Must be GB not UK!!!
70
+ 70: 'GQ'
71
+ 71: 'ER'
72
+ 72: 'EE'
73
+ 73: 'ET'
74
+ 74: 'FO'
75
+ 75: 'FK'
76
+ 76: 'FJ'
77
+ 77: 'FI'
78
+ 78: 'FR'
79
+ 79: 'GF'
80
+ 80: 'PF'
81
+ 81: 'TF'
82
+ 82: 'GA'
83
+ 83: 'GM'
84
+ 84: 'GE'
85
+ 85: 'DE'
86
+ 86: 'GH'
87
+ 87: 'GI'
88
+ 89: 'GR'
89
+ 90: 'GL'
90
+ 91: 'GD'
91
+ 92: 'GP'
92
+ 93: 'GU'
93
+ 94: 'GT'
94
+ 95: 'GN'
95
+ 96: 'GW'
96
+ 97: 'GY'
97
+ 98: 'HT'
98
+ 99: 'HM'
99
+ 100: 'HN'
100
+ 101: 'HK'
101
+ 102: 'HU'
102
+ 103: 'IS'
103
+ 104: 'IN'
104
+ 105: 'ID'
105
+ 106: 'IE'
106
+ 107: 'IL'
107
+ 108: 'IT'
108
+ 109: 'JM'
109
+ 110: 'JP'
110
+ 111: 'JO'
111
+ 112: 'KZ'
112
+ 113: 'KE'
113
+ 114: 'KI'
114
+ 115: 'KO'
115
+ 116: 'KW'
116
+ 117: 'KG'
117
+ 118: 'LA'
118
+ 119: 'LV'
119
+ 120: 'LB'
120
+ 121: 'LS'
121
+ 122: 'LR'
122
+ 123: 'LY'
123
+ 124: 'LI'
124
+ 125: 'LT'
125
+ 126: 'LU'
126
+ 127: 'MO'
127
+ 128: 'MK'
128
+ 129: 'MG'
129
+ 130: 'ME'
130
+ 131: 'MW'
131
+ 132: 'MY'
132
+ 133: 'MV'
133
+ 134: 'ML'
134
+ 135: 'MT'
135
+ 136: 'MH'
136
+ 137: 'MQ'
137
+ 138: 'MR'
138
+ 139: 'MU'
139
+ 140: 'YT'
140
+ 141: 'MX'
141
+ 142: 'FM'
142
+ 143: 'MD'
143
+ 144: 'MC'
144
+ 145: 'MN'
145
+ 146: 'MS'
146
+ 147: 'MA'
147
+ 148: 'MZ'
148
+ 149: 'MM'
149
+ 150: 'NA'
150
+ 151: 'NR'
151
+ 152: 'NP'
152
+ 153: 'NL'
153
+ 154: 'AN'
154
+ 155: 'NT'
155
+ 156: 'NC'
156
+ 157: 'NZ'
157
+ 158: 'NI'
158
+ 159: 'NE'
159
+ 160: 'NG'
160
+ 161: 'NU'
161
+ 162: 'NF'
162
+ 163: 'KP'
163
+ 164: 'NB'
164
+ 165: 'MP'
165
+ 166: 'NO'
166
+ 167: 'OM'
167
+ 168: 'PK'
168
+ 169: 'PW'
169
+ 170: 'PA'
170
+ 171: 'PG'
171
+ 172: 'PY'
172
+ 173: 'PE'
173
+ 174: 'PH'
174
+ 175: 'PN'
175
+ 176: 'PL'
176
+ 177: 'PO'
177
+ 178: 'PT'
178
+ 179: 'PR'
179
+ 180: 'QA'
180
+ 181: 'RE'
181
+ 182: 'RO'
182
+ 183: 'RT'
183
+ 184: 'RU'
184
+ 185: 'RW'
185
+ 186: 'SS'
186
+ 187: 'KN'
187
+ 188: 'LC'
188
+ 189: 'VC'
189
+ 190: 'SP'
190
+ 191: 'WS'
191
+ 192: 'SM'
192
+ 193: 'ST'
193
+ 194: 'SA'
194
+ 195: 'SF'
195
+ 196: 'SN'
196
+ 197: 'SC'
197
+ 198: 'SL'
198
+ 199: 'SG'
199
+ 200: 'SK'
200
+ 201: 'SI'
201
+ 202: 'SB'
202
+ 203: 'SO'
203
+ 204: 'ZA'
204
+ 205: 'GS'
205
+ 206: 'KR'
206
+ 207: 'ES'
207
+ 208: 'LK'
208
+ 209: 'NT'
209
+ 210: 'SW'
210
+ 211: 'VI'
211
+ 212: 'EU'
212
+ 213: 'SH'
213
+ 214: 'UV'
214
+ 215: 'KN'
215
+ 216: 'LC'
216
+ 217: 'MB'
217
+ 218: 'TB'
218
+ 219: 'PM'
219
+ 220: 'VL'
220
+ 221: 'VC'
221
+ 222: 'SD'
222
+ 223: 'SR'
223
+ 224: 'SJ'
224
+ 225: 'SZ'
225
+ 226: 'SE'
226
+ 227: 'CH'
227
+ 228: 'SY'
228
+ 229: 'TA'
229
+ 230: 'TW'
230
+ 231: 'TJ'
231
+ 232: 'TZ'
232
+ 233: 'TH'
233
+ 234: 'TI'
234
+ 235: 'TG'
235
+ 236: 'TK'
236
+ 237: 'TO'
237
+ 238: 'TL'
238
+ 239: 'TT'
239
+ 240: 'TU'
240
+ 241: 'TN'
241
+ 242: 'TR'
242
+ 243: 'TM'
243
+ 244: 'TC'
244
+ 245: 'TV'
245
+ 246: 'UG'
246
+ 247: 'UA'
247
+ 248: 'UI'
248
+ 249: 'AE'
249
+ 251: 'UY'
250
+ 252: 'UM'
251
+ 253: 'SU'
252
+ 254: 'UZ'
253
+ 255: 'VU'
254
+ 256: 'VA'
255
+ 257: 'VE'
256
+ 258: 'VN'
257
+ 259: 'VR'
258
+ 260: 'VG'
259
+ 261: 'VI'
260
+ 262: 'WL'
261
+ 263: 'WF'
262
+ 264: 'WS'
263
+ 265: 'YA'
264
+ 266: 'YE'
265
+ 267: 'YU'
266
+ 268: 'ZR'
267
+ 269: 'ZM'
268
+ 270: 'ZW'
269
+ 271: 'RS'