diff --git a/.run/Speech2TxtProvider (last).run.xml b/.run/Speech2TxtProvider (last).run.xml
new file mode 100644
index 00000000..ffb0bd66
--- /dev/null
+++ b/.run/Speech2TxtProvider (last).run.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 716927f8..9bd701b2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,20 +2,26 @@
All notable changes to this project will be documented in this file.
-## [0.7.2 - 2022-12-28]
+## [0.8.0 - 2024-01-xx]
+
+### Added
+
+- API for registering Speech to Text provider(*avalaible from Nextcloud 29*). #196
+
+## [0.7.2 - 2023-12-28]
### Fixed
- files: proper url encoding of special chars in `mkdir` and `delete` methods. #191 Thanks to @tobenary
- files: proper url encoding of special chars in all other `DAV` methods. #194
-## [0.7.1 - 2022-12-21]
+## [0.7.1 - 2023-12-21]
### Added
- The `ocs` method is now public, making it easy to use Nextcloud OCS that has not yet been described. #187
-## [0.7.0 - 2022-12-17]
+## [0.7.0 - 2023-12-17]
### Added
diff --git a/README.md b/README.md
index cfc2763e..401db688 100644
--- a/README.md
+++ b/README.md
@@ -24,21 +24,21 @@ Python library that provides a robust and well-documented API that allows develo
* **Sync + Async**: Provides both sync and async APIs.
### Capabilities
-| **_Capability_** | Nextcloud 26 | Nextcloud 27 | Nextcloud 28 |
-|-----------------------|:------------:|:------------:|:------------:|
-| Calendar | ✅ | ✅ | ✅ |
-| File System & Tags | ✅ | ✅ | ✅ |
-| Nextcloud Talk | ✅ | ✅ | ✅ |
-| Notifications | ✅ | ✅ | ✅ |
-| Shares | ✅ | ✅ | ✅ |
-| Users & Groups | ✅ | ✅ | ✅ |
-| User & Weather status | ✅ | ✅ | ✅ |
-| Other APIs*** | ✅ | ✅ | ✅ |
-| Talk Bot API* | N/A | ✅ | ✅ |
-| Text Processing* | N/A | ❌ | ❌ |
-| SpeechToText* | N/A | ❌ | ❌ |
-
-*_available only for NextcloudApp_
+| **_Capability_** | Nextcloud 26 | Nextcloud 27 | Nextcloud 28 | Nextcloud 29 |
+|-----------------------|:------------:|:------------:|:------------:|:------------:|
+| Calendar | ✅ | ✅ | ✅ | ✅ |
+| File System & Tags | ✅ | ✅ | ✅ | ✅ |
+| Nextcloud Talk | ✅ | ✅ | ✅ | ✅ |
+| Notifications | ✅ | ✅ | ✅ | ✅ |
+| Shares | ✅ | ✅ | ✅ | ✅ |
+| Users & Groups | ✅ | ✅ | ✅ | ✅ |
+| User & Weather status | ✅ | ✅ | ✅ | ✅ |
+| Other APIs*** | ✅ | ✅ | ✅ | ✅ |
+| Talk Bot API* | N/A | ✅ | ✅ | ✅ |
+| TextProcessing* | N/A | N/A | N/A | ❌ |
+| SpeechToText* | N/A | N/A | N/A | ✅ |
+
+*_available only for **NextcloudApp**_
***_Activity, Notes_
### Differences between the Nextcloud and NextcloudApp classes
diff --git a/docs/reference/ExApp.rst b/docs/reference/ExApp.rst
index dff532ee..a875d12e 100644
--- a/docs/reference/ExApp.rst
+++ b/docs/reference/ExApp.rst
@@ -56,3 +56,12 @@ UI methods should be accessed with the help of :class:`~nc_py_api.nextcloud.Next
.. autoclass:: nc_py_api.ex_app.ui.resources.UiStyle
:members:
+
+.. autoclass:: nc_py_api.ex_app.providers.providers.ProvidersApi
+ :members:
+
+.. autoclass:: nc_py_api.ex_app.providers.speech_to_text.SpeechToTextProvider
+ :members:
+
+.. autoclass:: nc_py_api.ex_app.providers.speech_to_text._SpeechToTextProviderAPI
+ :members:
diff --git a/examples/as_app/speech2text/Dockerfile b/examples/as_app/speech2text/Dockerfile
new file mode 100644
index 00000000..04f1e496
--- /dev/null
+++ b/examples/as_app/speech2text/Dockerfile
@@ -0,0 +1,15 @@
+FROM python:3.11-slim-bookworm
+
+COPY requirements.txt /
+
+ADD cs[s] /app/css
+ADD im[g] /app/img
+ADD j[s] /app/js
+ADD l10[n] /app/l10n
+ADD li[b] /app/lib
+
+RUN \
+ python3 -m pip install -r requirements.txt && rm -rf ~/.cache && rm requirements.txt
+
+WORKDIR /app/lib
+ENTRYPOINT ["python3", "main.py"]
diff --git a/examples/as_app/speech2text/Makefile b/examples/as_app/speech2text/Makefile
new file mode 100644
index 00000000..3cde8d7c
--- /dev/null
+++ b/examples/as_app/speech2text/Makefile
@@ -0,0 +1,43 @@
+.DEFAULT_GOAL := help
+
+.PHONY: help
+help:
+ @echo "Welcome to Speech2TextProvider example. Please use \`make \` where is one of"
+ @echo " "
+ @echo " Next commands are only for dev environment with nextcloud-docker-dev!"
+ @echo " They should run from the host you are developing on(with activated venv) and not in the container with Nextcloud!"
+ @echo " "
+ @echo " build-push build image and upload to ghcr.io"
+ @echo " "
+ @echo " deploy deploy Speech2TextProvider to registered 'docker_dev' for Nextcloud Last"
+ @echo " "
+ @echo " run install Speech2TextProvider for Nextcloud Last"
+ @echo " "
+ @echo " For development of this example use PyCharm run configurations. Development is always set for last Nextcloud."
+ @echo " First run 'Speech2TextProvider' and then 'make registerXX', after that you can use/debug/develop it and easy test."
+ @echo " "
+ @echo " register perform registration of running Speech2TextProvider into the 'manual_install' deploy daemon."
+
+.PHONY: build-push
+build-push:
+ docker login ghcr.io
+ docker buildx build --push --platform linux/arm64/v8,linux/amd64 --tag ghcr.io/cloud-py-api/speech_to_text_example:latest .
+
+.PHONY: deploy
+deploy:
+ docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:unregister speech2text_example --silent || true
+ docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:deploy speech2text_example docker_dev \
+ --info-xml https://raw.githubusercontent.com/cloud-py-api/nc_py_api/main/examples/as_app/speech2text_example/appinfo/info.xml
+
+.PHONY: run
+run:
+ docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:unregister speech2text_example --silent || true
+ docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:register speech2text_example docker_dev --force-scopes \
+ --info-xml https://raw.githubusercontent.com/cloud-py-api/nc_py_api/main/examples/as_app/speech2text_example/appinfo/info.xml
+
+.PHONY: register
+register:
+ docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:unregister speech2text_example --silent || true
+ docker exec master-nextcloud-1 sudo -u www-data php occ app_api:app:register speech2text_example manual_install --json-info \
+ "{\"appid\":\"speech2text_example\",\"name\":\"SpeechToText Provider\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"host\":\"host.docker.internal\",\"port\":9036,\"scopes\":{\"required\":[\"AI_PROVIDERS\"],\"optional\":[]},\"protocol\":\"http\",\"system_app\":0}" \
+ --force-scopes --wait-finish
diff --git a/examples/as_app/speech2text/appinfo/info.xml b/examples/as_app/speech2text/appinfo/info.xml
new file mode 100644
index 00000000..0a2595c3
--- /dev/null
+++ b/examples/as_app/speech2text/appinfo/info.xml
@@ -0,0 +1,37 @@
+
+
+ speech2text_example
+ SpeechToText Provider
+ Example of SpeechToText Provider
+
+
+
+ 1.0.0
+ MIT
+ Andrey Borysenko
+ Alexander Piskun
+ PyAppV2_Speech2TextProvider
+ tools
+ https://github.com/cloud-py-api/nc_py_api
+ https://github.com/cloud-py-api/nc_py_api/issues
+ https://github.com/cloud-py-api/nc_py_api
+
+
+
+
+
+ ghcr.io
+ cloud-py-api/speech2text_example
+ latest
+
+
+
+ AI_PROVIDERS
+
+
+
+
+ http
+ false
+
+
diff --git a/examples/as_app/speech2text/lib/main.py b/examples/as_app/speech2text/lib/main.py
new file mode 100644
index 00000000..0632fc4b
--- /dev/null
+++ b/examples/as_app/speech2text/lib/main.py
@@ -0,0 +1,78 @@
+"""Use the simplest model to just test speech recognition.
+
+Example is not production ready, as probably in production app we want running requests in subprocesses with timeout or
+run multiply workers to process requests simultaneously.
+"""
+
+import os
+import tempfile
+import typing
+from contextlib import asynccontextmanager
+
+import torch
+from fastapi import Depends, FastAPI, UploadFile, responses
+from huggingface_hub import snapshot_download
+from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline
+
+from nc_py_api import NextcloudApp
+from nc_py_api.ex_app import nc_app, persistent_storage, run_app, set_handlers
+
+MODEL_NAME = "distil-whisper/distil-small.en"
+
+
+@asynccontextmanager
+async def lifespan(_app: FastAPI):
+ set_handlers(APP, enabled_handler, models_to_fetch={MODEL_NAME: {"ignore_patterns": ["*.bin", "*onnx*"]}})
+ yield
+
+
+APP = FastAPI(lifespan=lifespan)
+
+
+@APP.post("/distil_whisper_small")
+async def distil_whisper_small(
+ _nc: typing.Annotated[NextcloudApp, Depends(nc_app)],
+ data: UploadFile,
+ max_execution_time: float = 0,
+):
+ print(max_execution_time)
+ model = AutoModelForSpeechSeq2Seq.from_pretrained(
+ snapshot_download(
+ MODEL_NAME,
+ local_files_only=True,
+ cache_dir=persistent_storage(),
+ ),
+ torch_dtype=torch.float32,
+ low_cpu_mem_usage=True,
+ use_safetensors=True,
+ ).to("cpu")
+
+ processor = AutoProcessor.from_pretrained(MODEL_NAME)
+ pipe = pipeline(
+ "automatic-speech-recognition",
+ model=model,
+ tokenizer=processor.tokenizer,
+ feature_extractor=processor.feature_extractor,
+ max_new_tokens=128,
+ torch_dtype=torch.float32,
+ device="cpu",
+ )
+ _, file_extension = os.path.splitext(data.filename)
+ with tempfile.NamedTemporaryFile(mode="w+b", suffix=f"{file_extension}") as tmp:
+ tmp.write(await data.read())
+ result = pipe(tmp.name)
+ return responses.Response(content=result["text"])
+
+
+# async
+def enabled_handler(enabled: bool, nc: NextcloudApp) -> str:
+ print(f"enabled={enabled}")
+ if enabled is True:
+ nc.providers.speech_to_text.register("distil_whisper_small", "DistilWhisperSmall", "/distil_whisper_small")
+ else:
+ nc.providers.speech_to_text.unregister("distil_whisper_small")
+ return ""
+
+
+if __name__ == "__main__":
+ run_app("main:APP", log_level="trace")
diff --git a/examples/as_app/speech2text/requirements.txt b/examples/as_app/speech2text/requirements.txt
new file mode 100644
index 00000000..949c3905
--- /dev/null
+++ b/examples/as_app/speech2text/requirements.txt
@@ -0,0 +1 @@
+nc_py_api[app]>=0.8.0
diff --git a/nc_py_api/_version.py b/nc_py_api/_version.py
index 94cb9adc..2444a6cf 100644
--- a/nc_py_api/_version.py
+++ b/nc_py_api/_version.py
@@ -1,3 +1,3 @@
"""Version of nc_py_api."""
-__version__ = "0.7.2"
+__version__ = "0.8.0.dev0"
diff --git a/nc_py_api/ex_app/defs.py b/nc_py_api/ex_app/defs.py
index d420f93c..a846c997 100644
--- a/nc_py_api/ex_app/defs.py
+++ b/nc_py_api/ex_app/defs.py
@@ -39,6 +39,8 @@ class ApiScope(enum.IntEnum):
"""Allows access to Talk API endpoints."""
TALK_BOT = 60
"""Allows to register Talk Bots."""
+ AI_PROVIDERS = 61
+ """Allows to register AI providers."""
ACTIVITIES = 110
"""Activity App endpoints."""
NOTES = 120
diff --git a/nc_py_api/ex_app/providers/__init__.py b/nc_py_api/ex_app/providers/__init__.py
new file mode 100644
index 00000000..f8485984
--- /dev/null
+++ b/nc_py_api/ex_app/providers/__init__.py
@@ -0,0 +1 @@
+"""APIs related to Nextcloud Providers."""
diff --git a/nc_py_api/ex_app/providers/providers.py b/nc_py_api/ex_app/providers/providers.py
new file mode 100644
index 00000000..05a6332c
--- /dev/null
+++ b/nc_py_api/ex_app/providers/providers.py
@@ -0,0 +1,24 @@
+"""Nextcloud API for AI Providers."""
+
+from ..._session import AsyncNcSessionApp, NcSessionApp
+from .speech_to_text import _AsyncSpeechToTextProviderAPI, _SpeechToTextProviderAPI
+
+
+class ProvidersApi:
+ """Class that encapsulates all AI Providers functionality."""
+
+ speech_to_text: _SpeechToTextProviderAPI
+ """SpeechToText Provider API."""
+
+ def __init__(self, session: NcSessionApp):
+ self.speech_to_text = _SpeechToTextProviderAPI(session)
+
+
+class AsyncProvidersApi:
+ """Class that encapsulates all AI Providers functionality."""
+
+ speech_to_text: _AsyncSpeechToTextProviderAPI
+ """SpeechToText Provider API."""
+
+ def __init__(self, session: AsyncNcSessionApp):
+ self.speech_to_text = _AsyncSpeechToTextProviderAPI(session)
diff --git a/nc_py_api/ex_app/providers/speech_to_text.py b/nc_py_api/ex_app/providers/speech_to_text.py
new file mode 100644
index 00000000..4461a4a8
--- /dev/null
+++ b/nc_py_api/ex_app/providers/speech_to_text.py
@@ -0,0 +1,109 @@
+"""Nextcloud API for declaring SpeechToText provider."""
+
+import dataclasses
+
+from ..._exceptions import NextcloudExceptionNotFound
+from ..._misc import require_capabilities
+from ..._session import AsyncNcSessionApp, NcSessionApp
+
+
+@dataclasses.dataclass
+class SpeechToTextProvider:
+ """Speech2Text provider description."""
+
+ def __init__(self, raw_data: dict):
+ self._raw_data = raw_data
+
+ @property
+ def name(self) -> str:
+ """Unique ID for the provider."""
+ return self._raw_data["name"]
+
+ @property
+ def display_name(self) -> str:
+ """Providers display name."""
+ return self._raw_data["display_name"]
+
+ @property
+ def action_handler(self) -> str:
+ """Relative ExApp url which will be called by Nextcloud."""
+ return self._raw_data["action_handler"]
+
+ def __repr__(self):
+ return f"<{self.__class__.__name__} name={self.name}, handler={self.action_handler}>"
+
+
+class _SpeechToTextProviderAPI:
+ """API for registering Speech2Text providers."""
+
+ _ep_suffix: str = "ai_provider/speech_to_text"
+
+ def __init__(self, session: NcSessionApp):
+ self._session = session
+
+ def register(self, name: str, display_name: str, callback_url: str) -> None:
+ """Registers or edit the SpeechToText provider."""
+ require_capabilities("app_api", self._session.capabilities)
+ params = {
+ "name": name,
+ "displayName": display_name,
+ "actionHandler": callback_url,
+ }
+ self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
+
+ def unregister(self, name: str, not_fail=True) -> None:
+ """Removes SpeechToText provider."""
+ require_capabilities("app_api", self._session.capabilities)
+ try:
+ self._session.ocs("DELETE", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
+ except NextcloudExceptionNotFound as e:
+ if not not_fail:
+ raise e from None
+
+ def get_entry(self, name: str) -> SpeechToTextProvider | None:
+ """Get information of the SpeechToText."""
+ require_capabilities("app_api", self._session.capabilities)
+ try:
+ return SpeechToTextProvider(
+ self._session.ocs("GET", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
+ )
+ except NextcloudExceptionNotFound:
+ return None
+
+
+class _AsyncSpeechToTextProviderAPI:
+ """API for registering Speech2Text providers."""
+
+ _ep_suffix: str = "ai_provider/speech_to_text"
+
+ def __init__(self, session: AsyncNcSessionApp):
+ self._session = session
+
+ async def register(self, name: str, display_name: str, callback_url: str) -> None:
+ """Registers or edit the SpeechToText provider."""
+ require_capabilities("app_api", await self._session.capabilities)
+ params = {
+ "name": name,
+ "displayName": display_name,
+ "actionHandler": callback_url,
+ }
+ await self._session.ocs("POST", f"{self._session.ae_url}/{self._ep_suffix}", json=params)
+
+ async def unregister(self, name: str, not_fail=True) -> None:
+ """Removes SpeechToText provider."""
+ require_capabilities("app_api", await self._session.capabilities)
+ try:
+ await self._session.ocs("DELETE", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
+ except NextcloudExceptionNotFound as e:
+ if not not_fail:
+ raise e from None
+
+ async def get_entry(self, name: str) -> SpeechToTextProvider | None:
+ """Get information of the SpeechToText."""
+ require_capabilities("app_api", await self._session.capabilities)
+ try:
+ return SpeechToTextProvider(
+ await self._session.ocs("GET", f"{self._session.ae_url}/{self._ep_suffix}", params={"name": name})
+ )
+ except NextcloudExceptionNotFound:
+ return None
diff --git a/nc_py_api/ex_app/ui/files_actions.py b/nc_py_api/ex_app/ui/files_actions.py
index 4b8b8c9d..e087c155 100644
--- a/nc_py_api/ex_app/ui/files_actions.py
+++ b/nc_py_api/ex_app/ui/files_actions.py
@@ -150,7 +150,7 @@ def unregister(self, name: str, not_fail=True) -> None:
raise e from None
def get_entry(self, name: str) -> UiFileActionEntry | None:
- """Get information of the file action meny entry for current app."""
+ """Get information of the file action meny entry."""
require_capabilities("app_api", self._session.capabilities)
try:
return UiFileActionEntry(
diff --git a/nc_py_api/nextcloud.py b/nc_py_api/nextcloud.py
index e03e73c3..a784ee86 100644
--- a/nc_py_api/nextcloud.py
+++ b/nc_py_api/nextcloud.py
@@ -31,6 +31,7 @@
from .apps import _AppsAPI, _AsyncAppsAPI
from .calendar import _CalendarAPI
from .ex_app.defs import ApiScope, LogLvl
+from .ex_app.providers.providers import AsyncProvidersApi, ProvidersApi
from .ex_app.ui.ui import AsyncUiApi, UiApi
from .files.files import AsyncFilesAPI, FilesAPI
from .notes import _AsyncNotesAPI, _NotesAPI
@@ -294,6 +295,8 @@ class NextcloudApp(_NextcloudBasic):
"""Nextcloud User Preferences API for ExApps"""
ui: UiApi
"""Nextcloud UI API for ExApps"""
+ providers: ProvidersApi
+ """API for registering providers for Nextcloud"""
def __init__(self, **kwargs):
"""The parameters will be taken from the environment.
@@ -305,6 +308,7 @@ def __init__(self, **kwargs):
self.appconfig_ex = AppConfigExAPI(self._session)
self.preferences_ex = PreferencesExAPI(self._session)
self.ui = UiApi(self._session)
+ self.providers = ProvidersApi(self._session)
def log(self, log_lvl: LogLvl, content: str) -> None:
"""Writes log to the Nextcloud log file."""
@@ -431,6 +435,8 @@ class AsyncNextcloudApp(_AsyncNextcloudBasic):
"""Nextcloud User Preferences API for ExApps"""
ui: AsyncUiApi
"""Nextcloud UI API for ExApps"""
+ providers: AsyncProvidersApi
+ """API for registering providers for Nextcloud"""
def __init__(self, **kwargs):
"""The parameters will be taken from the environment.
@@ -442,6 +448,7 @@ def __init__(self, **kwargs):
self.appconfig_ex = AsyncAppConfigExAPI(self._session)
self.preferences_ex = AsyncPreferencesExAPI(self._session)
self.ui = AsyncUiApi(self._session)
+ self.providers = AsyncProvidersApi(self._session)
async def log(self, log_lvl: LogLvl, content: str) -> None:
"""Writes log to the Nextcloud log file."""
diff --git a/pyproject.toml b/pyproject.toml
index 158d0163..fe160b73 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -143,7 +143,7 @@ basic.good-names = [
]
reports.output-format = "colorized"
similarities.ignore-imports = "yes"
-similarities.min-similarity-lines = 6
+similarities.min-similarity-lines = 10
messages_control.disable = [
"missing-class-docstring",
"missing-function-docstring",
diff --git a/scripts/ci_register.sh b/scripts/ci_register.sh
index ee135ba8..922dd25f 100755
--- a/scripts/ci_register.sh
+++ b/scripts/ci_register.sh
@@ -5,5 +5,5 @@
php occ app_api:daemon:register manual_install "Manual Install" manual-install 0 0 0
php occ app_api:app:register "$1" manual_install --json-info \
- "{\"appid\":\"$1\",\"name\":\"$1\",\"daemon_config_name\":\"manual_install\",\"version\":\"$2\",\"secret\":\"$3\",\"host\":\"$4\",\"scopes\":{\"required\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\"],\"optional\":[\"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\", \"TALK_BOT\", \"ACTIVITIES\", \"NOTES\"]},\"port\":$5,\"protocol\":\"http\",\"system_app\":1}" \
+ "{\"appid\":\"$1\",\"name\":\"$1\",\"daemon_config_name\":\"manual_install\",\"version\":\"$2\",\"secret\":\"$3\",\"host\":\"$4\",\"scopes\":{\"required\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\"],\"optional\":[\"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\", \"TALK_BOT\", \"ACTIVITIES\", \"NOTES\", \"AI_PROVIDERS\"]},\"port\":$5,\"protocol\":\"http\",\"system_app\":1}" \
--force-scopes --wait-finish
diff --git a/scripts/dev_register.sh b/scripts/dev_register.sh
index 3afc783c..fa0b3ab9 100644
--- a/scripts/dev_register.sh
+++ b/scripts/dev_register.sh
@@ -13,7 +13,7 @@ NEXTCLOUD_URL="http://$2" APP_PORT=9009 APP_ID="nc_py_api" APP_SECRET="12345" AP
echo $! > /tmp/_install.pid
python3 tests/_install_wait.py "http://localhost:9009/heartbeat" "\"status\":\"ok\"" 15 0.5
docker exec "$1" sudo -u www-data php occ app_api:app:register nc_py_api manual_install --json-info \
- "{\"appid\":\"nc_py_api\",\"name\":\"nc_py_api\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"host\":\"host.docker.internal\",\"scopes\":{\"required\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\"],\"optional\":[\"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\", \"TALK_BOT\", \"ACTIVITIES\", \"NOTES\"]},\"port\":9009,\"protocol\":\"http\",\"system_app\":1}" \
+ "{\"appid\":\"nc_py_api\",\"name\":\"nc_py_api\",\"daemon_config_name\":\"manual_install\",\"version\":\"1.0.0\",\"secret\":\"12345\",\"host\":\"host.docker.internal\",\"scopes\":{\"required\":[\"SYSTEM\", \"FILES\", \"FILES_SHARING\"],\"optional\":[\"USER_INFO\", \"USER_STATUS\", \"NOTIFICATIONS\", \"WEATHER_STATUS\", \"TALK\", \"TALK_BOT\", \"ACTIVITIES\", \"NOTES\", \"AI_PROVIDERS\"]},\"port\":9009,\"protocol\":\"http\",\"system_app\":1}" \
--force-scopes --wait-finish
cat /tmp/_install.pid
kill -15 "$(cat /tmp/_install.pid)"
diff --git a/tests/actual_tests/speech2text_provider_test.py b/tests/actual_tests/speech2text_provider_test.py
new file mode 100644
index 00000000..edb7340e
--- /dev/null
+++ b/tests/actual_tests/speech2text_provider_test.py
@@ -0,0 +1,56 @@
+import pytest
+
+from nc_py_api import NextcloudExceptionNotFound
+
+
+@pytest.mark.require_nc(major=29)
+def test_speech2text_provider(nc_app):
+ nc_app.providers.speech_to_text.register("test_id", "Test #1 Prov", "/some_url")
+ result = nc_app.providers.speech_to_text.get_entry("test_id")
+ assert result.name == "test_id"
+ assert result.display_name == "Test #1 Prov"
+ assert result.action_handler == "some_url"
+ nc_app.providers.speech_to_text.register("test_id2", "Test #2 Prov", "some_url2")
+ result2 = nc_app.providers.speech_to_text.get_entry("test_id2")
+ assert result2.name == "test_id2"
+ assert result2.display_name == "Test #2 Prov"
+ assert result2.action_handler == "some_url2"
+ nc_app.providers.speech_to_text.register("test_id", "Renamed", "/new_url")
+ result = nc_app.providers.speech_to_text.get_entry("test_id")
+ assert result.name == "test_id"
+ assert result.display_name == "Renamed"
+ assert result.action_handler == "new_url"
+ nc_app.providers.speech_to_text.unregister(result.name)
+ nc_app.providers.speech_to_text.unregister(result.name)
+ with pytest.raises(NextcloudExceptionNotFound):
+ nc_app.providers.speech_to_text.unregister(result.name, not_fail=False)
+ nc_app.providers.speech_to_text.unregister(result2.name, not_fail=False)
+ assert nc_app.providers.speech_to_text.get_entry(result2.name) is None
+ assert str(result).find("name=") != -1
+
+
+@pytest.mark.asyncio(scope="session")
+@pytest.mark.require_nc(major=29)
+async def test_speech2text_provider_async(anc_app):
+ await anc_app.providers.speech_to_text.register("test_id", "Test #1 Prov", "/some_url")
+ result = await anc_app.providers.speech_to_text.get_entry("test_id")
+ assert result.name == "test_id"
+ assert result.display_name == "Test #1 Prov"
+ assert result.action_handler == "some_url"
+ await anc_app.providers.speech_to_text.register("test_id2", "Test #2 Prov", "some_url2")
+ result2 = await anc_app.providers.speech_to_text.get_entry("test_id2")
+ assert result2.name == "test_id2"
+ assert result2.display_name == "Test #2 Prov"
+ assert result2.action_handler == "some_url2"
+ await anc_app.providers.speech_to_text.register("test_id", "Renamed", "/new_url")
+ result = await anc_app.providers.speech_to_text.get_entry("test_id")
+ assert result.name == "test_id"
+ assert result.display_name == "Renamed"
+ assert result.action_handler == "new_url"
+ await anc_app.providers.speech_to_text.unregister(result.name)
+ await anc_app.providers.speech_to_text.unregister(result.name)
+ with pytest.raises(NextcloudExceptionNotFound):
+ await anc_app.providers.speech_to_text.unregister(result.name, not_fail=False)
+ await anc_app.providers.speech_to_text.unregister(result2.name, not_fail=False)
+ assert await anc_app.providers.speech_to_text.get_entry(result2.name) is None
+ assert str(result).find("name=") != -1
diff --git a/tests/gfixture_set_env.py b/tests/gfixture_set_env.py
index d0987555..f8bf48da 100644
--- a/tests/gfixture_set_env.py
+++ b/tests/gfixture_set_env.py
@@ -4,6 +4,8 @@
environ["NC_AUTH_USER"] = "admin"
environ["NC_AUTH_PASS"] = "admin" # "MrtGY-KfY24-iiDyg-cr4n4-GLsNZ"
environ["NEXTCLOUD_URL"] = environ.get("NEXTCLOUD_URL", "http://stable27.local")
+ # environ["NEXTCLOUD_URL"] = environ.get("NEXTCLOUD_URL", "http://stable28.local")
+ # environ["NEXTCLOUD_URL"] = environ.get("NEXTCLOUD_URL", "http://nextcloud.local")
environ["APP_ID"] = "nc_py_api"
environ["APP_VERSION"] = "1.0.0"
environ["APP_SECRET"] = "12345"