-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path2_recordings_info_scraper.js
177 lines (154 loc) · 7.03 KB
/
2_recordings_info_scraper.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
// ==UserScript==
// @name Zoom LTI Recordings Scraper
// @version 0.1
// @description Get all links and passwords for the recordings in an LTI Zoom frame.
// @author MuaiyadH
// @match *://*.zoom.us/*
// @grant none
// ==/UserScript==
// Used to convert seconds to HH:MM:SS format
function secondsToTime(seconds){
return new Date(seconds * 1000).toISOString().substr(11, 8);
}
// Base function to send requests
function fetchData(url, xsrf, referrer){
return fetch(url, {
"headers": {
"accept": "application/json, text/plain, */*",
"accept-language": "en-GB,en;q=0.9,ar-SY;q=0.8,ar;q=0.7,en-US;q=0.6,ru;q=0.5,tr;q=0.4",
"content-type": "application/json;charset=UTF-8",
"sec-ch-ua": "\" Not;A Brand\";v=\"99\", \"Google Chrome\";v=\"97\", \"Chromium\";v=\"97\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-xsrf-token": xsrf
},
"referrer": referrer,
"referrerPolicy": "same-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
});
}
// Returns a promise containing the list of recordings
function getRecordingsList(page, scid, xsrf){
const today = new Date();
let end_date_string = today.getFullYear() + "-" + (today.getMonth()+1).toString().padStart(2,'0') + "-" + today.getDate().toString().padStart(2,'0');
let recordings_url = "https://applications.zoom.us/api/v1/lti/rich/recording/COURSE?startTime=&endTime=" + end_date_string + "&keyWord=&searchType=1&status=&page=" + page.toString() + "&total=0<i_scid=" + scid;
return fetchData(recordings_url, xsrf, "https://applications.zoom.us/lti/rich")
.then(response => response.json())
.then(data => {
if (data.status) {
return data.result.list;
} else {
return null;
}
});
}
// Returns a promise containing a certain recording's info
function getFileInfo(meeting, scid, xsrf){
let meetingId = encodeURIComponent(meeting.meetingId);
let file_url = "https://applications.zoom.us/api/v1/lti/rich/recording/file?meetingId=" + meetingId + "<i_scid=" + scid;
return fetchData(file_url, xsrf, "https://applications.zoom.us/lti/rich/home/recording/detail").then(response => response.json())
.then(data => {
if (data.status) {
let recording_files = data.result.recordingFiles;
for(let file of recording_files){
if (file.fileType != "M4A" || file.fileType == "MP4") { // If it's not a sound-only recording, or if it's an MP4 file type (a bit redundant, but eh)
let password_promise = getPassword(meetingId, scid, xsrf);
let start = file.recordingStart;
let end = file.recordingEnd;
let duration = secondsToTime(Math.floor((new Date(end) - new Date(start))/1000));
//return password_promise.then(password => { return ({"password":password,"recording_url":file.playUrl, "topic":meeting.topic, "duration":duration, "recordingStart": file.recordingStart, "file_size":file.fileSizeTransform}) });
return password_promise.then(password => { return [password,file.playUrl,meeting.topic,duration,file.recordingStart,file.fileSizeTransform] });
}
}
}else{
console.log("RECORDING COULD NOT BE FOUND", data, meeting);
}
});
}
// Returns a promise containing the password for a recording
function getPassword(meetingId, scid, xsrf){
let password_url = "https://applications.zoom.us/api/v1/lti/rich/recording/pwd?meetingId=" + meetingId + "<i_scid=" + scid;
let password_promise = fetchData(password_url, xsrf, "https://applications.zoom.us/lti/rich/home/recording/detail")
.then(response => response.json())
.then(data => {
if (data.status)
return data.result.password;
else
return null;
});
return password_promise;
}
// Functions to download as CSV file
// Source: https://stackoverflow.com/a/68146412/18429369
/** Download contents as a file
* Source: https://stackoverflow.com/questions/14964035/how-to-export-javascript-array-info-to-csv-on-client-side
*/
function downloadBlob(button_elem, content, filename, contentType) {
// Create a blob
var blob = new Blob([content], { type: contentType });
var url = URL.createObjectURL(blob);
button_elem.href = url;
button_elem.setAttribute('download', filename);
}
/** Convert a 2D array into a CSV string
*/
function arrayToCsv(data){
return data.map(row =>
row
.map(String) // convert every value to String
.map(v => v.replaceAll('"', '""')) // escape double colons
.map(v => `"${v}"`) // quote it
.join(',') // comma-separated
).join('\r\n'); // rows starting on new lines
}
// Used to add a Download button when finished gathering all the recordings data
function addDownloadButton(rows){
let header_elem = document.getElementsByClassName('zm-comp-header')[0];
let download_btn_elem = document.createElement('a');
download_btn_elem.textContent = "Download Recordings Info";
download_btn_elem.classList = ['ant-btn ant-btn-primary'];
download_btn_elem.style['margin-top'] = '10px'
header_elem.appendChild(download_btn_elem);
let data = arrayToCsv(rows);
downloadBlob(download_btn_elem, data, 'recordings_info.csv', 'text/csv;charset=utf-8;');
}
// Check if it's loaded as an iframe, and includes the correct URL
if (!(window.top === window.self) && window.self.location.href.includes("applications.zoom.us/lti/rich")) {
let xsrf_token = window.appConf.ajaxHeaders.find(function(e) {return "X-XSRF-TOKEN" == e.key}).value;
let scid = window.appConf.page.scid;
let rows = [["Password","Recording URL","Topic","Duration","Start datetime","Size"]];
// Getting all recordings from a page in a separate function to recursively get all recordings until no recordings are left
function getPage(page){
let meetings_promise = getRecordingsList(page, scid, xsrf_token);
meetings_promise.then(function(meetings_list) {
console.log(meetings_list);
let promises = [];
for(let meeting of meetings_list){
let file_info_promise = getFileInfo(meeting, scid, xsrf_token);
promises.push(file_info_promise);
file_info_promise.then(data => {
if (data) {
rows.push(data);
console.log(data);
}
});
}
Promise.all(promises).then(function(){
if (meetings_list.length == 0) {
addDownloadButton(rows);
return;
} else {
getPage(page + 1);
}
});
});
}
// Start the gathering process
getPage(1);
}