diff --git a/lib/Controller/LiveTranscriptionController.php b/lib/Controller/LiveTranscriptionController.php index 34cf1eaf65..f8e549886c 100644 --- a/lib/Controller/LiveTranscriptionController.php +++ b/lib/Controller/LiveTranscriptionController.php @@ -11,8 +11,10 @@ namespace OCA\Talk\Controller; use OCA\Talk\Exceptions\LiveTranscriptionAppNotEnabledException; use OCA\Talk\Middleware\Attribute\RequireCallEnabled; use OCA\Talk\Middleware\Attribute\RequireModeratorOrNoLobby; +use OCA\Talk\Middleware\Attribute\RequireModeratorParticipant; use OCA\Talk\Middleware\Attribute\RequireParticipant; use OCA\Talk\Participant; +use OCA\Talk\ResponseDefinitions; use OCA\Talk\Service\LiveTranscriptionService; use OCP\AppFramework\Http; use OCP\AppFramework\Http\Attribute\ApiRoute; @@ -20,6 +22,9 @@ use OCP\AppFramework\Http\Attribute\PublicPage; use OCP\AppFramework\Http\DataResponse; use OCP\IRequest; +/** + * @psalm-import-type TalkLiveTranscriptionLanguage from ResponseDefinitions + */ class LiveTranscriptionController extends AEnvironmentAwareOCSController { public function __construct( string $appName, @@ -97,4 +102,52 @@ class LiveTranscriptionController extends AEnvironmentAwareOCSController { return new DataResponse(null); } + + /** + * Get available languages for live transcriptions + * + * @return DataResponse, array{}>|DataResponse + * + * 200: Available languages got successfully + * 400: The external app "live_transcription" is not available + */ + #[PublicPage] + #[ApiRoute(verb: 'GET', url: '/api/{apiVersion}/live-transcription/languages', requirements: [ + 'apiVersion' => '(v1)', + ])] + public function getAvailableLanguages(): DataResponse { + try { + $languages = $this->liveTranscriptionService->getAvailableLanguages(); + } catch (LiveTranscriptionAppNotEnabledException $e) { + return new DataResponse(['error' => 'app'], Http::STATUS_BAD_REQUEST); + } + + return new DataResponse($languages); + } + + /** + * Set language for live transcriptions + * + * @param string $languageId the ID of the language to set + * @return DataResponse|DataResponse + * + * 200: Language set successfully + * 400: The external app "live_transcription" is not available + * 403: Participant is not a moderator + */ + #[PublicPage] + #[RequireModeratorParticipant] + #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/live-transcription/{token}/language', requirements: [ + 'apiVersion' => '(v1)', + 'token' => '[a-z0-9]{4,30}', + ])] + public function setLanguage(string $languageId): DataResponse { + try { + $this->liveTranscriptionService->setLanguage($this->room, $languageId); + } catch (LiveTranscriptionAppNotEnabledException $e) { + return new DataResponse(['error' => 'app'], Http::STATUS_BAD_REQUEST); + } + + return new DataResponse(null); + } } diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php index 576c7c394d..ca60a0617d 100644 --- a/lib/ResponseDefinitions.php +++ b/lib/ResponseDefinitions.php @@ -534,6 +534,14 @@ namespace OCA\Talk; * config-local: array>, * version: string, * } + * + * @psalm-type TalkLiveTranscriptionLanguage = array{ + * name: string, + * metadata: array{ + * separator: string, + * rtl: bool, + * }, + * } */ class ResponseDefinitions { } diff --git a/lib/Service/LiveTranscriptionService.php b/lib/Service/LiveTranscriptionService.php index 8bac04faf7..8a8bb16312 100644 --- a/lib/Service/LiveTranscriptionService.php +++ b/lib/Service/LiveTranscriptionService.php @@ -27,6 +27,7 @@ class LiveTranscriptionService { private ?string $userId, private IAppManager $appManager, private IUserManager $userManager, + private RoomService $roomService, protected LoggerInterface $logger, ) { } @@ -114,6 +115,58 @@ class LiveTranscriptionService { $this->requestToExAppLiveTranscription('POST', '/api/v1/call/transcribe', $parameters); } + /** + * @throws LiveTranscriptionAppNotEnabledException if the external app + * "live_transcription" is + * not enabled. + * @throws LiveTranscriptionAppAPIException if the request could not be sent + * to the app or the response could + * not be processed. + * @throws LiveTranscriptionAppResponseException if the request itself + * succeeded but the app + * responded with an error. + */ + public function getAvailableLanguages(): array { + $languages = $this->requestToExAppLiveTranscription('GET', '/api/v1/languages'); + if ($languages === null) { + $this->logger->error('Request to live_transcription (ExApp) failed: list of available languages is null'); + + throw new LiveTranscriptionAppAPIException('response-null-language-list'); + } + + return $languages; + } + + /** + * @throws LiveTranscriptionAppNotEnabledException if the external app + * "live_transcription" is + * not enabled. + * @throws LiveTranscriptionAppAPIException if the request could not be sent + * to the app or the response could + * not be processed. + * @throws LiveTranscriptionAppResponseException if the request itself + * succeeded but the app + * responded with an error. + */ + public function setLanguage(Room $room, string $languageId): void { + $parameters = [ + 'roomToken' => $room->getToken(), + 'langId' => ! empty($languageId) ? $languageId : 'es', + ]; + + try { + $this->requestToExAppLiveTranscription('POST', '/api/v1/call/set-language', $parameters); + } catch (LiveTranscriptionAppResponseException $e) { + // If there is no active transcription continue setting the language + // in the room. In any other case, abort. + if ($e->getResponse()->getStatusCode() !== 404) { + throw $e; + } + } + + $this->roomService->setLiveTranscriptionLanguageId($room, $languageId); + } + /** * @throws LiveTranscriptionAppNotEnabledException if the external app * "live_transcription" is diff --git a/lib/Service/RoomService.php b/lib/Service/RoomService.php index 201de7fcdb..c07bf80073 100644 --- a/lib/Service/RoomService.php +++ b/lib/Service/RoomService.php @@ -852,6 +852,11 @@ class RoomService { * Set the ID of the language to use for live transcriptions in the given * room. * + * This method is not meant to be directly used; use + * "LiveTranscriptionService::setLanguage" instead, which only sets the + * language in the room if the external app is available and the language is + * valid. + * * @param Room $room * @param string $newState ID of the language to set */ diff --git a/openapi-full.json b/openapi-full.json index a3cb422bf2..ce4a8660b2 100644 --- a/openapi-full.json +++ b/openapi-full.json @@ -1005,6 +1005,33 @@ } } }, + "LiveTranscriptionLanguage": { + "type": "object", + "required": [ + "name", + "metadata" + ], + "properties": { + "name": { + "type": "string" + }, + "metadata": { + "type": "object", + "required": [ + "separator", + "rtl" + ], + "properties": { + "separator": { + "type": "string" + }, + "rtl": { + "type": "boolean" + } + } + } + } + }, "Matterbridge": { "type": "object", "required": [ @@ -21129,6 +21156,308 @@ } } }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/live-transcription/languages": { + "get": { + "operationId": "live_transcription-get-available-languages", + "summary": "Get available languages for live transcriptions", + "tags": [ + "live_transcription" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Available languages got successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/LiveTranscriptionLanguage" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "The external app \"live_transcription\" is not available", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "enum": [ + "app" + ] + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/live-transcription/{token}/language": { + "post": { + "operationId": "live_transcription-set-language", + "summary": "Set language for live transcriptions", + "tags": [ + "live_transcription" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "languageId" + ], + "properties": { + "languageId": { + "type": "string", + "description": "the ID of the language to set" + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^[a-z0-9]{4,30}$" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Language set successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + }, + "400": { + "description": "The external app \"live_transcription\" is not available", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "enum": [ + "app" + ] + } + } + } + } + } + } + } + } + } + }, + "403": { + "description": "Participant is not a moderator", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "enum": [ + "app" + ] + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/threads/recent": { "get": { "operationId": "thread-get-recent-active-threads", diff --git a/openapi.json b/openapi.json index f45f52d108..a71dba11ee 100644 --- a/openapi.json +++ b/openapi.json @@ -910,6 +910,33 @@ } } }, + "LiveTranscriptionLanguage": { + "type": "object", + "required": [ + "name", + "metadata" + ], + "properties": { + "name": { + "type": "string" + }, + "metadata": { + "type": "object", + "required": [ + "separator", + "rtl" + ], + "properties": { + "separator": { + "type": "string" + }, + "rtl": { + "type": "boolean" + } + } + } + } + }, "Matterbridge": { "type": "object", "required": [ @@ -21034,6 +21061,308 @@ } } }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/live-transcription/languages": { + "get": { + "operationId": "live_transcription-get-available-languages", + "summary": "Get available languages for live transcriptions", + "tags": [ + "live_transcription" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Available languages got successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/LiveTranscriptionLanguage" + } + } + } + } + } + } + } + } + }, + "400": { + "description": "The external app \"live_transcription\" is not available", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "enum": [ + "app" + ] + } + } + } + } + } + } + } + } + } + } + } + } + }, + "/ocs/v2.php/apps/spreed/api/{apiVersion}/live-transcription/{token}/language": { + "post": { + "operationId": "live_transcription-set-language", + "summary": "Set language for live transcriptions", + "tags": [ + "live_transcription" + ], + "security": [ + {}, + { + "bearer_auth": [] + }, + { + "basic_auth": [] + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "languageId" + ], + "properties": { + "languageId": { + "type": "string", + "description": "the ID of the language to set" + } + } + } + } + } + }, + "parameters": [ + { + "name": "apiVersion", + "in": "path", + "required": true, + "schema": { + "type": "string", + "enum": [ + "v1" + ], + "default": "v1" + } + }, + { + "name": "token", + "in": "path", + "required": true, + "schema": { + "type": "string", + "pattern": "^[a-z0-9]{4,30}$" + } + }, + { + "name": "OCS-APIRequest", + "in": "header", + "description": "Required to be true for the API request to pass", + "required": true, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Language set successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "nullable": true + } + } + } + } + } + } + } + }, + "400": { + "description": "The external app \"live_transcription\" is not available", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "enum": [ + "app" + ] + } + } + } + } + } + } + } + } + } + }, + "403": { + "description": "Participant is not a moderator", + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "ocs" + ], + "properties": { + "ocs": { + "type": "object", + "required": [ + "meta", + "data" + ], + "properties": { + "meta": { + "$ref": "#/components/schemas/OCSMeta" + }, + "data": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "string", + "enum": [ + "app" + ] + } + } + } + } + } + } + } + } + } + } + } + } + }, "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/threads/recent": { "get": { "operationId": "thread-get-recent-active-threads", diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts index 402fb86aa0..8c400872b7 100644 --- a/src/types/openapi/openapi-full.ts +++ b/src/types/openapi/openapi-full.ts @@ -1611,6 +1611,40 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/live-transcription/languages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get available languages for live transcriptions */ + get: operations["live_transcription-get-available-languages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/live-transcription/{token}/language": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Set language for live transcriptions */ + post: operations["live_transcription-set-language"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/threads/recent": { parameters: { query?: never; @@ -2483,6 +2517,13 @@ export type components = { phones?: string[]; teams?: string[]; }; + LiveTranscriptionLanguage: { + name: string; + metadata: { + separator: string; + rtl: boolean; + }; + }; Matterbridge: { enabled: boolean; parts: components["schemas"]["MatterbridgeConfigFields"]; @@ -10377,6 +10418,127 @@ export interface operations { }; }; }; + "live_transcription-get-available-languages": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Available languages got successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + [key: string]: components["schemas"]["LiveTranscriptionLanguage"]; + }; + }; + }; + }; + }; + /** @description The external app "live_transcription" is not available */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + error: "app"; + }; + }; + }; + }; + }; + }; + }; + "live_transcription-set-language": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + token: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description the ID of the language to set */ + languageId: string; + }; + }; + }; + responses: { + /** @description Language set successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + /** @description The external app "live_transcription" is not available */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + error: "app"; + }; + }; + }; + }; + }; + /** @description Participant is not a moderator */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + error: "app"; + }; + }; + }; + }; + }; + }; + }; "thread-get-recent-active-threads": { parameters: { query?: { diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts index aef7654c40..c0802b4670 100644 --- a/src/types/openapi/openapi.ts +++ b/src/types/openapi/openapi.ts @@ -1611,6 +1611,40 @@ export type paths = { patch?: never; trace?: never; }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/live-transcription/languages": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get available languages for live transcriptions */ + get: operations["live_transcription-get-available-languages"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/ocs/v2.php/apps/spreed/api/{apiVersion}/live-transcription/{token}/language": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Set language for live transcriptions */ + post: operations["live_transcription-set-language"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/ocs/v2.php/apps/spreed/api/{apiVersion}/chat/{token}/threads/recent": { parameters: { query?: never; @@ -1945,6 +1979,13 @@ export type components = { phones?: string[]; teams?: string[]; }; + LiveTranscriptionLanguage: { + name: string; + metadata: { + separator: string; + rtl: boolean; + }; + }; Matterbridge: { enabled: boolean; parts: components["schemas"]["MatterbridgeConfigFields"]; @@ -9839,6 +9880,127 @@ export interface operations { }; }; }; + "live_transcription-get-available-languages": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Available languages got successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + [key: string]: components["schemas"]["LiveTranscriptionLanguage"]; + }; + }; + }; + }; + }; + /** @description The external app "live_transcription" is not available */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + error: "app"; + }; + }; + }; + }; + }; + }; + }; + "live_transcription-set-language": { + parameters: { + query?: never; + header: { + /** @description Required to be true for the API request to pass */ + "OCS-APIRequest": boolean; + }; + path: { + apiVersion: "v1"; + token: string; + }; + cookie?: never; + }; + requestBody: { + content: { + "application/json": { + /** @description the ID of the language to set */ + languageId: string; + }; + }; + }; + responses: { + /** @description Language set successfully */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: unknown; + }; + }; + }; + }; + /** @description The external app "live_transcription" is not available */ + 400: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + error: "app"; + }; + }; + }; + }; + }; + /** @description Participant is not a moderator */ + 403: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": { + ocs: { + meta: components["schemas"]["OCSMeta"]; + data: { + /** @enum {string} */ + error: "app"; + }; + }; + }; + }; + }; + }; + }; "thread-get-recent-active-threads": { parameters: { query?: {