yt 0.25.13 → 0.32.2

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 (96) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +305 -1
  3. data/MIT-LICENSE +1 -1
  4. data/README.md +86 -5
  5. data/YOUTUBE_IT.md +3 -3
  6. data/lib/yt.rb +5 -2
  7. data/lib/yt/actions/list.rb +3 -3
  8. data/lib/yt/associations/has_authentication.rb +33 -1
  9. data/lib/yt/associations/has_reports.rb +13 -18
  10. data/lib/yt/collections/assets.rb +2 -2
  11. data/lib/yt/collections/authentications.rb +9 -2
  12. data/lib/yt/collections/base.rb +3 -3
  13. data/lib/yt/collections/bulk_report_jobs.rb +28 -0
  14. data/lib/yt/collections/bulk_reports.rb +24 -0
  15. data/lib/yt/collections/claims.rb +22 -1
  16. data/lib/yt/collections/comment_threads.rb +41 -0
  17. data/lib/yt/collections/content_owners.rb +1 -1
  18. data/lib/yt/collections/group_infos.rb +27 -0
  19. data/lib/yt/collections/group_items.rb +45 -0
  20. data/lib/yt/collections/reports.rb +75 -13
  21. data/lib/yt/collections/revocations.rb +30 -0
  22. data/lib/yt/collections/video_groups.rb +29 -0
  23. data/lib/yt/collections/videos.rb +34 -9
  24. data/lib/yt/constants/geography.rb +326 -0
  25. data/lib/yt/errors/forbidden.rb +1 -3
  26. data/lib/yt/errors/no_items.rb +1 -3
  27. data/lib/yt/errors/request_error.rb +10 -7
  28. data/lib/yt/errors/server_error.rb +1 -3
  29. data/lib/yt/errors/unauthorized.rb +3 -3
  30. data/lib/yt/models/account.rb +12 -0
  31. data/lib/yt/models/advertising_options_set.rb +4 -4
  32. data/lib/yt/models/bulk_report.rb +23 -0
  33. data/lib/yt/models/bulk_report_job.rb +23 -0
  34. data/lib/yt/models/channel.rb +21 -12
  35. data/lib/yt/models/claim.rb +13 -2
  36. data/lib/yt/models/comment.rb +37 -0
  37. data/lib/yt/models/comment_thread.rb +50 -0
  38. data/lib/yt/models/content_detail.rb +6 -0
  39. data/lib/yt/models/content_owner.rb +31 -1
  40. data/lib/yt/models/group_info.rb +16 -0
  41. data/lib/yt/models/group_item.rb +15 -0
  42. data/lib/yt/models/resource.rb +3 -10
  43. data/lib/yt/models/revocation.rb +12 -0
  44. data/lib/yt/models/right_owner.rb +0 -2
  45. data/lib/yt/models/snippet.rb +24 -3
  46. data/lib/yt/models/video.rb +42 -11
  47. data/lib/yt/models/video_group.rb +186 -0
  48. data/lib/yt/request.rb +5 -3
  49. data/lib/yt/version.rb +2 -2
  50. data/spec/collections/comment_threads_spec.rb +46 -0
  51. data/spec/collections/playlist_items_spec.rb +1 -1
  52. data/spec/collections/reports_spec.rb +2 -2
  53. data/spec/constants/geography_spec.rb +16 -0
  54. data/spec/models/annotation_spec.rb +1 -1
  55. data/spec/models/claim_spec.rb +15 -3
  56. data/spec/models/comment_spec.rb +40 -0
  57. data/spec/models/comment_thread_spec.rb +93 -0
  58. data/spec/models/content_detail_spec.rb +7 -0
  59. data/spec/models/reference_spec.rb +2 -2
  60. data/spec/models/request_spec.rb +21 -0
  61. data/spec/models/resource_spec.rb +0 -15
  62. data/spec/models/video_spec.rb +1 -1
  63. data/spec/requests/as_account/account_spec.rb +16 -4
  64. data/spec/requests/as_account/authentications_spec.rb +1 -13
  65. data/spec/requests/as_account/channel_spec.rb +15 -45
  66. data/spec/requests/as_account/playlist_item_spec.rb +3 -3
  67. data/spec/requests/as_account/playlist_spec.rb +5 -32
  68. data/spec/requests/as_account/video_spec.rb +2022 -21
  69. data/spec/requests/as_content_owner/account_spec.rb +4 -0
  70. data/spec/requests/as_content_owner/bulk_report_job_spec.rb +19 -0
  71. data/spec/requests/as_content_owner/channel_spec.rb +59 -270
  72. data/spec/requests/as_content_owner/content_owner_spec.rb +89 -1
  73. data/spec/requests/as_content_owner/playlist_spec.rb +0 -15
  74. data/spec/requests/as_content_owner/video_group_spec.rb +112 -0
  75. data/spec/requests/as_content_owner/video_spec.rb +72 -146
  76. data/spec/requests/as_server_app/channel_spec.rb +1 -21
  77. data/spec/requests/as_server_app/comment_spec.rb +22 -0
  78. data/spec/requests/as_server_app/comment_thread_spec.rb +27 -0
  79. data/spec/requests/as_server_app/comment_threads_spec.rb +41 -0
  80. data/spec/requests/as_server_app/playlist_item_spec.rb +2 -2
  81. data/spec/requests/as_server_app/playlist_spec.rb +1 -22
  82. data/spec/requests/as_server_app/video_spec.rb +21 -19
  83. data/spec/requests/as_server_app/videos_spec.rb +5 -5
  84. data/spec/requests/unauthenticated/video_spec.rb +1 -9
  85. data/spec/spec_helper.rb +1 -1
  86. data/yt.gemspec +2 -1
  87. metadata +51 -17
  88. data/lib/yt/collections/ids.rb +0 -27
  89. data/lib/yt/config.rb +0 -54
  90. data/lib/yt/models/configuration.rb +0 -70
  91. data/lib/yt/models/description.rb +0 -58
  92. data/lib/yt/models/url.rb +0 -91
  93. data/spec/models/configuration_spec.rb +0 -44
  94. data/spec/models/description_spec.rb +0 -94
  95. data/spec/models/url_spec.rb +0 -84
  96. data/spec/requests/as_account/resource_spec.rb +0 -18
