-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbackground.js
337 lines (312 loc) · 10.6 KB
/
background.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
let blobs = {}
let requests = {}
let title = {}
let itag_codes = {
5: 'flv audio/video 240p',
6: 'flv audio/video 270p',
17: '3gp audio/video 144p',
18: 'mp4 audio/video 360p',
22: 'mp4 audio/video 720p',
34: 'flv audio/video 360p',
35: 'flv audio/video 480p',
36: '3gp audio/video 180p',
37: 'mp4 audio/video 1080p',
38: 'mp4 audio/video 3072p',
43: 'webm audio/video 360p',
44: 'webm audio/video 480p',
45: 'webm audio/video 720p',
46: 'webm audio/video 1080p',
82: 'mp4 audio/video 360p 3D',
83: 'mp4 audio/video 480p 3D',
84: 'mp4 audio/video 720p 3D',
85: 'mp4 audio/video 1080p 3D',
92: 'hls audio/video 240p 3D',
93: 'hls audio/video 360p 3D',
94: 'hls audio/video 480p 3D',
95: 'hls audio/video 720p 3D',
96: 'hls audio/video 1080p',
100: 'webm audio/video 360p 3D',
101: 'webm audio/video 480p 3D',
102: 'webm audio/video 720p 3D',
132: 'hls audio/video 240p',
133: 'mp4 video 240p',
134: 'mp4 video 360p',
135: 'mp4 video 480p',
136: 'mp4 video 720p',
137: 'mp4 video 1080p',
138: 'mp4 video 2160p60',
139: 'm4a audio 48k',
140: 'm4a audio 128k',
141: 'm4a audio 256k',
151: 'hls audio/video 72p',
160: 'mp4 video 144p',
167: 'webm video 360p',
168: 'webm video 480p',
169: 'webm video 1080p',
171: 'webm audio 128k',
218: 'webm video 480p',
219: 'webm video 144p',
242: 'webm video 240p',
243: 'webm video 360p',
244: 'webm video 480p',
245: 'webm video 480p',
246: 'webm video 480p',
247: 'webm video 720p',
248: 'webm video 1080p',
249: 'webm audio 50k',
250: 'webm audio 70k',
251: 'webm audio 160k',
264: 'mp4 video 1440p',
266: 'mp4 video 2160p60',
271: 'webm video 1440p',
272: 'webm video 4320p',
278: 'webm video 144p',
298: 'mp4 video 720p60',
299: 'mp4 video 1080p60',
302: 'webm video 720p60',
303: 'webm video 1080p60',
308: 'webm video 1440p60',
313: 'webm video 2160p',
315: 'webm video 2160p60',
330: 'webm video 144p60 hdr',
331: 'webm video 240p60 hdr',
332: 'webm video 360p60 hdr',
333: 'webm video 480p60 hdr',
334: 'webm video 720p60 hdr',
335: 'webm video 1080p60 hdr',
336: 'webm video 1440p60 hdr',
337: 'webm video 2160p60 hdr',
}
function get_itag(request_url){
let itag_regex = /\&itag=[^&]+\&/
let itag = request_url.match(itag_regex).pop()
itag = itag.replace('&itag=','').replace('&','')
return itag_codes[itag]
}
function get_signature(request_url,video_url){
let itag_regex = /\&itag=[^&]+\&/
let itag = request_url.match(itag_regex).pop()
itag = itag.replace('&itag=','').replace('&','')
return video_url+itag
}
function intercept_requests(tab_id){
chrome.tabs.get(tab_id)
.then(tab => {
chrome.webRequest.onCompleted.addListener(request => {
chrome.tabs.get(tab_id)
.then(current_tab => {
if(!current_tab) return
let video_url = current_tab.url
let old_title = title[tab_id]
let title_changed = old_title != current_tab.title
let title_exists = title.hasOwnProperty(tab_id)
let current_title = title_exists ?
title_changed ? current_tab.title : old_title :
current_tab.title
valid_request(request) ?
process_request(request.url,video_url,current_title) :
null
})
},{urls: ['<all_urls>'], tabId: tab_id})
})
}
function valid_request(request){
let regex = /googlevideo\.com\/videoplayback\?/
return request.url.match(regex)
}
function process_request(request_url,video_url,video_title){
load_data()
.then(data => {
let file_signature = get_signature(request_url,video_url)
if(!requests.hasOwnProperty(file_signature)){
if(!request_size(request_url)) return
requests[file_signature] = {
title: video_title,
mime: get_mime_description(request_url),
requests: generate_requests(request_url)
}
save_file(request_url,video_url)
}else{
update_title(request_url,video_url,video_title)
}
})
}
function load_data(){
return chrome.storage.local.get(['requests'])
.then(data => requests = data.requests ? data.requests : {})
}
function generate_requests(request_url){
let size = request_size(request_url)
let request_quantity = size > 100000000 ? 1000 : 100
let current_byte = -1
let bytes_per_request = Math.floor(size/request_quantity)
let request_list = Array(request_quantity).fill('')
let list = request_list.map((item,index) => {
let range_regex = /\&range=[0-9]+-[0-9]+\&/
let start = current_byte+1
let end = start + bytes_per_request
if(index == request_quantity-1) end = size-1
current_byte = end
return request_url
.replace(range_regex,'&range='+start+'-'+end+'&')
})
return list
}
function request_size(request_url){
let clen_regex = /\&clen=[0-9]+\&/
let size = null
let has_size = request_url.match(clen_regex)
if(!has_size) return size
let clen = request_url
.match(clen_regex)
.shift()
.replace('&clen=','')
.replace('&','')
size = parseInt(clen)
return size
}
function range_start(request_url){
let range_regex = /\&range=[0-9]+-[0-9]+/
let start = null
let has_range = request_url.match(range_regex)
if(!has_range) return start
start = request_url
.match(range_regex)
.shift()
.replace('&range=','')
.split('-')
.shift()
start = parseInt(start)
return start
}
function range_end(request_url){
let range_regex = /\&range=[0-9]+-[0-9]+/
let end = null
let has_range = request_url.match(range_regex)
if(!has_range) return end
end = request_url
.match(range_regex)
.shift()
.replace('&range=','')
.split('-')
.pop()
end = parseInt(end)
return end
}
function update_title(request_url,video_url,video_title){
let file_signature = get_signature(request_url,video_url)
chrome.storage.local.get(['files'])
.then(data => {
if(data.files && data.files.hasOwnProperty(file_signature)){
data.files[file_signature].title = video_title
chrome.storage.local.set({files: data.files})
}
})
}
function save_file(request_url,video_url){
let file_signature = get_signature(request_url,video_url)
chrome.storage.local.get(['files'],data => {
let file_structure = {
request_url: request_url,
video_url: video_url,
title: requests[file_signature].title,
mime: requests[file_signature].mime,
in_progress: false,
progress: 0,
file: null
}
if(!data.files) data.files = {}
data.files[file_signature] = JSON.parse(JSON.stringify(file_structure))
chrome.storage.local.set({files: data.files,blobs: blobs, requests: requests})
})
}
function get_mime_description(request_url){
let itag = get_itag(request_url)
if(itag) return itag
return get_mime(request_url)
}
function get_mime(request_url){
let mime_regex = /\&mime=[^&]+\&/
let mime = request_url.match(mime_regex).pop()
mime = mime.replace('&mime=','').replace('&','').replace('%2F','/')
return mime
}
function download_file(request_url,video_url){
let file_signature = get_signature(request_url,video_url)
let request_list = requests[file_signature].requests
let mime = get_mime(request_url)
let file_name = video_url+'.'+mime.split('/').pop()
let promise = Promise.all(request_list.map((request,index) => {
return fetch(request,{method: 'POST'})
.then(data => data.blob())
.then(data => data.slice(54,data.size,data.type))
.then(data => ({content: data, id: index}))
}))
promise.then(data => {
blobs[file_signature] = {
data: data,
name: file_name,
mime: mime,
id: data.length-1
}
chrome.storage.local.get(['files'])
.then(data => {
let file_name = blobs[file_signature].name
let file_mime = blobs[file_signature].mime
let sorted_data = blobs[file_signature]
.data
.sort((a,b) => a.id - b.id)
.map(data => data.content)
let file = new File(sorted_data,file_name,{type: file_mime})
let reader = new FileReader()
reader.onload = () => {
data.files[file_signature].file = reader.result
chrome.storage.local.set({files: data.files})
.then(() => chrome.action.getBadgeText({}))
.then(text => text ? (parseInt(text)+1).toString() : '1')
.then(new_text => chrome.action.setBadgeText({text: new_text}))
}
reader.readAsDataURL(file)
})
})
}
function erase_data(){
chrome.action.setBadgeText({text: '0'})
requests = {}
blobs = {}
}
function erase_file(request_url,video_url){
chrome.action.getBadgeText({})
.then(text => parseInt(text))
.then(text => text ? (text-1).toString() : '0')
.then(new_text => chrome.action.setBadgeText({text: new_text}))
let file_signature = get_signature(request_url,video_url)
delete requests[file_signature]
delete blobs[file_signature]
}
chrome.runtime.onConnect.addListener(conn => {
conn.onMessage.addListener((msg,port) => {
switch(msg.msg){
case 'youtube_tab':
let tab_id = port.sender.tab.id
intercept_requests(tab_id)
break;
case 'download_file':
let file_signature = get_signature(msg.request_url,msg.video_url)
if(blobs[file_signature]) delete blobs[file_signature]
download_file(msg.request_url,msg.video_url)
break;
case 'erase_data':
erase_data()
break;
case 'erase_file':
erase_file(msg.request_url,msg.video_url)
break;
default:
break;
}
})
})
chrome.storage.local.get(['requests'])
.then(data => data.requests ? data.requests : {})
.then(data => requests = data)