Skip to content

Commit a8a141c

Browse files
committed
new: chrome extension to extract key and urls
1 parent 1a2e0f6 commit a8a141c

15 files changed

+309
-1
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,14 @@ A Dockerfile is also present. You can use it to create your build or download it
4444
-u string
4545
Url master m3u8
4646
-w int
47-
Number of workers to download the segments (default 4)
47+
Number of workers to download the segments (default 4)
48+
49+
## Chrome extension
50+
51+
Using the Chrome extension is it possible to extract key and URL directly from the browser.
52+
53+
Once [installed](https://dev.to/ben/how-to-install-chrome-extensions-manually-from-github-1612) you'll find a new tab
54+
in the webinspector called "Flowdownloader".
55+
Open the page with the video, open the WebInspector and go to the `Flowdownloader` tab. Press play on the video and enjoy :)
56+
57+
![Flowdownloader](https://raw.githubusercontent.com/Matrix86/flowdownloader/master/extension.gif)

extension.gif

270 KB
Loading

extension/css/bootstrap.min.css

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!doctype html>
2+
3+
<html>
4+
5+
<head>
6+
<title>Flowdowloader</title>
7+
<script src="/js/jquery-3.5.1.min.js" type="text/javascript"></script>
8+
<script src="/devtools/devtools-page.js" type="text/javascript"></script>
9+
</head>
10+
11+
</html>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
(function() {
2+
chrome.devtools.panels.create(
3+
'Flowdownloader',
4+
'images/icon32.png',
5+
'devtools/panel.html',
6+
function(panel) {
7+
}
8+
);
9+
})();

extension/devtools/panel.html

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<html>
2+
3+
<head>
4+
<link rel="stylesheet" href="/css/bootstrap.min.css">
5+
6+
<script type="text/javascript" src="/js/jquery-3.5.1.min.js">
7+
</script>
8+
<script type="text/javascript" src="panel.js"></script>
9+
<script type="text/javascript" src="/js/bootstrap.min.js">
10+
</script>
11+
<style>
12+
body {
13+
background-color: #0769ad;
14+
}
15+
16+
.alert-success .btn-success {
17+
float: right;
18+
}
19+
20+
.alert-success > div:after {
21+
clear: both;
22+
content: '';
23+
display: table;
24+
}
25+
</style>
26+
</head>
27+
<body>
28+
<div class="container" style="padding-top: 10px;">
29+
<div class="jumbotron">
30+
<h1 class="display-4">Flowdowloader is ready</h1>
31+
<p class="lead">Open the video and press play to try extracting key.</p>
32+
<hr class="my-4">
33+
<div id="spinner" class="d-flex justify-content-center">
34+
<div class="spinner-border text-success" role="status">
35+
<span class="sr-only">Listening...</span>
36+
</div>
37+
</div>
38+
39+
<div class="alert alert-success" id="content" style="display: none;">
40+
<div class="container">
41+
<div class="row">
42+
<div class="col-11">
43+
<input id="command" class="form-control" type="text" readonly>
44+
</div>
45+
<div class="col-1">
46+
<button type="button" class="btn btn-outline-success" id="copyBtn">Copy</button>
47+
</div>
48+
</div>
49+
<br/>
50+
<div class="row">
51+
<div class="col">
52+
<div class="input-group mb-3">
53+
<div class="input-group-prepend">
54+
<span class="input-group-text" id="basic-addon1">Primary URL</span>
55+
</div>
56+
<input type="text" class="form-control" aria-describedby="basic-addon1" id="primary_txt" readonly>
57+
</div>
58+
</div>
59+
</div>
60+
<div class="row">
61+
<div class="col">
62+
<div class="input-group mb-3">
63+
<div class="input-group-prepend">
64+
<span class="input-group-text" id="basic-addon1">Secondary URL</span>
65+
</div>
66+
<input type="text" class="form-control" aria-describedby="basic-addon1" id="secondary_txt" readonly>
67+
</div>
68+
</div>
69+
</div>
70+
<div class="row">
71+
<div class="col">
72+
<div class="input-group mb-3">
73+
<div class="input-group-prepend">
74+
<span class="input-group-text" id="basic-addon1">Key</span>
75+
</div>
76+
<input type="text" class="form-control" aria-describedby="basic-addon1" id="key_txt" readonly>
77+
</div>
78+
</div>
79+
</div>
80+
<div class="row">
81+
<div class="col">
82+
<div class="input-group mb-3">
83+
<div class="input-group-prepend">
84+
<span class="input-group-text" id="basic-addon1">IV</span>
85+
</div>
86+
<input type="text" class="form-control" aria-describedby="basic-addon1" id="iv_txt" readonly>
87+
</div>
88+
</div>
89+
</div>
90+
91+
92+
</div>
93+
</div>
94+
</div>
95+
</div>
96+
</body>
97+
98+
</html>

extension/devtools/panel.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
var keyURL = "";
2+
3+
class UI {
4+
key = "";
5+
primaryURL = "";
6+
secondaryURL = "";
7+
IV = "";
8+
9+
constructor() {
10+
$('#spinner').css('visibility', 'visible');
11+
$('#content').hide();
12+
}
13+
14+
setPrimaryUrl(url) {
15+
this.primaryURL = url;
16+
this.renderCommand();
17+
}
18+
19+
setSecondaryUrl(url) {
20+
this.secondaryURL = url;
21+
this.renderCommand();
22+
}
23+
24+
setIV(iv) {
25+
this.IV = iv;
26+
this.renderCommand();
27+
}
28+
29+
setKey(key) {
30+
this.key = key;
31+
this.renderCommand();
32+
}
33+
34+
renderCommand() {
35+
var command = "flowdownloader";
36+
if(this.key != "") {
37+
command += " -k \"" + this.key + "\""
38+
$('#key_txt').val(this.key);
39+
}
40+
41+
if(this.IV != "") {
42+
$('#iv_txt').val(this.IV);
43+
}
44+
45+
if(this.secondaryURL != "") {
46+
$('#secondary_txt').val(this.secondaryURL);
47+
}
48+
49+
if(this.primaryURL != "") {
50+
command += " -u " + this.primaryURL;
51+
$('#primary_txt').val(this.primaryURL);
52+
} else {
53+
command += " -s -u " + this.secondaryURL;
54+
}
55+
56+
$('#command').val(command);
57+
$('#spinner').css('visibility', 'hidden');
58+
$('#content').show();
59+
}
60+
}
61+
62+
var ui = new UI();
63+
64+
chrome.devtools.network.onRequestFinished.addListener(
65+
function(data) {
66+
if(data.request.url.endsWith(".m3u8")) {
67+
console.log("request received: ", data.request.url);
68+
var currentURL = data.request.url;
69+
70+
data.getContent(
71+
function(content, encoding){
72+
if(encoding == "base64") {
73+
content = atob(content);
74+
}
75+
76+
//#EXT-X-KEY:METHOD=AES-128,URI="https://site.com/key_handle.php?key=video_1"
77+
var lines = content.split('\n');
78+
for(var i = 0;i < lines.length;i++){
79+
console.log("analysis:", lines[i]);
80+
if( lines[i].startsWith("#EXT-X-VERSION:")) {
81+
// probably it is the secondary
82+
ui.setSecondaryUrl(currentURL);
83+
}
84+
if(lines[i].startsWith("#EXT-X-KEY:METHOD=")) {
85+
// Key URL extraction
86+
var matches = lines[i].match(/URI=\"([^"]+)\"/);
87+
if(matches && matches.length == 2) {
88+
ui.setSecondaryUrl(currentURL);
89+
console.log("key url found:", matches[1]);
90+
keyURL = matches[1];
91+
92+
// IV extraction
93+
matches = lines[i].match(/IV=([^,\s]+)/);
94+
if(matches && matches.length == 2) {
95+
console.log("IV found:", matches[1]);
96+
ui.setIV(matches[1]);
97+
}
98+
}
99+
break;
100+
} else if(lines[i].startsWith("#EXT-X-STREAM-INF:")) {
101+
// is the primary URL
102+
ui.setPrimaryUrl(currentURL);
103+
break;
104+
}
105+
}
106+
}
107+
);
108+
} else if(data.request.url == keyURL) {
109+
data.getContent(
110+
function(content, encoding){
111+
if(encoding != "base64") {
112+
content = btoa(content);
113+
}
114+
// key found
115+
ui.setKey(content);
116+
console.log("KEY: ",content);
117+
keyURL = "";
118+
}
119+
);
120+
}
121+
}
122+
);
123+
124+
function copyToClipboard(text) {
125+
const input = document.createElement('input');
126+
input.style.position = 'fixed';
127+
input.style.opacity = 0;
128+
input.value = text;
129+
document.body.appendChild(input);
130+
input.select();
131+
document.execCommand('Copy');
132+
document.body.removeChild(input);
133+
};
134+
135+
document.addEventListener('DOMContentLoaded', function() {
136+
document.getElementById('copyBtn').addEventListener('click', function() {
137+
var txt = document.getElementById('command').value;
138+
console.log("copying to clipboard", txt);
139+
copyToClipboard(txt);
140+
});
141+
});

extension/images/icon.png

2.2 KB
Loading

extension/images/icon144.png

3.55 KB
Loading

extension/images/icon36.png

871 Bytes
Loading

0 commit comments

Comments
 (0)