@@ -0,0 +1,45 @@
1
+ require 'yt/collections/base'
2
+ require 'yt/models/group_item'
3
+
4
+ module Yt
5
+ module Collections
6
+ # @private
7
+ class GroupItems < Base
8
+
9
+ private
10
+
11
+ def attributes_for_new_item(data)
12
+ super(data).tap do |attributes|
13
+ attributes[:video] = data['video']
14
+ end
15
+ end
16
+
17
+ def list_params
18
+ super.tap do |params|
19
+ params[:host] = 'youtubeanalytics.googleapis.com'
20
+ params[:path] = "/v2/groupItems"
21
+ params[:params] = {group_id: @parent.id}
22
+ if @auth.owner_name
23
+ params[:params][:on_behalf_of_content_owner] = @auth.owner_name
24
+ end
25
+ end
26
+ end
27
+
28
+ def eager_load_items_from(items)
29
+ if included_relationships.include?(:video)
30
+ all_video_ids = items.map{|item| item['resource']['id']}.uniq
31
+ all_video_ids.each_slice(50) do |video_ids|
32
+ conditions = {id: video_ids.join(',')}
33
+ conditions[:part] = 'snippet,status,statistics,contentDetails'
34
+ videos = Collections::Videos.new(auth: @auth).where conditions
35
+ items.each do |item|
36
+ video = videos.find{|v| v.id == item['resource']['id']}
37
+ item['video'] = video if video
38
+ end
39
+ end
40
+ end
41
+ super
42
+ end
43
+ end
44
+ end
45
+ end
@@ -11,12 +11,15 @@ module Yt
11
11
  hash[:traffic_source] = {name: 'insightTrafficSourceType', parse: ->(source, *values) { @metrics.keys.zip(values.map{|v| {TRAFFIC_SOURCES.key(source) => v}}).to_h} }
12
12
  hash[:playback_location] = {name: 'insightPlaybackLocationType', parse: ->(location, *values) { @metrics.keys.zip(values.map{|v| {PLAYBACK_LOCATIONS.key(location) => v}}).to_h} }
13
13
  hash[:embedded_player_location] = {name: 'insightPlaybackLocationDetail', parse: ->(url, *values) {@metrics.keys.zip(values.map{|v| {url => v}}).to_h} }
14
+ hash[:subscribed_status] = {name: 'subscribedStatus', parse: ->(status, *values) {@metrics.keys.zip(values.map{|v| {SUBSCRIBED_STATUSES.key(status) => v}}).to_h} }
14
15
  hash[:related_video] = {name: 'insightTrafficSourceDetail', parse: ->(video_id, *values) { @metrics.keys.zip(values.map{|v| {video_id => v}}).to_h} }
15
16
  hash[:search_term] = {name: 'insightTrafficSourceDetail', parse: ->(search_term, *values) {@metrics.keys.zip(values.map{|v| {search_term => v}}).to_h} }
16
17
  hash[:referrer] = {name: 'insightTrafficSourceDetail', parse: ->(url, *values) {@metrics.keys.zip(values.map{|v| {url => v}}).to_h} }
