Skip to content

Commit

Permalink
Merge pull request #2 from jdaviderb/update-decipher
Browse files Browse the repository at this point in the history
update decipher object
  • Loading branch information
jdaviderb authored Sep 18, 2019
2 parents 7eb737f + 1e7f5f1 commit 87ec559
Show file tree
Hide file tree
Showing 16 changed files with 12,113 additions and 79 deletions.
14 changes: 7 additions & 7 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2019-09-03 01:03:51 -0500 using RuboCop version 0.74.0.
# on 2019-09-17 22:24:06 -0500 using RuboCop version 0.74.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 5
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
# AllowedNames: io, id, to, by, on, in, at, ip, db
Naming/UncommunicativeMethodParamName:
Exclude:
- 'lib/youtube_audio/decipher.rb'
# Offense count: 4
# Cop supports --auto-correct.
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
# URISchemes: http, https
Metrics/LineLength:
Max: 109
6 changes: 3 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
youtube_audio (0.1.0)
youtube_audio (0.2.0)
mechanize
mini_racer

Expand Down Expand Up @@ -33,9 +33,9 @@ GEM
nokogiri (~> 1.6)
ntlm-http (~> 0.1, >= 0.1.1)
webrobots (>= 0.0.9, < 0.2)
mime-types (3.2.2)
mime-types (3.3)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.0331)
mime-types-data (3.2019.0904)
mini_portile2 (2.4.0)
mini_racer (0.2.6)
libv8 (>= 6.9.411)
Expand Down
5,982 changes: 5,982 additions & 0 deletions fixtures/vcr_cassettes/youtube/player_base_js.yml

Large diffs are not rendered by default.

6,037 changes: 6,007 additions & 30 deletions fixtures/vcr_cassettes/youtube/xoWRkd3oGcs.yml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions lib/youtube_audio.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'mechanize'
require 'mini_racer'
require 'cgi'
require 'net/http'
require 'youtube_audio/version'
require 'youtube_audio/url_decipher'
require 'youtube_audio/download'
Expand All @@ -13,6 +14,7 @@
require 'youtube_audio/decipher'
require 'youtube_audio/search'
require 'youtube_audio/search_item'
require 'youtube_audio/extract_decode_function'

module YoutubeAudio
end
42 changes: 22 additions & 20 deletions lib/youtube_audio/decipher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,36 @@

module YoutubeAudio
class Decipher
attr_reader :script_player_url
URL = 'https://www.youtube.com'

def initialize(script_player_url)
@script_player_url = script_player_url
end

# @param cipher [string] youtube signature
def decipher(cipher)
decipher = cipher.split('')
pb(decipher, 35)
pb(decipher, 30)
wh(decipher)
pb(decipher, 2)
decipher = p7(decipher, 3)
wh(decipher)
pb(decipher, 8)
decipher = p7(decipher, 2)
decipher.join('')
klass = extract_decode_function_handler
miniracer_klass = miniracer_context

miniracer_klass.eval(klass.cipher_helpers_object)
miniracer_klass.eval("var #{klass.decipher_function}")
miniracer_klass.eval("#{klass.decryption_function}('#{cipher}')")
end

def pb(a, b)
c = a[0]
a[0] = a[b % a.length]
a[b % a.length] = c
private

def extract_decode_function_handler
@extract_decode_function_handler ||=
ExtractDecodeFunction.new(script_player_content)
end

def wh(a)
result = a.reverse!
result
def miniracer_context
MiniRacer::Context.new
end

def p7(a, b)
result = a.slice(b, a.length)
result
def script_player_content
@script_player_content ||= Net::HTTP.get(URI(URL + script_player_url))
end
end
end
39 changes: 39 additions & 0 deletions lib/youtube_audio/extract_decode_function.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

module YoutubeAudio
class ExtractDecodeFunction
attr_reader :player_script
CIPHER_TEST = Regexp.new('([\\w$]+)\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(""\\)\\s*;')

def initialize(player_script)
@player_script = player_script
end

def cipher_helper_object_name
@cipher_helper_object_name ||=
decipher_function.scan(/\;([a-zA-Z0-9]{0,255})\./).flatten.first
end

def cipher_helpers_object
object_decibper_index = player_script.index("var #{cipher_helper_object_name}={")
object_decibper_index_end = player_script[object_decibper_index..-1].index('};')
player_script[object_decibper_index..object_decibper_index + object_decibper_index_end]
end

def decipher_function
@decipher_function ||=
player_script.scan(Regexp.new(function_pattern)).flatten.first
end

def decryption_function
@decryption_function ||= player_script.scan(CIPHER_TEST).flatten.first
end

private

def function_pattern
@function_pattern ||=
'(' + decryption_function + '=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})'
end
end
end
12 changes: 10 additions & 2 deletions lib/youtube_audio/format.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

module YoutubeAudio
class Format
def initialize(response_raw)
attr_reader :script_player_url

def initialize(response_raw, script_player_url: nil)
@script_player_url = script_player_url
@response_raw = response_raw
end

Expand All @@ -11,7 +14,12 @@ def audio?
end

def url
return UrlDecipher.new(cipher).decipher if cipher
if cipher
return UrlDecipher.new(
cipher,
script_player_url: script_player_url
).decipher
end

