Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ gem 'pry', platforms: not_jruby
gem 'simplecov'
gem 'simplecov-cobertura'
gem 'yard'
gem 'irb'
4 changes: 2 additions & 2 deletions lib/yt/actions/get.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def get
private

def get_request(params = {})
@list_request = Yt::Request.new(params).tap do |request|
@get_request = Yt::Request.new(params).tap do |request|
print "#{request.as_curl}\n" if Yt.configuration.developing?
end
end
Expand All @@ -30,4 +30,4 @@ def get_params
end
end
end
end
end
2 changes: 1 addition & 1 deletion lib/yt/associations/has_authentication.rb
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def authentication_url_params
params[:redirect_uri] = @redirect_uri
params[:response_type] = :code
params[:access_type] = :offline
params[:approval_prompt] = @force ? :force : :auto
params[:prompt] = :consent if @force
# params[:include_granted_scopes] = true
params[:state] = @state if @state
end
Expand Down
4 changes: 3 additions & 1 deletion lib/yt/collections/reports.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ class Reports < Base
product_page: 'PRODUCT_PAGE',
shorts: 'SHORTS',
sound_page: 'SOUND_PAGE',
video_remixes: 'VIDEO_REMIXES'
video_remixes: 'VIDEO_REMIXES',
immersive_live: 'IMMERSIVE_LIVE',
shorts_content_links: 'SHORTS_CONTENT_LINKS'
}

# @see https://developers.google.com/youtube/analytics/dimensions#Playback_Location_Dimensions
Expand Down
33 changes: 33 additions & 0 deletions lib/yt/models/caption.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'yt/models/resource'
require "fileutils"

module Yt
module Models
Expand All @@ -14,6 +15,38 @@ class Caption < Resource
delegate :language, to: :snippet
delegate :name, to: :snippet
delegate :status, to: :snippet

# Downloads a caption file.
# @param [String] path A name for the downloaded file with caption content.
# @see https://developers.google.com/youtube/v3/docs/captions#resource
def download(path)
case io
when StringIO then File.open(path, 'w') { |f| f.write(io.read) }
when Tempfile then io.close; FileUtils.mv(io.path, path)
end
end

def io
@io ||= get_request(download_params).open_uri
end

private

# @return [Hash] the parameters to submit to YouTube to download caption.
# @see https://developers.google.com/youtube/v3/docs/captions/download
def download_params
{}.tap do |params|
params[:method] = :get
params[:host] = 'youtube.googleapis.com'
params[:auth] = @auth
params[:exptected_response] = Net::HTTPOK
params[:api_key] = Yt.configuration.api_key if Yt.configuration.api_key
params[:path] = "/youtube/v3/captions/#{@id}"
if @auth.owner_name
params[:params] = {on_behalf_of_content_owner: @auth.owner_name}
end
end
end
end
end
end
67 changes: 45 additions & 22 deletions lib/yt/models/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ class Resource < Base

# @!attribute [r] id
# @return [String] the ID that YouTube uses to identify each resource.
def id
if @id.nil? && @match && @match[:kind] == :channel
@id ||= fetch_channel_id
else
@id
end
end
attr_reader :id

### STATUS ###