17
18
  hash[:video] = {name: 'video', parse: ->(video_id, *values) { @metrics.keys.zip(values.map{|v| {video_id => v}}).to_h} }
18
19
  hash[:playlist] = {name: 'playlist', parse: ->(playlist_id, *values) { @metrics.keys.zip(values.map{|v| {playlist_id => v}}).to_h} }
19
- hash[:device_type] = {name: 'deviceType', parse: ->(type, *values) {@metrics.keys.zip(values.map{|v| {type.downcase.to_sym => v}}).to_h} }
20
+ hash[:device_type] = {name: 'deviceType', parse: ->(type, *values) {@metrics.keys.zip(values.map{|v| {DEVICE_TYPES.key(type) => v}}).to_h} }
21
+ hash[:operating_system] = {name: 'operatingSystem', parse: ->(os, *values) {@metrics.keys.zip(values.map{|v| {OPERATING_SYSTEMS.key(os) => v}}).to_h} }
22
+ hash[:youtube_product] = {name: 'youtubeProduct', parse: ->(product, *values) {@metrics.keys.zip(values.map{|v| {YOUTUBE_PRODUCTS.key(product) => v}}).to_h} }
20
23
  hash[:country] = {name: 'country', parse: ->(country_code, *values) { @metrics.keys.zip(values.map{|v| {country_code => v}}).to_h} }
21
24
  hash[:state] = {name: 'province', parse: ->(country_and_state_code, *values) { @metrics.keys.zip(values.map{|v| {country_and_state_code[3..-1] => v}}).to_h} }
22
25
  hash[:gender_age_group] = {name: 'gender,ageGroup', parse: ->(gender, *values) { [gender.downcase.to_sym, *values] }}
@@ -43,6 +46,8 @@ module Yt
43
46
  google: 'GOOGLE_SEARCH',
44
47
  notification: 'NOTIFICATION',
45
48
  playlist_page: 'YT_PLAYLIST_PAGE',
49
+ campaign_card: 'CAMPAIGN_CARD',
50
+ end_screen: 'END_SCREEN',
46
51
  info_card: 'INFO_CARD'
47
52
  }
48
53
 
@@ -53,17 +58,72 @@ module Yt
53
58
  embedded: 'EMBEDDED',
54
59
  other: 'YT_OTHER',
55
60
  external_app: 'EXTERNAL_APP',
61
+ search: 'SEARCH', # undocumented but returned by the API
62
+ browse: 'BROWSE', # undocumented but returned by the API
56
63
  mobile: 'MOBILE' # only present for data < September 10, 2013
57
64
  }
58
65
 
66
+ # @see https://developers.google.com/youtube/analytics/v1/dimsmets/dims#Playback_Detail_Dimensions
67
+ SUBSCRIBED_STATUSES = {
68
+ subscribed: 'SUBSCRIBED',
69
+ unsubscribed: 'UNSUBSCRIBED'
70
+ }
71
+
72
+ # @see https://developers.google.com/youtube/analytics/v1/dimsmets/dims#youtubeProduct
73
+ YOUTUBE_PRODUCTS = {
74
+ core: 'CORE',
75
+ gaming: 'GAMING',
76
+ kids: 'KIDS',
77
+ unknown: 'UNKNOWN'
78
+ }
79
+
80
+ # @see https://developers.google.com/youtube/analytics/v1/dimsmets/dims#Device_Dimensions
81
+ DEVICE_TYPES = {
82
+ desktop: 'DESKTOP',
83
+ game_console: 'GAME_CONSOLE',
84
+ mobile: 'MOBILE',
85
+ tablet: 'TABLET',
86
+ tv: 'TV',
87
+ unknown_platform: 'UNKNOWN_PLATFORM'
88
+ }
89
+
90
+ # @see https://developers.google.com/youtube/analytics/v1/dimsmets/dims#Device_Dimensions
91
+ OPERATING_SYSTEMS = {
92
+ android: 'ANDROID',
93
+ bada: 'BADA',
94
+ blackberry: 'BLACKBERRY',
95
+ chromecast: 'CHROMECAST',
96
+ docomo: 'DOCOMO',
97
+ firefox: 'FIREFOX',
98
+ hiptop: 'HIPTOP',
99
+ ios: 'IOS',
100
+ linux: 'LINUX',
101
+ macintosh: 'MACINTOSH',
102
+ meego: 'MEEGO',
103
+ nintendo_3ds: 'NINTENDO_3DS',
104
+ other: 'OTHER',
105
+ playstation: 'PLAYSTATION',
106
+ playstation_vita: 'PLAYSTATION_VITA',
107
+ realmedia: 'REALMEDIA',
108
+ smart_tv: 'SMART_TV',
109
+ symbian: 'SYMBIAN',
110
+ tizen: 'TIZEN',
111
+ webos: 'WEBOS',
112
+ wii: 'WII',
113
+ windows: 'WINDOWS',
114
+ windows_mobile: 'WINDOWS_MOBILE',
115
+ xbox: 'XBOX'
116
+ }
117
+
59
118
  attr_writer :metrics