@response_raw&.dig('url')
end
Expand Down
7 changes: 5 additions & 2 deletions lib/youtube_audio/player_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

module YoutubeAudio
class PlayerResponse
attr_reader :script_player_url

# @response_raw url [Hash]
def initialize(response_raw)
def initialize(response_raw, script_player_url: nil)
@response_raw = response_raw
@script_player_url = script_player_url
end

def formats
Expand All @@ -14,7 +17,7 @@ def formats
end

def to_youtube_format(format_raw)
Format.new(format_raw)
Format.new(format_raw, script_player_url: script_player_url)
end
end
end
6 changes: 5 additions & 1 deletion lib/youtube_audio/script_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ def player_response
mini_racer_context.eval(@script)
# get player response ...
response = mini_racer_context.eval('ytInitialPlayerConfig')
PlayerResponse.new(JSON.parse(response['args']['player_response']))

PlayerResponse.new(
JSON.parse(response['args']['player_response']),
script_player_url: response['assets']['js']
)
end
end
end
10 changes: 6 additions & 4 deletions lib/youtube_audio/url_decipher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

module YoutubeAudio
class UrlDecipher
attr_reader :cipher
attr_reader :cipher, :script_player_url

def initialize(cipher)
def initialize(cipher, script_player_url:)
@cipher = cipher
@script_player_url = script_player_url
end

def decipher
decoded = CGI.parse(@cipher)

signature = Decipher.new.decipher(decoded.dig('s').first)
decoded.dig('url').first + '&' + decoded.dig('sp').first + "=#{signature}"
sign = Decipher.new(script_player_url).decipher(decoded.dig('s').first)

decoded.dig('url').first + '&' + decoded.dig('sp').first + "=#{sign}"
end
end
end
2 changes: 1 addition & 1 deletion lib/youtube_audio/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module YoutubeAudio
VERSION = '0.1.0'
VERSION = '0.2.0'
end
14 changes: 10 additions & 4 deletions spec/decipher_spec.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
# frozen_string_literal: true

RSpec.describe YoutubeAudio::Decipher do
subject { described_class.new }
let(:player_base_url) do
'/yts/jsbin/player-plasma-ias-phone-en_US-vflZqC1Iu/base.js'
end

subject { described_class.new(player_base_url) }

describe '#decipher' do
let(:signature) do
'qLALgxI2AwRAIgbG7LwidtAcuTBa1Rw8hp5sUXLESfIyXM2MHr4m6jDtUCIARjHJ-5Y' \
'KOvBfrSPrfc9N62UwxcUvy4kVFNQRL_2glvvlg'
end
let(:decipher) do
'ALgxI2wwRAIgbG7LwidtAcuTBa1Rs8hp5qUXLESfIyXM2MHr4m6jDtUCIARjHJ-5' \
'YKOvBfrSPrfc9N62UwxcUvy4kVFNQRL_2glv'
'vgg2_LRQNFVk4yvUcxwU26N9cfrPSrfBvOKY5-JHjRAICUtDj6m4rHM2MXyIfSELX' \
'Uslph8wR1aBTucAtdiwL7GbgIARwA2Ixq'
end

it 'returns decipher signature' do
expect(subject.decipher(signature)).to eq(decipher)
VCR.use_cassette('youtube/player_base.js') do
expect(subject.decipher(signature)).to eq(decipher)
end
end
end
end
1 change: 1 addition & 0 deletions spec/download_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
VCR.use_cassette('youtube/xoWRkd3oGcs') do
expect(subject.formats.length).to eq(4)
expect(subject.formats.first).to be_instance_of(YoutubeAudio::Format)
expect(subject.formats.first.url).to match('mp4')
expect(subject.formats.first.audio_quality).to match('AUDIO_Q')
end
end
Expand Down
5 changes: 3 additions & 2 deletions spec/format_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
)
end

subject { described_class.new(response_raw) }
subject { described_class.new(response_raw, script_player_url: 'mock') }

describe '#mime_type' do
it { expect(subject.mime_type).to eq('audio/mp4') }
Expand All @@ -36,7 +36,8 @@
.and_return('url=mock&s=mock-signature&sp=sig')

expect(YoutubeAudio::UrlDecipher).to receive(:new)
.with('url=mock&s=mock-signature&sp=sig')
.with('url=mock&s=mock-signature&sp=sig',
script_player_url: 'mock')
.and_return(mock)

expect(subject.url).to eq('decipher')
Expand Down
13 changes: 10 additions & 3 deletions spec/url_decipher_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,19 @@
RSpec.describe YoutubeAudio::UrlDecipher do
let(:cipher) { 'url=mock&s=mock-signature&sp=sig' }

subject { described_class.new(cipher) }
subject do
described_class.new(cipher, script_player_url: 'script_player_url_mock')
end

describe '#decipher' do
before do
expect(YoutubeAudio::Decipher).to receive(:new)
.with('script_player_url_mock')
.and_return(double(decipher: 'sig-mock'))
end

it 'deciphers a cipher url' do
expect(YoutubeAudio::Decipher).to receive(:new).and_call_original
expect(subject.decipher).to eq('mock&sig=gk-simcat')
expect(subject.decipher).to eq('mock&sig=sig-mock')
end
end
end

0 comments on commit 87ec559

Please sign in to comment.