347 lines
13 KiB
JavaScript
347 lines
13 KiB
JavaScript
//■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
|
||
//
|
||
// ZOOM APIを使って録画データをダウンロードし、オリジナルデータは削除するスクリプト
|
||
// 2024-08-25 作成
|
||
//
|
||
// 登録されているユーザーのメールアドレスのフォルダを作り、そのなかにTopic名のフォルダを作成して、
|
||
// そこに個別にDLされる。ファイル名は録画開始日時とTOPIC名。
|
||
//
|
||
//■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
|
||
//Client ID : 3d605ODWTiKbJRrifQjerA
|
||
//Client Secret : HjxpKnITWdWQYgAajW8iWgq3YAZ61aLg
|
||
//Account ID : tpo4aQgXRdedqBGApzPTcA
|
||
//Verification Toke : CrWw9OrNQYm1QQ8telPNpw
|
||
const moment = require('moment-timezone');
|
||
const axios = require('axios');
|
||
const fs = require('fs');
|
||
const path = require('path');
|
||
|
||
const clientId = '3d605ODWTiKbJRrifQjerA'
|
||
const clientSecret = 'HjxpKnITWdWQYgAajW8iWgq3YAZ61aLg';
|
||
const accountId = 'tpo4aQgXRdedqBGApzPTcA';
|
||
|
||
//■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
|
||
//実行する基準の月と月数を指定
|
||
//const baseYear = 2024;
|
||
//const baseMonth = 8
|
||
//const baseDate = moment(baseYear + '-' + baseMonth.toString().padStart(2, '0') + '-01T00:00:00Z').utc();
|
||
const monthLength = 1;
|
||
|
||
//■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
|
||
//Zoom APIのエンドポイント
|
||
const baseUrl = 'https://api.zoom.us/v2';
|
||
const baseDirectory = 'ZoomRecordings';
|
||
|
||
let accessToken;
|
||
let saveDirectory;
|
||
let fromDate;
|
||
let toDate;
|
||
|
||
//アクセストークンの取得
|
||
const getAccessToken = async () => {
|
||
try {
|
||
const auth = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
|
||
const response = await axios.post('https://zoom.us/oauth/token', null, {
|
||
headers: {
|
||
'Authorization': `Basic ${auth}`,
|
||
},
|
||
params: {
|
||
grant_type: 'account_credentials',
|
||
account_id: accountId
|
||
}
|
||
});
|
||
|
||
return response.data.access_token;
|
||
} catch (error) {
|
||
console.error('エラー アクセストークンの取得に失敗しました : ', error);
|
||
}
|
||
};
|
||
|
||
//ユーザー一覧取得
|
||
async function getUserList() {
|
||
const response = await axios.get(baseUrl + '/users', {
|
||
headers: {
|
||
'Authorization': `Bearer ${accessToken}`
|
||
},
|
||
params: {
|
||
//取得したいパラメータを指定 (e.g., type, status)
|
||
}
|
||
});
|
||
return response.data;
|
||
}
|
||
|
||
//ミーティングの一覧を取得
|
||
//UserIdを指定する
|
||
async function getMeetingList(userId) {
|
||
const response = await axios.get(baseUrl + '/users/' + userId + '/meetings', {
|
||
headers: {
|
||
'Authorization': `Bearer ${accessToken}`
|
||
},
|
||
params: {
|
||
//取得したいパラメータを指定 (e.g., type, status)
|
||
}
|
||
});
|
||
return response.data;
|
||
}
|
||
|
||
//ファイルリストの取得
|
||
async function getRecordingFileList(uuid) {
|
||
//ダウンロードリクエストを送信
|
||
const response = await axios.get(baseUrl + '/meetings/' + uuid + '/recordings', {
|
||
headers: {
|
||
'Authorization': `Bearer ${accessToken}`
|
||
},
|
||
params: {
|
||
//取得したいパラメータを指定 (e.g., type, status)
|
||
}
|
||
});
|
||
return response.data;
|
||
}
|
||
|
||
//録画リストの一覧を取得
|
||
//UserIdを指定する
|
||
async function getRecordingList(userId) {
|
||
//ダウンロードリクエストを送信
|
||
console.log(`▶▶▶ ${fromDate} ${toDate} の録画リストを取得中...`);
|
||
const response = await axios.get(baseUrl + '/users/' + userId + '/recordings', {
|
||
headers: {
|
||
'Authorization': `Bearer ${accessToken}`
|
||
},
|
||
params: {
|
||
//期間を指定
|
||
from: fromDate,
|
||
to: toDate
|
||
//取得したいパラメータを指定 (e.g., type, status)
|
||
}
|
||
});
|
||
return response.data;
|
||
}
|
||
|
||
//録画データをダウンロード
|
||
async function downloadFile(file, downloadUrl, filePath) {
|
||
console.log(`▶▶▶ ${downloadUrl} ダウンロード開始`);
|
||
try {
|
||
const response = await axios.get(downloadUrl, { responseType: 'stream' });
|
||
return new Promise((resolve, reject) => {
|
||
response.data.pipe(fs.createWriteStream(filePath))
|
||
.on('finish', async () => {
|
||
//ダウンロードできたら、ファイルを削除
|
||
await deleteRecording(file.meeting_id, file.id);
|
||
resolve();
|
||
})
|
||
.on('error', reject);
|
||
});
|
||
} catch (error) {
|
||
console.error(`Error downloading ${filePath}:`, error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
//録画データを削除
|
||
async function deleteRecording(meetingUuid, recordingId) {
|
||
//ヘッダーの設定
|
||
const headers = {
|
||
'Authorization': `Bearer ${accessToken}`
|
||
};
|
||
|
||
//二重エンコードする
|
||
const encodedMeetingUuid = doubleEncodeUUID(meetingUuid);
|
||
|
||
//削除リクエストのURLを作成
|
||
const deleteUrl = `https://api.zoom.us/v2/meetings/${encodedMeetingUuid}/recordings/${recordingId}`;
|
||
|
||
try {
|
||
await axios.delete(deleteUrl, { headers });
|
||
console.log(`▶▶▶ 録画データ ${recordingId} を削除しました`);
|
||
} catch (error) {
|
||
console.error('Error deleting recording:', error);
|
||
}
|
||
}
|
||
|
||
async function recordingFileDownload(recordingList) {
|
||
|
||
for (meeting of recordingList.meetings) {
|
||
let startTime = meeting.start_time;
|
||
startTime = startTime.replaceAll(':', '.');
|
||
startTime = startTime.replaceAll('Z', '');
|
||
startTime = startTime.replaceAll('T', ' ');
|
||
|
||
let topicName = sanitizeFolderName(meeting.topic);
|
||
let saveFolder = startTime + ' ' + topicName + ' ' + meeting.id;
|
||
//フォルダが存在しない場合に作成
|
||
fs.mkdir(path.join(saveDirectory, saveFolder), { recursive: true }, (err) => {
|
||
if (err) {
|
||
console.error('Error creating directory:', err);
|
||
} else {
|
||
//console.log('Directory created successfully!');
|
||
}
|
||
});
|
||
|
||
console.log('▶▶▶ ' + saveFolder + ' 録画データのダウンロード開始');
|
||
|
||
for (file of meeting.recording_files) {
|
||
let startTime = file.recording_start;
|
||
startTime = startTime.replaceAll(':', '.');
|
||
startTime = startTime.replaceAll('Z', '');
|
||
startTime = startTime.replaceAll('T', ' ');
|
||
|
||
let downloadUrl = file.download_url + '?access_token=' + accessToken;
|
||
let fileName = startTime + ' ' + topicName + ' ' + meeting.id + '.' + file.file_extension;
|
||
let filePath = path.join(saveDirectory, saveFolder + '/' + fileName);
|
||
|
||
//console.log(file);
|
||
console.log("▶▶▶ " + fileName + ' ダウンロード開始');
|
||
await downloadFile(file, downloadUrl, filePath);
|
||
}
|
||
|
||
console.log('▷▷▷ ' + saveFolder + ' 録画データのダウンロード完了');
|
||
|
||
}
|
||
|
||
}
|
||
|
||
//メイン処理
|
||
async function main() {
|
||
|
||
let baseYear;
|
||
let baseMonth;
|
||
|
||
if (process.argv.length == 4) {
|
||
baseYear = process.argv[2];
|
||
baseMonth = process.argv[3];
|
||
if (isNaN(baseYear) || isNaN(baseMonth)) {
|
||
console.log("■■■■■■■■■■■■■■■■■■■■■■■■■■");
|
||
console.log("■ 引数が数字ではありません 実行例↓");
|
||
console.log("■ node .\\実行ファイル名 年 月");
|
||
console.log("■ 年と月は数字のみ(前0不要)");
|
||
console.log("■■■■■■■■■■■■■■■■■■■■■■■■■■");
|
||
return false;
|
||
} else {
|
||
baseYear = parseFloat(baseYear);
|
||
baseMonth = parseFloat(baseMonth);
|
||
if (baseYear <= 2000 || baseYear > 3000) {
|
||
console.log("■■■■■■■■■■■■■■■■■■■■■■■■■■");
|
||
console.log("■ 引数が数字ではありません 実行例↓");
|
||
console.log("■ node .\\実行ファイル名 年 月");
|
||
console.log("■ 年は西暦4桁で指定してください");
|
||
console.log("■■■■■■■■■■■■■■■■■■■■■■■■■■");
|
||
return false;
|
||
}
|
||
|
||
if (baseMonth < 1 || baseMonth > 12) {
|
||
console.log("■■■■■■■■■■■■■■■■■■■■■■■■■■");
|
||
console.log("■ 引数が数字ではありません 実行例↓");
|
||
console.log("■ node .\\実行ファイル名 年 月");
|
||
console.log("■ 月は1~12で指定してください");
|
||
console.log("■■■■■■■■■■■■■■■■■■■■■■■■■■");
|
||
return false;
|
||
}
|
||
}
|
||
} else {
|
||
console.log("■■■■■■■■■■■■■■■■■■■■■■■■■■");
|
||
console.log("■ 引数を正確に指定してください 実行例↓");
|
||
console.log("■ node .\\実行ファイル名 年 月");
|
||
console.log("■ 年と月は数字のみ(前0不要)");
|
||
console.log("■■■■■■■■■■■■■■■■■■■■■■■■■■");
|
||
return false;
|
||
}
|
||
|
||
let baseDate = moment(baseYear + '-' + baseMonth.toString().padStart(2, '0') + '-01T00:00:00Z').utc();
|
||
let fromDate0 = baseDate.clone();
|
||
let toDate0 = baseDate.clone().add(1, 'months').subtract(1, 'days');
|
||
//let toDate0 = baseDate.clone().add(1, 'months');
|
||
|
||
console.log("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
|
||
console.log("★ ZOOM録画データのダウンロード開始");
|
||
console.log("■baseDate : " + baseDate);
|
||
|
||
//console.log("■ 検証のため停止");
|
||
//return false;
|
||
|
||
for (i = 0; i < monthLength; i++) {
|
||
fromDate = fromDate0.format('YYYY-MM-DD');;
|
||
toDate = toDate0.format('YYYY-MM-DD');;
|
||
console.log(fromDate + ' ' + toDate);
|
||
|
||
|
||
try {
|
||
//アクセストークンの取得
|
||
accessToken = await getAccessToken();
|
||
//console.log(accessToken);
|
||
|
||
//ユーザー情報一覧取得
|
||
const userList = await getUserList();
|
||
//console.log(userList);
|
||
|
||
//ユーザーIDを指定してミーティングリストの取得
|
||
//const meetingList = await getMeetingList('qQ0Tr6EHTBiYxOkSSd_CUw');
|
||
//console.log(meetingList);
|
||
|
||
//ミーティングIDを指定してファイルリストの取得
|
||
//const fileList = await getRecordingFileList('tmqmRfofTYSjU2AvjR7j/A==');
|
||
//console.log(fileList);
|
||
|
||
|
||
for (user of userList.users) {
|
||
saveDirectory = baseDirectory;
|
||
saveDirectory = path.join(saveDirectory, user.email);
|
||
let recordingList;
|
||
|
||
//録画データがなくなるまで繰り返す
|
||
do {
|
||
recordingList = await getRecordingList(user.id);
|
||
if (recordingList.meetings.length > 0) {
|
||
console.log("■■■ " + user.id + ' ' + user.display_name + ' ' + user.email);
|
||
console.log("▶▶▶ 録画データ数 :" + recordingList.meetings.length);
|
||
console.log(recordingList.meetings);
|
||
await recordingFileDownload(recordingList);
|
||
}
|
||
} while (recordingList.meetings.length > 0);
|
||
|
||
}
|
||
|
||
|
||
//録画リストの取得
|
||
//UserIDを指定する
|
||
//const recordingList = await getRecordingList('E4UdyiFuRvuvm_I4K-WqLQ');
|
||
//console.log(recordingList);
|
||
|
||
|
||
//取得した録画リストをダウンロードして削除する
|
||
//await recordingFileDownload(recordingList);
|
||
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
|
||
//月を1つ進める
|
||
fromDate0 = fromDate0.clone().add(1, 'months');
|
||
toDate0 = toDate0.clone().add(1, 'months');
|
||
}
|
||
|
||
console.log("★ ZOOM録画データのダウンロード終了");
|
||
console.log("★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
|
||
|
||
}
|
||
|
||
//URLエンコードを2回行う
|
||
function doubleEncodeUUID(uuid) {
|
||
return encodeURIComponent(encodeURIComponent(uuid));
|
||
}
|
||
|
||
//フォルダ名に使えない文字列の排除
|
||
function sanitizeFolderName(str) {
|
||
// フォルダ名に使用できない一般的な文字の正規表現
|
||
const invalidCharsRegex = /[/\\:*?"<>|]/g;
|
||
|
||
// 空白をハイフンに置換
|
||
const sanitizedStr = str.replace(/\s+/g, '-');
|
||
|
||
// 不適切な文字を削除
|
||
return sanitizedStr.replace(invalidCharsRegex, '');
|
||
}
|
||
|
||
|
||
//処理開始
|
||
main();
|