60
119
 
61
- def within(days_range, country, state, dimension, videos, try_again = true)
120
+ def within(days_range, country, state, dimension, videos, historical, max_retries = 3)
62
121
  @days_range = days_range
63
- @dimension = dimension
64
122
  @country = country
65
123
  @state = state
124
+ @dimension = dimension
66
125
  @videos = videos
126
+ @historical = historical
67
127
  if dimension == :gender_age_group # array of array
68
128
  Hash.new{|h,k| h[k] = Hash.new 0.0}.tap do |hash|
69
129
  each{|gender, age_group, value| hash[gender][age_group[3..-1]] = value}
@@ -86,10 +146,10 @@ module Yt
86
146
  end
87
147
  elsif dimension == :day
88
148
  hash = hash.transform_values{|h| h.sort_by{|day, v| day}.to_h}
89
- elsif dimension.in? [:traffic_source, :country, :state, :playback_location, :device_type]
149
+ elsif dimension.in? [:traffic_source, :country, :state, :playback_location, :device_type, :operating_system, :subscribed_status]
90
150
  hash = hash.transform_values{|h| h.sort_by{|range, v| -v}.to_h}
91
151
  end
92
- (@metrics.one? || @metrics.keys == [:earnings, :estimated_minutes_watched]) ? hash[@metrics.keys.first] : hash
152
+ (@metrics.one? || @metrics.keys == [:estimated_revenue, :estimated_minutes_watched]) ? hash[@metrics.keys.first] : hash
93
153
  end
94
154
  # NOTE: Once in a while, YouTube responds with 400 Error and the message
95
155
  # "Invalid query. Query did not conform to the expectations."; in this
@@ -98,7 +158,7 @@ module Yt
98
158
  # same query is a workaround that works and can hardly cause any damage.
99
159
  # Similarly, once in while YouTube responds with a random 503 error.
100
160
  rescue Yt::Error => e
101
- try_again && rescue?(e) ? sleep(3) && within(days_range, country, state, dimension, videos, false) : raise
161
+ (max_retries > 0) && rescue?(e) ? sleep(3) && within(days_range, country, state, dimension, videos, historical, max_retries - 1) : raise
102
162
  end
103
163
 
104
164
  private
@@ -117,7 +177,8 @@ module Yt
117
177
  # @see https://developers.google.com/youtube/analytics/v1/content_owner_reports
118
178
  def list_params
119
179
  super.tap do |params|
120
- params[:path] = '/youtube/analytics/v1/reports'
180
+ params[:host] = 'youtubeanalytics.googleapis.com'
181
+ params[:path] = '/v2/reports'
121
182
  params[:params] = reports_params
122
183
  params[:camelize_params] = false
123
184
  end
@@ -125,15 +186,16 @@ module Yt
125
186
 
126
187
  def reports_params
127
188
  @parent.reports_params.tap do |params|
128
- params['start-date'] = @days_range.begin
129
- params['end-date'] = @days_range.end
189
+ params['startDate'] = @days_range.begin
190
+ params['endDate'] = @days_range.end
130
191
  params['metrics'] = @metrics.keys.join(',').to_s.camelize(:lower)
131
192
  params['dimensions'] = DIMENSIONS[@dimension][:name] unless @dimension == :range
132
- params['max-results'] = 50 if @dimension.in? [:playlist, :video]
133
- params['max-results'] = 25 if @dimension.in? [:embedded_player_location, :related_video, :search_term, :referrer]
193
+ params['includeHistoricalChannelData'] = @historical if @historical
194
+ params['maxResults'] = 50 if @dimension.in? [:playlist, :video]
195
+ params['maxResults'] = 25 if @dimension.in? [:embedded_player_location, :related_video, :search_term, :referrer]
134
196
  if @dimension.in? [:video, :playlist, :embedded_player_location, :related_video, :search_term, :referrer]
135
- if @metrics.keys == [:earnings, :estimated_minutes_watched]
136
- params['sort'] = '-earnings'
197
+ if @metrics.keys == [:estimated_revenue, :estimated_minutes_watched]
198
+ params['sort'] = '-estimatedRevenue'
137
199
  else
138
200
  params['sort'] = "-#{@metrics.keys.join(',').to_s.camelize(:lower)}"
139
201
  end
