Skip to content

Commit

Permalink
Merge pull request #517 from jrchamp/fix/only-newest-occurrence-recor…
Browse files Browse the repository at this point in the history
…dings

get meeting recordings: use complete recording data
  • Loading branch information
jrchamp authored Sep 28, 2023
2 parents 6c7eb69 + 25871a2 commit 910bcd8
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 68 deletions.
12 changes: 5 additions & 7 deletions classes/task/delete_meeting_recordings.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,11 @@ public function execute() {
foreach ($zoomrecordings as $meetinguuid => $recordings) {
// Now check which recordings still exist on Zoom.
$recordinglist = $service->get_recording_url_list($meetinguuid);
foreach ($recordinglist as $recordingpair) {
foreach ($recordingpair as $recordinginfo) {
$zoomrecordingid = trim($recordinginfo->recordingid);
if (isset($recordings[$zoomrecordingid])) {
mtrace('Recording id: ' . $zoomrecordingid . ' exist(s)...skipping');
unset($recordings[$zoomrecordingid]);
}
foreach ($recordinglist as $recordinginfo) {
$zoomrecordingid = trim($recordinginfo->recordingid);
if (isset($recordings[$zoomrecordingid])) {
mtrace('Recording id: ' . $zoomrecordingid . ' exist(s)...skipping');
unset($recordings[$zoomrecordingid]);
}
}

Expand Down
99 changes: 65 additions & 34 deletions classes/task/get_meeting_recordings.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,44 +77,75 @@ public function execute() {
'video' => get_string('recordingtypevideo', 'mod_zoom'),
];

$zoommeetings = zoom_get_all_meeting_records();
foreach ($zoommeetings as $zoom) {
$localmeetings = zoom_get_all_meeting_records();

$now = time();
$from = gmdate('Y-m-d', strtotime('-1 day', $now));
$to = gmdate('Y-m-d', strtotime('+1 day', $now));

$hostmeetings = [];

foreach ($localmeetings as $zoom) {
// Only get recordings for this meeting if its recurring or already finished.
$now = time();
if ($zoom->recurring || $now > (intval($zoom->start_time) + intval($zoom->duration))) {
// Get all existing recordings for this meeting.
$recordings = zoom_get_meeting_recordings($zoom->id);
// Fetch all recordings for this meeting.
$zoomrecordingpairlist = $service->get_recording_url_list($zoom->meeting_id);
if (!empty($zoomrecordingpairlist)) {
foreach ($zoomrecordingpairlist as $recordingstarttime => $zoomrecordingpair) {
// The video recording and audio only recordings are grouped together by their recording start timestamp.
foreach ($zoomrecordingpair as $zoomrecordinginfo) {
if (isset($recordings[trim($zoomrecordinginfo->recordingid)])) {
mtrace('Recording id: ' . $zoomrecordinginfo->recordingid . ' exist(s)...skipping');
continue;
}

$recordingtypestring = $recordingtypestrings[$zoomrecordinginfo->recordingtype];

$rec = new \stdClass();
$rec->zoomid = $zoom->id;
$rec->meetinguuid = trim($zoomrecordinginfo->meetinguuid);
$rec->zoomrecordingid = trim($zoomrecordinginfo->recordingid);
$rec->name = trim($zoom->name) . ' (' . $recordingtypestring . ')';
$rec->externalurl = $zoomrecordinginfo->url;
$rec->passcode = trim($zoomrecordinginfo->passcode);
$rec->recordingtype = $zoomrecordinginfo->recordingtype;
$rec->recordingstart = $recordingstarttime;
$rec->showrecording = $zoom->recordings_visible_default;
$rec->timecreated = $now;
$rec->timemodified = $now;
$rec->id = $DB->insert_record('zoom_meeting_recordings', $rec);
mtrace('Recording id: ' . $zoomrecordinginfo->recordingid . ' (' . $zoomrecordinginfo->recordingtype .
') added to the database');
}
$hostmeetings[$zoom->host_id][$zoom->meeting_id] = $zoom;
}
}

if (empty($hostmeetings)) {
mtrace('No meetings need to be processed.');
return;
}

$meetingpasscodes = [];
$localrecordings = zoom_get_meeting_recordings_grouped();

foreach ($hostmeetings as $hostid => $meetings) {
// Fetch all recordings for this user.
$zoomrecordings = $service->get_user_recordings($hostid, $from, $to);

foreach ($zoomrecordings as $recordingid => $recording) {
if (isset($localrecordings[$recording->meetinguuid][$recordingid])) {
mtrace('Recording id: ' . $recordingid . ' exists...skipping');
continue;
}

if (empty($meetings[$recording->meetingid])) {
// Skip meetings that are not in Moodle.
mtrace('Meeting id: ' . $recording->meetingid . ' does not exist...skipping');
continue;
}

// As of 2023-09-24, 'password' is not present in the user recordings API response.
if (empty($meetingpasscodes[$recording->meetinguuid])) {
try {
$settings = $service->get_recording_settings($recording->meetinguuid);
$meetingpasscodes[$recording->meetinguuid] = $settings->password;
} catch (moodle_exception $error) {
continue;
}
}

$zoom = $meetings[$recording->meetingid];

$recordingtype = $recording->recordingtype;
$recordingtypestring = $recordingtypestrings[$recordingtype];

$record = new \stdClass();
$record->zoomid = $zoom->id;
$record->meetinguuid = $recording->meetinguuid;
$record->zoomrecordingid = $recordingid;
$record->name = trim($zoom->name) . ' (' . $recordingtypestring . ')';
$record->externalurl = $recording->url;
$record->passcode = $meetingpasscodes[$recording->meetinguuid];
$record->recordingtype = $recordingtype;
$record->recordingstart = $recording->recordingstart;
$record->showrecording = $zoom->recordings_visible_default;
$record->timecreated = $now;
$record->timemodified = $now;

$record->id = $DB->insert_record('zoom_meeting_recordings', $record);
mtrace('Recording id: ' . $recordingid . ' (' . $recordingtype . ') added to the database');
}
}
}
Expand Down
115 changes: 88 additions & 27 deletions classes/webservice.php
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ private function make_call($path, $data = [], $method = 'get') {
* Makes a call like make_call() but specifically for GETs with paginated results.
*
* @param string $url The URL to append to the API URL
* @param array|string $data The data to attach to the call.
* @param array $data The data to attach to the call.
* @param string $datatoget The name of the array of the data to get.
* @return array The retrieved data.
* @see make_call()
Expand Down Expand Up @@ -411,7 +411,7 @@ public function autocreate_user($user) {
*/
public function list_users() {
if (empty(self::$userslist)) {
self::$userslist = $this->make_paginated_call('users', null, 'users');
self::$userslist = $this->make_paginated_call('users', [], 'users');
}

return self::$userslist;
Expand Down Expand Up @@ -823,15 +823,15 @@ public function get_meeting_invitation($zoom) {
/**
* Retrieve ended meetings report for a specified user and period. Handles multiple pages.
*
* @param int $userid Id of user of interest
* @param string $userid Id of user of interest
* @param string $from Start date of period in the form YYYY-MM-DD
* @param string $to End date of period in the form YYYY-MM-DD
* @return array The retrieved meetings.
* @link https://zoom.github.io/api/#retrieve-meetings-report
*/
public function get_user_report($userid, $from, $to) {
$url = 'report/users/' . $userid . '/meetings';
$data = ['from' => $from, 'to' => $to, 'page_size' => ZOOM_MAX_RECORDS_PER_CALL];
$data = ['from' => $from, 'to' => $to];
return $this->make_paginated_call($url, $data, 'meetings');
}

Expand All @@ -846,7 +846,7 @@ public function get_user_report($userid, $from, $to) {
*/
public function list_meetings($userid, $webinar) {
$url = 'users/' . $userid . ($webinar ? '/webinars' : '/meetings');
$instances = $this->make_paginated_call($url, null, ($webinar ? 'webinars' : 'meetings'));
$instances = $this->make_paginated_call($url, [], ($webinar ? 'webinars' : 'meetings'));
return $instances;
}

Expand Down Expand Up @@ -980,37 +980,38 @@ public function encode_uuid($uuid) {
* of the meeting and then starts recording again without ending the meeting.
*
* @link https://marketplace.zoom.us/docs/api-reference/zoom-api/cloud-recording/recordingget
* @param string $meetingid The string meeting ID.
* @param string $meetingid The string meeting UUID.
* @return array Returns the list of recording URLs and the type of recording that is being sent back.
*/
public function get_recording_url_list($meetingid) {
$meetingid = $this->encode_uuid($meetingid);
$url = 'meetings/' . $meetingid . '/recordings';
$settingsurl = 'meetings/' . $meetingid . '/recordings/settings';
$allowedrecordingtypes = ['MP4', 'M4A'];
$recordings = [];

// Only pick the video recording and audio only recordings.
// The transcript is available in both of these, so the extra file is unnecessary.
$allowedrecordingtypes = [
'MP4' => 'video',
'M4A' => 'audio',
];

try {
$url = 'meetings/' . $this->encode_uuid($meetingid) . '/recordings';
$response = $this->make_call($url);
if (!empty($response->recording_files)) {
$settingsresponse = $this->make_call($settingsurl);
foreach ($response->recording_files as $rec) {
if (!empty($rec->play_url) && in_array($rec->file_type, $allowedrecordingtypes, true)) {
$type = (!empty($rec->recording_type) && $rec->recording_type === 'audio_only') ? 'audio' : 'video';

// Only pick the video recording and audio only recordings.
// The transcript is available in both of these, so the extra file is unnecessary.
if (!empty($response->recording_files)) {
foreach ($response->recording_files as $recording) {
if (!empty($recording->play_url) && isset($allowedrecordingtypes[$recording->file_type])) {
$recordinginfo = new stdClass();
$recordinginfo->recordingid = $rec->id;
$recordinginfo->meetinguuid = $rec->meeting_id;
$recordinginfo->url = $rec->play_url;
$recordinginfo->filetype = $rec->file_type;
$recordinginfo->recordingtype = $recordingtype;
$recordinginfo->passcode = $settingsresponse->password;
$recordings[strtotime($rec->recording_start)][] = $recordinginfo;
$recordinginfo->recordingid = $recording->id;
$recordinginfo->meetinguuid = $response->uuid;
$recordinginfo->url = $recording->play_url;
$recordinginfo->filetype = $recording->file_type;
$recordinginfo->recordingtype = $allowedrecordingtypes[$recording->file_type];
$recordinginfo->passcode = $response->password;
$recordinginfo->recordingstart = strtotime($recording->recording_start);

$recordings[$recording->id] = $recordinginfo;
}
}

ksort($recordings);
}
} catch (moodle_exception $error) {
// No recordings found for this meeting id.
Expand All @@ -1020,6 +1021,54 @@ public function get_recording_url_list($meetingid) {
return $recordings;
}

/**
* Retrieve recordings for a specified user and period. Handles multiple pages.
*
* @param string $userid User ID.
* @param string $from Start date of period in the form YYYY-MM-DD
* @param string $to End date of period in the form YYYY-MM-DD
* @return array
* @link https://developers.zoom.us/docs/api/rest/reference/zoom-api/methods/#operation/recordingsList
*/
public function get_user_recordings($userid, $from, $to) {
$recordings = [];

// Only pick the video recording and audio only recordings.
// The transcript is available in both of these, so the extra file is unnecessary.
$allowedrecordingtypes = [
'MP4' => 'video',
'M4A' => 'audio',
];

try {
$url = 'users/' . $userid . '/recordings';
$data = ['from' => $from, 'to' => $to];
$response = $this->make_paginated_call($url, $data, 'meetings');

foreach ($response as $meeting) {
foreach ($meeting->recording_files as $recording) {
if (!empty($recording->play_url) && isset($allowedrecordingtypes[$recording->file_type])) {
$recordinginfo = new stdClass();
$recordinginfo->recordingid = $recording->id;
$recordinginfo->meetingid = $meeting->id;
$recordinginfo->meetinguuid = $meeting->uuid;
$recordinginfo->url = $recording->play_url;
$recordinginfo->filetype = $recording->file_type;
$recordinginfo->recordingtype = $allowedrecordingtypes[$recording->file_type];
$recordinginfo->recordingstart = strtotime($recording->recording_start);

$recordings[$recording->id] = $recordinginfo;
}
}
}
} catch (moodle_exception $error) {
// No recordings found for this user.
$recordings = [];
}

return $recordings;
}

/**
* Returns a server to server oauth access token, good for 1 hour.
*
Expand Down Expand Up @@ -1123,7 +1172,7 @@ private function oauth($cache) {
/**
* List the meeting or webinar registrants from Zoom.
*
* @param int $id The meeting_id or webinar_id of the meeting or webinar to retrieve.
* @param string $id The meeting_id or webinar_id of the meeting or webinar to retrieve.
* @param bool $webinar Whether the meeting or webinar whose information you want is a webinar.
* @return stdClass The meeting's or webinar's information.
*/
Expand All @@ -1132,4 +1181,16 @@ public function get_meeting_registrants($id, $webinar) {
$response = $this->make_call($url);
return $response;
}

/**
* Get the recording settings for a meeting.
*
* @param string $meetinguuid The UUID of a meeting with recordings.
* @return stdClass The meeting's recording settings.
*/
public function get_recording_settings($meetinguuid) {
$url = 'meetings/' . $this->encode_uuid($meetinguuid) . '/recordings/settings';
$response = $this->make_call($url);
return $response;
}
}

0 comments on commit 910bcd8

Please sign in to comment.