2020-05-16 17:14:17 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
namespace OCA\BigBlueButton\BigBlueButton;
|
|
|
|
|
|
|
|
use BigBlueButton\BigBlueButton;
|
2020-05-17 11:09:16 +02:00
|
|
|
use BigBlueButton\Core\Record;
|
2020-09-23 12:33:09 +02:00
|
|
|
use BigBlueButton\Parameters\CreateMeetingParameters;
|
2020-05-17 11:09:16 +02:00
|
|
|
use BigBlueButton\Parameters\DeleteRecordingsParameters;
|
2020-09-23 12:33:09 +02:00
|
|
|
use BigBlueButton\Parameters\GetRecordingsParameters;
|
2020-05-17 13:39:01 +02:00
|
|
|
use BigBlueButton\Parameters\IsMeetingRunningParameters;
|
2020-09-23 12:33:09 +02:00
|
|
|
use BigBlueButton\Parameters\JoinMeetingParameters;
|
2021-12-01 15:05:07 +01:00
|
|
|
use OCA\BigBlueButton\AppInfo\Application;
|
2020-09-23 12:33:09 +02:00
|
|
|
use OCA\BigBlueButton\Crypto;
|
2020-05-16 17:14:17 +02:00
|
|
|
use OCA\BigBlueButton\Db\Room;
|
2020-09-23 12:33:09 +02:00
|
|
|
use OCA\BigBlueButton\Event\MeetingStartedEvent;
|
2020-09-23 09:13:26 +02:00
|
|
|
use OCA\BigBlueButton\UrlHelper;
|
2021-12-01 15:05:07 +01:00
|
|
|
use OCP\App\IAppManager;
|
2021-04-18 13:14:05 +02:00
|
|
|
use OCP\Defaults;
|
2020-09-23 12:33:09 +02:00
|
|
|
use OCP\EventDispatcher\IEventDispatcher;
|
2020-05-16 17:14:17 +02:00
|
|
|
use OCP\IConfig;
|
2020-09-22 16:08:14 +02:00
|
|
|
use OCP\IL10N;
|
2021-12-01 15:05:07 +01:00
|
|
|
use OCP\IRequest;
|
2020-09-23 12:33:09 +02:00
|
|
|
use OCP\IURLGenerator;
|
2020-05-16 17:14:17 +02:00
|
|
|
|
2020-06-19 09:28:58 +02:00
|
|
|
class API {
|
2020-05-16 17:14:17 +02:00
|
|
|
/** @var IConfig */
|
|
|
|
private $config;
|
|
|
|
|
|
|
|
/** @var IURLGenerator */
|
|
|
|
private $urlGenerator;
|
|
|
|
|
2020-06-19 10:49:40 +02:00
|
|
|
/** @var BigBlueButton|null */
|
2020-05-16 17:14:17 +02:00
|
|
|
private $server;
|
|
|
|
|
2020-09-22 12:19:48 +02:00
|
|
|
/** @var Crypto */
|
|
|
|
private $crypto;
|
|
|
|
|
2020-09-22 13:51:47 +02:00
|
|
|
/** @var IEventDispatcher */
|
|
|
|
private $eventDispatcher;
|
|
|
|
|
2020-09-22 16:08:14 +02:00
|
|
|
/** @var IL10N */
|
|
|
|
private $l10n;
|
|
|
|
|
2020-09-23 09:13:26 +02:00
|
|
|
/** @var UrlHelper */
|
|
|
|
private $urlHelper;
|
|
|
|
|
2021-04-18 13:14:05 +02:00
|
|
|
/** @var Defaults */
|
|
|
|
private $defaults;
|
|
|
|
|
2021-12-01 15:05:07 +01:00
|
|
|
/** @var IAppManager */
|
|
|
|
private $appManager;
|
|
|
|
|
|
|
|
/** @var IRequest */
|
|
|
|
private $request;
|
|
|
|
|
2020-05-16 17:14:17 +02:00
|
|
|
public function __construct(
|
|
|
|
IConfig $config,
|
2020-06-15 17:23:53 +02:00
|
|
|
IURLGenerator $urlGenerator,
|
2020-09-22 13:51:47 +02:00
|
|
|
Crypto $crypto,
|
2020-09-22 16:08:14 +02:00
|
|
|
IEventDispatcher $eventDispatcher,
|
2020-09-23 09:13:26 +02:00
|
|
|
IL10N $l10n,
|
2021-04-18 13:14:05 +02:00
|
|
|
UrlHelper $urlHelper,
|
2021-12-01 15:05:07 +01:00
|
|
|
Defaults $defaults,
|
|
|
|
IAppManager $appManager,
|
|
|
|
IRequest $request
|
2020-05-16 17:14:17 +02:00
|
|
|
) {
|
|
|
|
$this->config = $config;
|
|
|
|
$this->urlGenerator = $urlGenerator;
|
2020-09-22 12:19:48 +02:00
|
|
|
$this->crypto = $crypto;
|
2020-09-22 13:51:47 +02:00
|
|
|
$this->eventDispatcher = $eventDispatcher;
|
2020-09-22 16:08:14 +02:00
|
|
|
$this->l10n = $l10n;
|
2020-09-23 09:13:26 +02:00
|
|
|
$this->urlHelper = $urlHelper;
|
2021-04-18 13:14:05 +02:00
|
|
|
$this->defaults = $defaults;
|
2021-12-01 15:05:07 +01:00
|
|
|
$this->appManager = $appManager;
|
|
|
|
$this->request = $request;
|
2020-05-16 17:14:17 +02:00
|
|
|
}
|
|
|
|
|
2021-02-24 15:23:26 +01:00
|
|
|
private function getServer(): BigBlueButton {
|
2020-05-16 17:14:17 +02:00
|
|
|
if (!$this->server) {
|
|
|
|
$apiUrl = $this->config->getAppValue('bbb', 'api.url');
|
|
|
|
$secret = $this->config->getAppValue('bbb', 'api.secret');
|
|
|
|
|
|
|
|
$this->server = new BigBlueButton($apiUrl, $secret);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->server;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create join url.
|
|
|
|
*
|
|
|
|
* @return string join url
|
|
|
|
*/
|
2021-01-22 19:12:55 +01:00
|
|
|
public function createJoinUrl(Room $room, float $creationTime, string $displayname, bool $isModerator, ?string $uid = null) {
|
|
|
|
$password = $isModerator ? $room->moderatorPassword : $room->attendeePassword;
|
2020-05-16 17:14:17 +02:00
|
|
|
|
|
|
|
$joinMeetingParams = new JoinMeetingParameters($room->uid, $displayname, $password);
|
|
|
|
|
2020-12-17 00:58:31 +01:00
|
|
|
// ensure that float is not converted to a string in scientific notation
|
2022-01-05 02:40:00 +01:00
|
|
|
$joinMeetingParams->setCreateTime(sprintf("%.0f", $creationTime));
|
2020-05-16 17:14:17 +02:00
|
|
|
$joinMeetingParams->setJoinViaHtml5(true);
|
|
|
|
$joinMeetingParams->setRedirect(true);
|
2020-06-04 18:56:55 +02:00
|
|
|
$joinMeetingParams->setGuest($uid === null);
|
2020-05-16 17:14:17 +02:00
|
|
|
|
2021-04-19 14:47:15 +02:00
|
|
|
$joinMeetingParams->addUserData('bbb_listen_only_mode', $room->getListenOnly());
|
|
|
|
|
|
|
|
$joinMeetingParams->addUserData('bbb_skip_check_audio_on_first_join', !$room->getMediaCheck()); // 2.3
|
|
|
|
$joinMeetingParams->addUserData('bbb_skip_video_preview_on_first_join', !$room->getMediaCheck()); // 2.3
|
|
|
|
|
2021-04-25 20:34:36 +02:00
|
|
|
if ($room->getCleanLayout()) {
|
|
|
|
$joinMeetingParams->addUserData('bbb_auto_swap_layout', true);
|
|
|
|
$joinMeetingParams->addUserData('bbb_show_participants_on_login', false);
|
|
|
|
$joinMeetingParams->addUserData('bbb_show_public_chat_on_login', false);
|
|
|
|
}
|
2021-04-19 14:47:15 +02:00
|
|
|
|
2021-04-18 13:14:05 +02:00
|
|
|
if ($this->config->getAppValue('bbb', 'join.theme') === 'true') {
|
|
|
|
$primaryColor = $this->defaults->getColorPrimary();
|
|
|
|
$textColor = $this->defaults->getTextColorPrimary();
|
|
|
|
|
|
|
|
$joinMeetingParams->addUserData('bbb_custom_style', ":root{--nc-primary-color:$primaryColor;--nc-primary-text-color:$textColor;--nc-bg-color:#444;--color-primary:var(--nc-primary-color);--btn-primary-color:var(--nc-primary-text-color);--color-text:#222;--loader-bg:var(--nc-bg-color);--user-list-bg:#fff;--user-list-text:#222;--list-item-bg-hover:#f5f5f5;--item-focus-border:var(--nc-primary-color);--color-off-white:#fff;--color-gray-dark:var(--nc-bg-color);}body{background-color:var(--nc-bg-color);}.overlay--1aTlbi{background-color:var(--nc-bg-color);}.userlistPad--o5KDX{border-right: 1px solid #ededed;}.scrollStyle--Ckr4w{background: transparent;}.item--yl1AH:hover, .item--yl1AH:focus{color:--nc-primary-text-color;}#message-input:focus{box-shadow:0 0 0 1px var(--nc-primary-color);border-color:--nc-primary-color;}.active--Z1SuO2X{border-radius:5px;}");
|
|
|
|
}
|
|
|
|
|
2020-05-16 17:14:17 +02:00
|
|
|
if ($uid) {
|
|
|
|
$joinMeetingParams->setUserId($uid);
|
2021-04-20 14:33:31 +02:00
|
|
|
$joinMeetingParams->setAvatarURL($this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $uid, 'size' => 32]));
|
2020-05-16 17:14:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this->getServer()->getJoinMeetingURL($joinMeetingParams);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create meeting room.
|
|
|
|
*
|
2021-02-24 16:33:37 +01:00
|
|
|
* @return float|int creation time
|
2020-05-16 17:14:17 +02:00
|
|
|
*/
|
2020-06-19 09:28:58 +02:00
|
|
|
public function createMeeting(Room $room, Presentation $presentation = null) {
|
2020-05-16 17:14:17 +02:00
|
|
|
$bbb = $this->getServer();
|
2020-06-10 13:53:46 +02:00
|
|
|
$meetingParams = $this->buildMeetingParams($room, $presentation);
|
2020-05-16 17:14:17 +02:00
|
|
|
|
|
|
|
try {
|
2020-06-10 13:53:46 +02:00
|
|
|
$response = $bbb->createMeeting($meetingParams);
|
2020-05-16 17:14:17 +02:00
|
|
|
} catch (\Exception $e) {
|
2020-06-10 13:53:46 +02:00
|
|
|
throw new \Exception('Can not process create request: ' . $bbb->getCreateMeetingUrl($meetingParams));
|
2020-05-16 17:14:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!$response->success()) {
|
2021-08-04 16:38:06 +02:00
|
|
|
throw new \Exception('Can not create meeting: ' . $response->getMessage());
|
2020-05-16 17:14:17 +02:00
|
|
|
}
|
|
|
|
|
2020-09-22 13:51:47 +02:00
|
|
|
if ($response->getMessageKey() !== 'duplicateWarning') {
|
|
|
|
$this->eventDispatcher->dispatch(MeetingStartedEvent::class, new MeetingStartedEvent($room));
|
|
|
|
}
|
|
|
|
|
2020-05-16 17:14:17 +02:00
|
|
|
return $response->getCreationTime();
|
|
|
|
}
|
|
|
|
|
2020-06-19 09:28:58 +02:00
|
|
|
private function buildMeetingParams(Room $room, Presentation $presentation = null): CreateMeetingParameters {
|
2020-05-16 17:14:17 +02:00
|
|
|
$createMeetingParams = new CreateMeetingParameters($room->uid, $room->name);
|
2022-01-05 02:40:00 +01:00
|
|
|
$createMeetingParams->setAttendeePW($room->attendeePassword);
|
|
|
|
$createMeetingParams->setModeratorPW($room->moderatorPassword);
|
2020-05-16 17:14:17 +02:00
|
|
|
$createMeetingParams->setRecord($room->record);
|
|
|
|
$createMeetingParams->setAllowStartStopRecording($room->record);
|
2022-01-05 02:40:00 +01:00
|
|
|
$createMeetingParams->setLogoutURL($this->urlGenerator->getBaseUrl());
|
2021-07-30 12:12:42 +02:00
|
|
|
$createMeetingParams->setMuteOnStart($room->getJoinMuted());
|
2020-05-16 17:14:17 +02:00
|
|
|
|
2021-12-01 15:05:07 +01:00
|
|
|
$createMeetingParams->addMeta('bbb-origin-version', $this->appManager->getAppVersion(Application::ID));
|
|
|
|
$createMeetingParams->addMeta('bbb-origin', \method_exists($this->defaults, 'getProductName') ? $this->defaults->getProductName() : 'Nextcloud');
|
|
|
|
$createMeetingParams->addMeta('bbb-origin-server-name', $this->request->getServerHost());
|
|
|
|
|
2020-09-22 12:19:48 +02:00
|
|
|
$mac = $this->crypto->calculateHMAC($room->uid);
|
|
|
|
|
|
|
|
$endMeetingUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.hook.meetingEnded', ['token' => $room->uid, 'mac' => $mac]);
|
|
|
|
$createMeetingParams->setEndCallbackUrl($endMeetingUrl);
|
|
|
|
|
|
|
|
$recordingReadyUrl = $this->urlGenerator->linkToRouteAbsolute('bbb.hook.recordingReady', ['token' => $room->uid, 'mac' => $mac]);
|
|
|
|
$createMeetingParams->setRecordingReadyCallbackUrl($recordingReadyUrl);
|
|
|
|
|
2020-09-23 09:13:26 +02:00
|
|
|
$invitationUrl = $this->urlHelper->linkToInvitationAbsolute($room);
|
2020-09-22 16:08:14 +02:00
|
|
|
$createMeetingParams->setModeratorOnlyMessage($this->l10n->t('To invite someone to the meeting, send them this link: %s', [$invitationUrl]));
|
2020-05-16 17:14:17 +02:00
|
|
|
|
|
|
|
if (!empty($room->welcome)) {
|
2022-01-05 02:40:00 +01:00
|
|
|
$createMeetingParams->setWelcome($room->welcome);
|
2020-05-16 17:14:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if ($room->maxParticipants > 0) {
|
|
|
|
$createMeetingParams->setMaxParticipants($room->maxParticipants);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($presentation !== null && $presentation->isValid()) {
|
2021-02-24 16:33:37 +01:00
|
|
|
/** @psalm-suppress InvalidArgument */
|
2020-05-16 17:14:17 +02:00
|
|
|
$createMeetingParams->addPresentation($presentation->getUrl(), null, $presentation->getFilename());
|
|
|
|
}
|
|
|
|
|
2020-06-04 18:56:55 +02:00
|
|
|
if ($room->access === Room::ACCESS_WAITING_ROOM) {
|
|
|
|
$createMeetingParams->setGuestPolicyAskModerator();
|
|
|
|
}
|
|
|
|
|
2020-05-16 17:14:17 +02:00
|
|
|
return $createMeetingParams;
|
|
|
|
}
|
2020-05-17 11:09:16 +02:00
|
|
|
|
2020-06-19 09:28:58 +02:00
|
|
|
public function getRecording(string $recordId) {
|
2020-05-17 11:09:16 +02:00
|
|
|
$recordingParams = new GetRecordingsParameters();
|
2022-01-05 02:40:00 +01:00
|
|
|
$recordingParams->setRecordID($recordId);
|
2020-05-17 11:09:16 +02:00
|
|
|
$recordingParams->setState('any');
|
|
|
|
|
|
|
|
$response = $this->getServer()->getRecordings($recordingParams);
|
|
|
|
|
|
|
|
if (!$response->success()) {
|
|
|
|
throw new \Exception('Could not process get recording request');
|
|
|
|
}
|
|
|
|
|
|
|
|
$records = $response->getRecords();
|
|
|
|
|
|
|
|
if (count($records) === 0) {
|
|
|
|
throw new \Exception('Found no record with given id');
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->recordToArray($records[0]);
|
|
|
|
}
|
|
|
|
|
2021-02-24 15:23:26 +01:00
|
|
|
public function getRecordings(Room $room): array {
|
2020-05-17 11:09:16 +02:00
|
|
|
$recordingParams = new GetRecordingsParameters();
|
2022-01-05 02:40:00 +01:00
|
|
|
$recordingParams->setMeetingID($room->uid);
|
2020-05-17 11:09:16 +02:00
|
|
|
$recordingParams->setState('processing,processed,published,unpublished');
|
|
|
|
|
|
|
|
$response = $this->getServer()->getRecordings($recordingParams);
|
|
|
|
|
|
|
|
if (!$response->success()) {
|
|
|
|
throw new \Exception('Could not process get recordings request');
|
|
|
|
}
|
|
|
|
|
|
|
|
$records = $response->getRecords();
|
|
|
|
|
|
|
|
return array_map(function ($record) {
|
|
|
|
return $this->recordToArray($record);
|
|
|
|
}, $records);
|
|
|
|
}
|
|
|
|
|
2020-06-19 09:28:58 +02:00
|
|
|
public function deleteRecording(string $recordingId): bool {
|
2020-05-17 11:09:16 +02:00
|
|
|
$deleteParams = new DeleteRecordingsParameters($recordingId);
|
|
|
|
|
|
|
|
$response = $this->getServer()->deleteRecordings($deleteParams);
|
|
|
|
|
|
|
|
return $response->isDeleted();
|
|
|
|
}
|
|
|
|
|
2021-02-24 15:23:26 +01:00
|
|
|
/**
|
|
|
|
* @return (array|bool|int|string)[]
|
|
|
|
*
|
|
|
|
* @psalm-return array{id: string, meetingId: string, name: string, published: bool, state: string, startTime: string, participants: int, type: string, length: string, url: string, metas: array}
|
|
|
|
*/
|
|
|
|
private function recordToArray(Record $record): array {
|
2020-05-17 11:09:16 +02:00
|
|
|
return [
|
2021-07-26 15:44:14 +02:00
|
|
|
'id' => $record->getRecordId(),
|
|
|
|
'meetingId' => $record->getMeetingId(),
|
|
|
|
'name' => $record->getName(),
|
|
|
|
'published' => $record->isPublished(),
|
|
|
|
'state' => $record->getState(),
|
|
|
|
'startTime' => $record->getStartTime(),
|
2020-05-17 11:09:16 +02:00
|
|
|
'participants' => $record->getParticipantCount(),
|
2021-07-26 15:44:14 +02:00
|
|
|
'type' => $record->getPlaybackType(),
|
|
|
|
'length' => $record->getPlaybackLength(),
|
|
|
|
'url' => $record->getPlaybackUrl(),
|
|
|
|
'metas' => $record->getMetas(),
|
2020-05-17 11:09:16 +02:00
|
|
|
];
|
|
|
|
}
|
2020-05-17 13:39:01 +02:00
|
|
|
|
2021-02-24 15:23:26 +01:00
|
|
|
public function check(string $url, string $secret): string {
|
2020-05-17 13:39:01 +02:00
|
|
|
$server = new BigBlueButton($url, $secret);
|
|
|
|
|
|
|
|
$meetingParams = new IsMeetingRunningParameters('foobar');
|
|
|
|
|
|
|
|
try {
|
|
|
|
$response = $server->isMeetingRunning($meetingParams);
|
|
|
|
|
|
|
|
if (!$response->success() && !$response->failed()) {
|
|
|
|
return 'invalid-url';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$response->success()) {
|
|
|
|
return 'invalid-secret';
|
|
|
|
}
|
|
|
|
|
|
|
|
return 'success';
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return 'invalid-url';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-13 16:14:40 +01:00
|
|
|
/**
|
|
|
|
* @param null|string $url
|
|
|
|
*/
|
|
|
|
public function getVersion(?string $url = null) {
|
2020-05-17 13:39:01 +02:00
|
|
|
$server = $url === null ? $this->getServer() : new BigBlueButton($url, '');
|
|
|
|
|
|
|
|
return $server->getApiVersion()->getVersion();
|
|
|
|
}
|
2020-08-29 14:37:50 +02:00
|
|
|
|
|
|
|
public function isRunning(Room $room): bool {
|
|
|
|
$isMeetingRunningParams = new IsMeetingRunningParameters($room->getUid());
|
|
|
|
|
|
|
|
$response = $this->getServer()->isMeetingRunning($isMeetingRunningParams);
|
|
|
|
|
|
|
|
return $response->success() && $response->isRunning();
|
|
|
|
}
|
2020-05-16 17:14:17 +02:00
|
|
|
}
|