@@ -0,0 +1,30 @@
1
+ require 'yt/collections/authentications'
2
+ require 'yt/models/revocation'
3
+
4
+ module Yt
5
+ module Collections
6
+ class Revocations < Authentications
7
+ attr_accessor :auth_params
8
+
9
+ private
10
+
11
+ # This overrides the parent mehthod defined in Authentications
12
+ def attributes_for_new_item(data)
13
+ {data: data}
14
+ end
15
+
16
+ def list_params
17
+ super.tap do |params|
18
+ params[:host] = 'accounts.google.com'
19
+ params[:path] = '/o/oauth2/revoke'
20
+ params[:request_format] = nil
21
+ params[:method] = :get
22
+ params[:auth] = nil
23
+ params[:body] = nil
24
+ params[:camelize_body] = false
25
+ params[:params] = auth_params
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ require 'yt/collections/base'
2
+ require 'yt/models/video_group'
3
+ require 'yt/models/group_info'
4
+
5
+ module Yt
6
+ module Collections
7
+ # @private
8
+ class VideoGroups < Base
9
+
10
+ private
11
+
12
+ def attributes_for_new_item(data)
13
+ {id: data['id'], auth: @auth, group_info: Yt::GroupInfo.new(data: data)}
14
+ end
15
+
16
+ def new_item(data)
17
+ super if data['contentDetails']['itemType'] == 'youtube#video'
18
+ end
19
+
20
+ def list_params
21
+ super.tap do |params|
22
+ params[:host] = 'youtubeanalytics.googleapis.com'
23
+ params[:path] = "/v2/groups"
24
+ params[:params] = @parent.video_groups_params
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -10,6 +10,7 @@ module Yt
10
10
  class Videos < Base
11
11
  def where(requirements = {})
12
12
  @published_before = nil
13
+ @halt_list = false
13
14
  super
14
15
  end
15
16
 
@@ -25,18 +26,23 @@ module Yt
25
26
  attributes[:content_details] = data['contentDetails']
26
27
  attributes[:statistics] = data['statistics']
27
28
  attributes[:video_category] = data['videoCategory']
29
+ attributes[:claim] = data['claim']
28
30
  attributes[:auth] = @auth
29
31
  end
30
32
  end
31
33
 
32
34
  def eager_load_items_from(items)
33
35
  if included_relationships.any?
34
- include_category = included_relationships.delete(:category)
35
- included_relationships.append(:snippet).uniq! if include_category
36
+ associations = [:claim, :category]
37
+ if (included_relationships & associations).any?
38
+ included_relationships.append(:snippet).uniq!
39
+ end
36
40
 
37
41
  ids = items.map{|item| item['id']['videoId']}
38
- parts = included_relationships.map{|r| r.to_s.camelize(:lower)}
39
- conditions = {id: ids.join(','), part: parts.join(',')}
42
+ parts = (included_relationships - associations).map do |r|
43
+ r.to_s.camelize(:lower)
44
+ end
45
+ conditions = { id: ids.join(','), part: parts.join(',') }
40
46
  videos = Collections::Videos.new(auth: @auth).where conditions
41
47
 
42
48
  items.each do |item|
@@ -51,7 +57,20 @@ module Yt
51
57
  end if video
52
58
  end
53
59
 
54
- if include_category
60
+ if included_relationships.include? :claim
61
+ video_ids = items.map{|item| item['id']['videoId']}.uniq
62
+ conditions = {
63
+ video_id: video_ids.join(','),
64
+ include_third_party_claims: false
65
+ }
66
+ claims = @parent.claims.includes(:asset).where conditions
67
+ items.each do |item|
68
+ claim = claims.find { |c| c.video_id == item['id']['videoId']}
69
+ item['claim'] = claim
70
+ end
71
+ end
72
+
73
+ if included_relationships.include? :category
55
74
  category_ids = items.map{|item| item['snippet']['categoryId']}.uniq
56
75
  conditions = {id: category_ids.join(',')}
57
76
  video_categories = Collections::VideoCategories.new(auth: @auth).where conditions
@@ -77,7 +96,7 @@ module Yt
77
96
  def next_page
78
97
  super.tap do |items|
79
98
  halt_list if use_list_endpoint? && items.empty? && @page_token.nil?
80
- add_offset_to(items) if !use_list_endpoint? && @page_token.nil? && videos_params[:order] == 'date'
99
+ add_offset_to(items) if !use_list_endpoint? && videos_params[:order] == 'date' && !(videos_params[:for_mine] || videos_params[:for_content_owner])
81
100
  end
82
101
  end
83
102
 
@@ -86,9 +105,15 @@ module Yt
86
105
  # that limit, the query is restarted with a publishedBefore filter in
87
106
  # case there are more videos to be listed for a channel
88
107
  def add_offset_to(items)
