youtube_audio 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +7 -7
- data/.travis.yml +8 -0
- data/Gemfile.lock +3 -3
- data/README.md +16 -4
- data/fixtures/vcr_cassettes/youtube/player_base_js.yml +5982 -0
- data/fixtures/vcr_cassettes/youtube/xoWRkd3oGcs.yml +6007 -30
- data/lib/youtube_audio/decipher.rb +22 -20
- data/lib/youtube_audio/extract_decode_function.rb +39 -0
- data/lib/youtube_audio/format.rb +10 -2
- data/lib/youtube_audio/player_response.rb +5 -2
- data/lib/youtube_audio/script_parser.rb +5 -1
- data/lib/youtube_audio/url_decipher.rb +6 -4
- data/lib/youtube_audio/version.rb +1 -1
- data/lib/youtube_audio.rb +2 -0
- metadata +4 -2
@@ -2,34 +2,36 @@
|
|
2
2
|
|
3
3
|
module YoutubeAudio
|
4
4
|
class Decipher
|
5
|
+
attr_reader :script_player_url
|
6
|
+
URL = 'https://www.youtube.com'
|
7
|
+
|
8
|
+
def initialize(script_player_url)
|
9
|
+
@script_player_url = script_player_url
|
10
|
+
end
|
11
|
+
|
5
12
|
# @param cipher [string] youtube signature
|
6
13
|
def decipher(cipher)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
wh(decipher)
|
14
|
-
pb(decipher, 8)
|
15
|
-
decipher = p7(decipher, 2)
|
16
|
-
decipher.join('')
|
14
|
+
klass = extract_decode_function_handler
|
15
|
+
miniracer_klass = miniracer_context
|
16
|
+
|
17
|
+
miniracer_klass.eval(klass.cipher_helpers_object)
|
18
|
+
miniracer_klass.eval("var #{klass.decipher_function}")
|
19
|
+
miniracer_klass.eval("#{klass.decryption_function}('#{cipher}')")
|
17
20
|
end
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
private
|
23
|
+
|
24
|
+
def extract_decode_function_handler
|
25
|
+
@extract_decode_function_handler ||=
|
26
|
+
ExtractDecodeFunction.new(script_player_content)
|
23
27
|
end
|
24
28
|
|
25
|
-
def
|
26
|
-
|
27
|
-
result
|
29
|
+
def miniracer_context
|
30
|
+
MiniRacer::Context.new
|
28
31
|
end
|
29
32
|
|
30
|
-
def
|
31
|
-
|
32
|
-
result
|
33
|
+
def script_player_content
|
34
|
+
@script_player_content ||= Net::HTTP.get(URI(URL + script_player_url))
|
33
35
|
end
|
34
36
|
end
|
35
37
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module YoutubeAudio
|
4
|
+
class ExtractDecodeFunction
|
5
|
+
attr_reader :player_script
|
6
|
+
CIPHER_TEST = Regexp.new('([\\w$]+)\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(""\\)\\s*;')
|
7
|
+
|
8
|
+
def initialize(player_script)
|
9
|
+
@player_script = player_script
|
10
|
+
end
|
11
|
+
|
12
|
+
def cipher_helper_object_name
|
13
|
+
@cipher_helper_object_name ||=
|
14
|
+
decipher_function.scan(/\;([a-zA-Z0-9]{0,255})\./).flatten.first
|
15
|
+
end
|
16
|
+
|
17
|
+
def cipher_helpers_object
|
18
|
+
object_decibper_index = player_script.index("var #{cipher_helper_object_name}={")
|
19
|
+
object_decibper_index_end = player_script[object_decibper_index..-1].index('};')
|
20
|
+
player_script[object_decibper_index..object_decibper_index + object_decibper_index_end]
|
21
|
+
end
|
22
|
+
|
23
|
+
def decipher_function
|
24
|
+
@decipher_function ||=
|
25
|
+
player_script.scan(Regexp.new(function_pattern)).flatten.first
|
26
|
+
end
|
27
|
+
|
28
|
+
def decryption_function
|
29
|
+
@decryption_function ||= player_script.scan(CIPHER_TEST).flatten.first
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def function_pattern
|
35
|
+
@function_pattern ||=
|
36
|
+
'(' + decryption_function + '=function\\([a-zA-Z0-9_]+\\)\\{.+?\\})'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/youtube_audio/format.rb
CHANGED
@@ -2,7 +2,10 @@
|
|
2
2
|
|
3
3
|
module YoutubeAudio
|
4
4
|
class Format
|
5
|
-
|
5
|
+
attr_reader :script_player_url
|
6
|
+
|
7
|
+
def initialize(response_raw, script_player_url: nil)
|
8
|
+
@script_player_url = script_player_url
|
6
9
|
@response_raw = response_raw
|
7
10
|
end
|
8
11
|
|
@@ -11,7 +14,12 @@ module YoutubeAudio
|
|
11
14
|
end
|
12
15
|
|
13
16
|
def url
|
14
|
-
|
17
|
+
if cipher
|
18
|
+
return UrlDecipher.new(
|
19
|
+
cipher,
|
20
|
+
script_player_url: script_player_url
|
21
|
+
).decipher
|
22
|
+
end
|
15
23
|
|
16
24
|
@response_raw&.dig('url')
|
17
25
|
end
|
@@ -2,9 +2,12 @@
|
|
2
2
|
|
3
3
|
module YoutubeAudio
|
4
4
|
class PlayerResponse
|
5
|
+
attr_reader :script_player_url
|
6
|
+
|
5
7
|
# @response_raw url [Hash]
|
6
|
-
def initialize(response_raw)
|
8
|
+
def initialize(response_raw, script_player_url: nil)
|
7
9
|
@response_raw = response_raw
|
10
|
+
@script_player_url = script_player_url
|
8
11
|
end
|
9
12
|
|
10
13
|
def formats
|
@@ -14,7 +17,7 @@ module YoutubeAudio
|
|
14
17
|
end
|
15
18
|
|
16
19
|
def to_youtube_format(format_raw)
|
17
|
-
Format.new(format_raw)
|
20
|
+
Format.new(format_raw, script_player_url: script_player_url)
|
18
21
|
end
|
19
22
|
end
|
20
23
|
end
|
@@ -16,7 +16,11 @@ module YoutubeAudio
|
|
16
16
|
mini_racer_context.eval(@script)
|
17
17
|
# get player response ...
|
18
18
|
response = mini_racer_context.eval('ytInitialPlayerConfig')
|
19
|
-
|
19
|
+
|
20
|
+
PlayerResponse.new(
|
21
|
+
JSON.parse(response['args']['player_response']),
|
22
|
+
script_player_url: response['assets']['js']
|
23
|
+
)
|
20
24
|
end
|
21
25
|
end
|
22
26
|
end
|
@@ -2,17 +2,19 @@
|
|
2
2
|
|
3
3
|
module YoutubeAudio
|
4
4
|
class UrlDecipher
|
5
|
-
attr_reader :cipher
|
5
|
+
attr_reader :cipher, :script_player_url
|
6
6
|
|
7
|
-
def initialize(cipher)
|
7
|
+
def initialize(cipher, script_player_url:)
|
8
8
|
@cipher = cipher
|
9
|
+
@script_player_url = script_player_url
|
9
10
|
end
|
10
11
|
|
11
12
|
def decipher
|
12
13
|
decoded = CGI.parse(@cipher)
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
sign = Decipher.new(script_player_url).decipher(decoded.dig('s').first)
|
16
|
+
|
17
|
+
decoded.dig('url').first + '&' + decoded.dig('sp').first + "=#{sign}"
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
data/lib/youtube_audio.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'mechanize'
|
4
4
|
require 'mini_racer'
|
5
5
|
require 'cgi'
|
6
|
+
require 'net/http'
|
6
7
|
require 'youtube_audio/version'
|
7
8
|
require 'youtube_audio/url_decipher'
|
8
9
|
require 'youtube_audio/download'
|
@@ -13,6 +14,7 @@ require 'youtube_audio/script_parser'
|
|
13
14
|
require 'youtube_audio/decipher'
|
14
15
|
require 'youtube_audio/search'
|
15
16
|
require 'youtube_audio/search_item'
|
17
|
+
require 'youtube_audio/extract_decode_function'
|
16
18
|
|
17
19
|
module YoutubeAudio
|
18
20
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: youtube_audio
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jorge Hernandez
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-09-
|
11
|
+
date: 2019-09-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -157,10 +157,12 @@ files:
|
|
157
157
|
- bin/console
|
158
158
|
- bin/setup
|
159
159
|
- fixtures/vcr_cassettes/search/results.yml
|
160
|
+
- fixtures/vcr_cassettes/youtube/player_base_js.yml
|
160
161
|
- fixtures/vcr_cassettes/youtube/xoWRkd3oGcs.yml
|
161
162
|
- lib/youtube_audio.rb
|
162
163
|
- lib/youtube_audio/decipher.rb
|
163
164
|
- lib/youtube_audio/download.rb
|
165
|
+
- lib/youtube_audio/extract_decode_function.rb
|
164
166
|
- lib/youtube_audio/format.rb
|
165
167
|
- lib/youtube_audio/formats.rb
|
166
168
|
- lib/youtube_audio/player_response.rb
|