Expand Down Expand Up @@ -51,7 +45,11 @@ def initialize(options = {})
if options[:url]
@url = options[:url]
@match = find_pattern_match
@id = @match['id']
if kind == "channel" && @match.key?('format')
@id ||= fetch_channel_id
else
@id = @match['id']
end
else
@id = options[:id]
end
Expand Down Expand Up @@ -98,16 +96,16 @@ def update(attributes = {})
# @return [Array<Regexp>] patterns matching URLs of YouTube channels.
CHANNEL_PATTERNS = [
%r{^(?:https?://)?(?:www\.)?youtube\.com/channel/(?<id>UC[a-zA-Z0-9_-]{22})},
%r{^(?:https?://)?(?:www\.)?youtube\.com/(?<format>c/|user/)?(?<name>[a-zA-Z0-9_-]+)}
%r{^(?:https?://)?(?:www\.)?youtube\.com/(?<format>c/|user/)?(?<name>[a-zA-Z0-9_-]+)},
%r{^(?:https?://)?(?:www\.)?youtube\.com/(?<format>@)(?<name>[a-zA-Z0-9_-]+)}
]

private

def find_pattern_match
patterns.find do |kind, regex|
if data = @url.match(regex)
# Note: With Ruby 2.4, the following is data.named_captures
break data.names.zip(data.captures).to_h.merge kind: kind
break data.named_captures.merge kind: kind
end
end || {kind: :unknown}
end
Expand All @@ -123,19 +121,44 @@ def patterns
end

def fetch_channel_id
response = Net::HTTP.start 'www.youtube.com', 443, use_ssl: true do |http|
http.request Net::HTTP::Get.new("/#{@match['format']}#{@match['name']}")
end
if response.is_a?(Net::HTTPRedirection)
api_key = Yt.configuration.api_key if Yt.configuration.api_key
case @match['format']
when "@"
handle = "@#{@match['name']}"
response = Net::HTTP.start 'youtube.googleapis.com', 443, use_ssl: true do |http|
http.request Net::HTTP::Get.new("/youtube/v3/channels?part=snippet&forHandle=#{handle}&key=#{api_key}")
end
if response.is_a?(Net::HTTPOK) && item = JSON(response.body)['items']&.first
item['id']
else
raise Yt::Errors::NoItems
end
when "user/"
username = @match['name']
response = Net::HTTP.start 'youtube.googleapis.com', 443, use_ssl: true do |http|
http.request Net::HTTP::Get.new("/youtube/v3/channels?part=snippet&forUsername=#{username}&key=#{api_key}")
end
if response.is_a?(Net::HTTPOK) && item = JSON(response.body)['items']&.first
item['id']
else
raise Yt::Errors::NoItems
end
else # "c/", nil
response = Net::HTTP.start 'www.youtube.com', 443, use_ssl: true do |http|
http.request Net::HTTP::Get.new(response['location'])
http.request Net::HTTP::Get.new("/#{@match['format']}#{@match['name']}")
end
if response.is_a?(Net::HTTPRedirection)
response = Net::HTTP.start 'www.youtube.com', 443, use_ssl: true do |http|
http.request Net::HTTP::Get.new(response['location'])
end
end
# puts response.body
regex = %r{(?<id>UC[a-zA-Z0-9_-]{22})}
if data = response.body.match(regex)
data[:id]
else
raise Yt::Errors::NoItems
end
end
regex = %r{<meta itemprop="channelId" content="(?<id>UC[a-zA-Z0-9_-]{22})">}
if data = response.body.match(regex)
data[:id]
else
raise Yt::Errors::NoItems
end
end

Expand Down
5 changes: 5 additions & 0 deletions lib/yt/request.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'net/http' # for Net::HTTP.start
require 'uri' # for URI.json
require 'json' # for JSON.parse
require "open-uri" # for URI.open
require 'active_support' # does not load anything by default, but is required
require 'active_support/core_ext' # for Hash.from_xml, Hash.to_param

Expand Down Expand Up @@ -84,6 +85,10 @@ def run
end
end

def open_uri
URI.open(uri.to_s, 'Authorization' => "Bearer #{@auth.access_token}")
end

# Returns the +cURL+ version of the request, useful to re-run the request
# in a shell terminal.
# @return [String] the +cURL+ version of the request.
Expand Down
2 changes: 1 addition & 1 deletion spec/requests/as_account/authentications_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@

context 'given a forced approval prompt' do
let(:attrs) { auth_attrs.merge force: true }
it { expect(account.authentication_url).to match 'approval_prompt=force' }
it { expect(account.authentication_url).to match 'prompt=consent' }
end
end
end
Expand Down
93 changes: 46 additions & 47 deletions spec/requests/as_server_app/url_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,51 +45,50 @@
it {expect{url.resource}.to raise_error Yt::Errors::NoItems }
end

# # TODO: need to fix our code, as YouTube behavior changes
# context 'given a YouTube channel URL in the name form' do
# let(:text) { "http://www.youtube.com/#{name}" }

# describe 'works when the name matches the custom URL' do
# let(:name) { 'nbcsports' }
# it {expect(url.id).to eq 'UCqZQlzSHbVJrwrn5XvzrzcA' }
# end

# describe 'works when the name matches the username' do
# let(:name) { '2012NBCOlympics' }
# it {expect(url.id).to eq 'UCqZQlzSHbVJrwrn5XvzrzcA' }
# end

# describe 'fails with unknown channels' do
# let(:name) { 'not-an-actual-channel' }
# it {expect{url.id}.to raise_error Yt::Errors::NoItems }
# end
# end

# context 'given a YouTube channel URL in the custom form' do
# let(:text) { "https://youtube.com/c/#{name}" }

# describe 'works with existing channels' do
# let(:name) { 'ogeeku' }
# it {expect(url.id).to eq 'UC4nG_NxJniKoB-n6TLT2yaw' }
# end

# describe 'fails with unknown channels' do
# let(:name) { 'not-an-actual-channel' }
# it {expect{url.id}.to raise_error Yt::Errors::NoItems }
# end
# end

# context 'given a YouTube channel URL in the username form' do
# let(:text) { "youtube.com/user/#{name}" }

# describe 'works with existing channels' do
# let(:name) { 'ogeeku' }
# it {expect(url.id).to eq 'UC4lU5YG9QDgs0X2jdnt7cdQ' }
# end

# describe 'fails with unknown channels' do
# let(:name) { 'not-an-actual-channel' }
# it {expect{url.id}.to raise_error Yt::Errors::NoItems }
# end
# end
context 'given a YouTube channel URL in the name form' do
let(:text) { "http://www.youtube.com/#{name}" }

describe 'works when the name matches the custom URL' do
let(:name) { 'nbcsports' }
it {expect(url.id).to eq 'UCqZQlzSHbVJrwrn5XvzrzcA' }
end

describe 'works when the name matches the username' do
let(:name) { '2012NBCOlympics' }
it {expect(url.id).to eq 'UCqZQlzSHbVJrwrn5XvzrzcA' }
end

describe 'fails with unknown channels' do
let(:name) { 'not-an-actual-channel' }
it {expect{url.id}.to raise_error Yt::Errors::NoItems }
end
end

context 'given a YouTube channel URL in the custom form' do
let(:text) { "https://youtube.com/c/#{name}" }

describe 'works with existing channels' do
let(:name) { 'ogeeku' }
it {expect(url.id).to eq 'UC4nG_NxJniKoB-n6TLT2yaw' }
end

describe 'fails with unknown channels' do
let(:name) { 'not-an-actual-channel' }
it {expect{url.id}.to raise_error Yt::Errors::NoItems }
end
end

context 'given a YouTube channel URL in the username form' do
let(:text) { "youtube.com/user/#{name}" }

describe 'works with existing channels' do
let(:name) { 'ogeeku' }
it {expect(url.id).to eq 'UC4lU5YG9QDgs0X2jdnt7cdQ' }
end

describe 'fails with unknown channels' do
let(:name) { 'not-an-actual-channel' }
it {expect{url.id}.to raise_error Yt::Errors::NoItems }
end
end
end
6 changes: 6 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@
ENV['YT_TEST_API_KEY'] ||= 'ZZZ'
ENV['YT_TEST_REFRESH_TOKEN'] ||= 'ABC'

ENV['YT_TEST_CONTENT_OWNER_NAME'] ||= 'abcd'
ENV['YT_TEST_PARTNER_CLIENT_ID'] ||= 'abcd'
ENV['YT_TEST_PARTNER_CLIENT_SECRET'] ||= 'abcd'
ENV['YT_TEST_CONTENT_OWNER_REFRESH_TOKEN'] ||= 'abcd'
ENV['YT_TEST_CONTENT_OWNER_ACCESS_TOKEN'] ||= 'abcd'

Dir['./spec/support/**/*.rb'].each {|f| require f}

RSpec.configure do |config|
Expand Down