89
- if items.count == videos_params[:max_results]
108
+ @fetched_items ||= 0
109
+ if (@fetched_items += items.count) >= 500
90
110
  last_published = items.last['snippet']['publishedAt']
91
- @page_token, @published_before = '', last_published
111
+ last_published = DateTime.rfc3339(last_published) - 1.second
112
+ last_published = last_published.strftime '%FT%T.999Z'
113
+ @page_token, @published_before, @fetched_items = '', last_published, 0
114
+ elsif (1...50) === @last_index % 50
115
+ @halt_list, @page_token = true, nil
116
+ @last_index += items.size
92
117
  end
93
118
  end
94
119
 
@@ -134,4 +159,4 @@ module Yt
134
159
  end
135
160
  end
136
161
  end
137
- end
162
+ end
@@ -0,0 +1,326 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/hash/indifferent_access'
3
+
4
+ module Yt
5
+ # The list of country codes and names used by the YouTube Analytics API.
6
+ # @see https://developers.google.com/youtube/analytics/v1/dimsmets/dims#country
7
+ COUNTRIES = {
8
+ AC: 'Ascension',
9
+ AD: 'Andorra',
10
+ AE: 'United Arab Emirates',
11
+ AF: 'Afghanistan',
12
+ AG: 'Antigua and Barbuda',
13
+ AI: 'Anguilla',
14
+ AL: 'Albania',
15
+ AM: 'Armenia',
16
+ AN: 'Netherlands Antilles',
17
+ AO: 'Angola',
18
+ AQ: 'Antarctic Territory',
19
+ AR: 'Argentina',
20
+ AS: 'American Samoa',
21
+ AT: 'Austria',
22
+ AU: 'Australia',
23
+ AW: 'Aruba',
24
+ AX: 'Aland',
25
+ AZ: 'Azerbaijan',
26
+ BA: 'Bosnia and Herzegovina',
27
+ BB: 'Barbados',
28
+ BD: 'Bangladesh',
29
+ BE: 'Belgium',
30
+ BF: 'Burkina Faso',
31
+ BG: 'Bulgaria',
32
+ BH: 'Bahrain',
33
+ BI: 'Burundi',
34
+ BJ: 'Benin',
35
+ BL: 'Saint Barthélemy',
36
+ BM: 'Bermuda',
37
+ BN: 'Brunei',
38
+ BO: 'Bolivia',
39
+ BQ: 'Bonaire, Sint Eustatius and Saba',
40
+ BR: 'Brazil',
41
+ BS: 'Bahamas',
42
+ BT: 'Bhutan',
43
+ BV: 'Bouvet Island',
44
+ BW: 'Botswana',
45
+ BY: 'Belarus',
46
+ BZ: 'Belize',
47
+ CA: 'Canada',
48
+ CC: 'Cocos (Keeling) Islands',
49
+ CD: 'Congo',
50
+ CF: 'Central African Republic',
51
+ CG: 'Congo',
52
+ CH: 'Switzerland',
53
+ CI: 'Ivory Coast',
54
+ CK: 'Cook Islands',
55
+ CL: 'Chile',
56
+ CM: 'Cameroon',
57
+ CN: 'China',
58
+ CO: 'Colombia',
59
+ CR: 'Costa Rica',
60
+ CU: 'Cuba',
61
+ CV: 'Cape Verde',
62
+ CW: 'Curaçao',
63
+ CX: 'Christmas Island',
64
+ CY: 'Cyprus',
65
+ CZ: 'Czech Republic',
66
+ DE: 'Germany',
67
+ DJ: 'Djibouti',
68
+ DK: 'Denmark',
69
+ DM: 'Dominica',
70
+ DO: 'Dominican Republic',
71
+ DZ: 'Algeria',
72
+ EC: 'Ecuador',
73
+ EE: 'Estonia',
74
+ EG: 'Egypt',
75
+ EH: 'Western Sahara',
76
+ ER: 'Eritrea',
77
+ ES: 'Spain',
78
+ ET: 'Ethiopia',
79
+ FI: 'Finland',
80
+ FJ: 'Fiji',
81
+ FK: 'Falkland Islands',
82
+ FM: 'Micronesia',
83
+ FO: 'Faroe Islands',
84
+ FR: 'France',
85
+ GA: 'Gabon',
86
+ GB: 'United Kingdom',
87
+ GD: 'Grenada',
88
+ GE: 'Georgia',
89
+ GF: 'French Guiana',
90
+ GG: 'Guernsey',
91
+ GH: 'Ghana',
92
+ GI: 'Gibraltar',
93
+ GL: 'Greenland',
94
+ GM: 'Gambia',
95
+ GN: 'Guinea',
96
+ GP: 'Guadeloupe',
97
+ GQ: 'Equatorial Guinea',
98
+ GR: 'Greece',
99
+ GS: 'South Georgia & South Sandwich Islands',
100
+ GT: 'Guatemala',
101
+ GU: 'Guam',
102
+ GW: 'Guinea-Bissau',
103
+ GY: 'Guyana',
104
+ HK: 'Hong Kong',
105
+ HM: 'Heard Island and McDonald Islands',
106
+ HN: 'Honduras',
107
+ HR: 'Croatia',
108
+ HT: 'Haiti',
109
+ HU: 'Hungary',
110
+ ID: 'Indonesia',
111
+ IE: 'Ireland',
112
+ IL: 'Israel',
113
+ IM: 'Isle of Man',
114
+ IN: 'India',
115
+ IO: 'British Indian Ocean Territory',
116
+ IQ: 'Iraq',
117
+ IR: 'Iran',
118
+ IS: 'Iceland',
119
+ IT: 'Italy',
120
+ JE: 'Jersey',
121
+ JM: 'Jamaica',
122
+ JO: 'Jordan',
123
+ JP: 'Japan',
124
+ KE: 'Kenya',
125
+ KG: 'Kyrgyzstan',
126
+ KH: 'Cambodia',
127
+ KI: 'Kiribati',
128
+ KM: 'Comoros',
129
+ KN: 'Saint Kitts and Nevis',
130
+ KP: 'North Korea',
131
+ KR: 'South Korea',
132
+ KW: 'Kuwait',
133
+ KY: 'Cayman Islands',
134
+ KZ: 'Kazakhstan',
135
+ LA: 'Laos',
136
+ LB: 'Lebanon',
137
+ LC: 'Saint Lucia',
138
+ LI: 'Liechtenstein',
139
+ LK: 'Sri Lanka',
140
+ LR: 'Liberia',
141
+ LS: 'Lesotho',
142
+ LT: 'Lithuania',
143
+ LU: 'Luxembourg',
144
+ LV: 'Latvia',
145
+ LY: 'Libya',
146
+ MA: 'Morocco',
147
+ MC: 'Monaco',
148
+ MD: 'Moldova',
149
+ ME: 'Montenegro',
150
+ MF: 'Saint Martin',
151
+ MG: 'Madagascar',
152
+ MH: 'Marshall Islands',
153
+ MK: 'Macedonia',
154
+ ML: 'Mali',
155
+ MM: 'Myanmar',
156
+ MN: 'Mongolia',
157
+ MO: 'Macau',
158
+ MP: 'Northern Mariana Islands',
159
+ MQ: 'Martinique',
160
+ MR: 'Mauritania',
161
+ MS: 'Montserrat',
162
+ MT: 'Malta',
163
+ MU: 'Mauritius',
164
+ MV: 'Maldives',
165
+ MW: 'Malawi',
166
+ MX: 'Mexico',
167
+ MY: 'Malaysia',
168
+ MZ: 'Mozambique',
169
+ NA: 'Namibia',
170
+ NC: 'New Caledonia',
171
+ NE: 'Niger',
172
+ NF: 'Norfolk Island',
173
+ NG: 'Nigeria',
174
+ NI: 'Nicaragua',
175
+ NL: 'Netherlands',
176
+ NO: 'Norway',
177
+ NP: 'Nepal',
178
+ NR: 'Nauru',
179
+ NU: 'Niue',
180
+ NZ: 'New Zealand',
181
+ OM: 'Oman',
182
+ PA: 'Panama',
183
+ PE: 'Peru',
184
+ PF: 'French Polynesia',
185
+ PG: 'Papua New Guinea',
186
+ PH: 'Philippines',
187
+ PK: 'Pakistan',
188
+ PL: 'Poland',
189
+ PM: 'Saint Pierre and Miquelon',
190
+ PN: 'Pitcairn Islands',
191
+ PR: 'Puerto Rico',
192
+ PS: 'Palestinian Territory',
193
+ PT: 'Portugal',
194
+ PW: 'Palau',
195
+ PY: 'Paraguay',
196
+ QA: 'Qatar',
197
+ RE: 'Reunion',
198
+ RO: 'Romania',
199
+ RS: 'Serbia',
200
+ RU: 'Russia',
201
+ RW: 'Rwanda',
202
+ SA: 'Saudi Arabia',
203
+ SB: 'Solomon Islands',
204
+ SC: 'Seychelles',
205
+ SD: 'Sudan',
206
+ SE: 'Sweden',
207
+ SG: 'Singapore',
208
+ SH: 'Saint Helena',
209
+ SI: 'Slovenia',
210
+ SJ: 'Svalbard',
211
+ SK: 'Slovakia',
212
+ SL: 'Sierra Leone',
213
+ SM: 'San Marino',
214
+ SN: 'Senegal',
215
+ SO: 'Somalia',
216
+ SR: 'Suriname',
217
+ SS: 'South Sudan',
218
+ ST: 'Sao Tome and Principe',
219
+ SV: 'El Salvador',
220
+ SX: 'Sint Maarten',
221
+ SY: 'Syria',
222
+ SZ: 'Swaziland',
223
+ TA: 'Tristan da Cunha',
224
+ TC: 'Turks and Caicos Islands',
225
+ TD: 'Chad',
226
+ TF: 'French Southern and Antarctic Lands',
227
+ TG: 'Togo',
228
+ TH: 'Thailand',
229
+ TJ: 'Tajikistan',
230
+ TK: 'Tokelau',
231
+ TL: 'East Timor',
232
+ TM: 'Turkmenistan',
233
+ TN: 'Tunisia',
234
+ TO: 'Tonga',
235
+ TR: 'Turkey',
236
+ TT: 'Trinidad and Tobago',
237
+ TV: 'Tuvalu',
238
+ TW: 'Taiwan',
239
+ TZ: 'Tanzania',
240
+ UA: 'Ukraine',
241
+ UG: 'Uganda',
242
+ UM: 'Midway Islands',
243
+ US: 'United States',
244
+ UY: 'Uruguay',
245
+ UZ: 'Uzbekistan',
246
+ VA: 'Vatican City',
247
+ VC: 'Saint Vincent and the Grenadines',
248
+ VE: 'Venezuela',
249
+ VG: 'British Virgin Islands',
250
+ VI: 'U.S. Virgin Islands',
251
+ VN: 'Vietnam',
252
+ VU: 'Vanuatu',
253
+ XK: 'Kosovo',
254
+ WF: 'Wallis and Futuna',
255
+ WS: 'Samoa',
256
+ YE: 'Yemen',
257
+ YT: 'Mayotte',
258
+ ZA: 'South Africa',
259
+ ZM: 'Zambia',
260
+ ZW: 'Zimbabwe',
261
+ ZZ: 'Unknown Region'
262
+ }.with_indifferent_access
263
+
264
+ # The list of U.S. state codes and names used by the YouTube Analytics API.
265
+ # @see https://developers.google.com/youtube/analytics/v1/dimsmets/dims#province
266
+ US_STATES = {
267
+ AL: 'Alabama',
268
+ AK: 'Alaska',
269
+ AZ: 'Arizona',
270
+ AR: 'Arkansas',
271
+ CA: 'California',
272
+ CO: 'Colorado',
273
+ CT: 'Connecticut',
274
+ DE: 'Delaware',
275
+ FL: 'Florida',
276
+ GA: 'Georgia',
277
+ HI: 'Hawaii',
278
+ ID: 'Idaho',
279
+ IL: 'Illinois',
280
+ IN: 'Indiana',
281
+ IA: 'Iowa',
282
+ KS: 'Kansas',
283
+ KY: 'Kentucky',
284
+ LA: 'Louisiana',
285
+ ME: 'Maine',
286
+ MD: 'Maryland',
287
+ MA: 'Massachusetts',
288
+ MI: 'Michigan',
289
+ MN: 'Minnesota',
290
+ MS: 'Mississippi',
291
+ MO: 'Missouri',
292
+ MT: 'Montana',
293
+ NE: 'Nebraska',
294
+ NV: 'Nevada',
295
+ NH: 'New Hampshire',
296
+ NJ: 'New Jersey',
297
+ NM: 'New Mexico',
298
+ NY: 'New York',
299
+ NC: 'North Carolina',
300
+ ND: 'North Dakota',
301
+ OH: 'Ohio',
302
+ OK: 'Oklahoma',
303
+ OR: 'Oregon',
304
+ PA: 'Pennsylvania',
305
+ RI: 'Rhode Island',
306
+ SC: 'South Carolina',
307
+ SD: 'South Dakota',
308
+ TN: 'Tennessee',
309
+ TX: 'Texas',
310
+ UT: 'Utah',
311
+ VT: 'Vermont',
312
+ VA: 'Virginia',
313
+ WA: 'Washington',
314
+ WV: 'West Virginia',
315
+ WI: 'Wisconsin',
316
+ WY: 'Wyoming',
317
+ DC: 'District of Columbia',
318
+ AS: 'American Samoa',
319
+ GU: 'Guam',
320
+ MP: 'Northern Mariana Islands',
321
+ PR: 'Puerto Rico',
322
+ UM: 'United States Minor Outlying Islands',
323
+ VI: 'Virgin Islands, U.S.',
324
+ ZZ: 'Unknown Region'
325
+ }.with_indifferent_access
326
+ end