yt 0.25.13 → 0.32.2

Sign up to get free protection for your applications and to get access to all the features.
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