diff --git a/docs/NextcloudApp.rst b/docs/NextcloudApp.rst index 7a3fa319..b930744a 100644 --- a/docs/NextcloudApp.rst +++ b/docs/NextcloudApp.rst @@ -75,6 +75,7 @@ It's advisable to write these steps as commands in a Makefile for quick use. Examples for such Makefiles can be found in this repository: `Skeleton `_ , +`TalkBot `_ `ToGif `_ , `nc_py_api `_ diff --git a/docs/NextcloudTalkBot.rst b/docs/NextcloudTalkBot.rst index 6112e3b1..4b475c1c 100644 --- a/docs/NextcloudTalkBot.rst +++ b/docs/NextcloudTalkBot.rst @@ -1,4 +1,75 @@ -Nextcloud Talk Bot API in Application -===================================== +Nextcloud Talk Bot API in Applications +====================================== -to-do +The AppEcosystem is an excellent choice for developing and deploying bots for Nextcloud Talk. + +Bots for Nextcloud Talk, in essence, don't differ significantly from regular external applications. +The functionality of an external application can include just the bot or provide additional functionalities as well. + +Let's consider a simple example of how to transform the `skeleton` of an external application into a Nextcloud Talk bot. + +The first step is to add the **TALK_BOT** and **TALK** scopes to your `info.xml` file: + +.. code-block:: xml + + + + TALK + TALK_BOT + + + + + +The TALK_BOT scope enables your application to register the bot within the Nextcloud system, while the TALK scope permits access to Talk's endpoints. + +In the global **enabled_handler**, you should include a call to your bot's enabled_handler, as shown in the bot example: + +.. code-block:: python + + def enabled_handler(enabled: bool, nc: NextcloudApp) -> str: + try: + CURRENCY_BOT.enabled_handler(enabled, nc) # registering/unregistering the bot's stuff. + except Exception as e: + return str(e) + return "" + +Afterward, using FastAPI, you can define endpoints that will be invoked by Talk: + +.. code-block:: python + + @APP.post("/currency_talk_bot") + async def currency_talk_bot( + message: Annotated[talk_bot.TalkBotMessage, Depends(talk_bot_app)], + background_tasks: BackgroundTasks, + ): + return requests.Response() + +.. note:: + You must include to each endpoint your bot provides the **Depends(talk_bot_app)**. + **message: Annotated[talk_bot.TalkBotMessage, Depends(talk_bot_app)]** + +Depending on **talk_bot_app** serves as an automatic authentication handler for messages from the cloud, which returns the received message from Nextcloud upon successful authentication. + +Additionally, if your bot can provide quick and fixed execution times, you may not need to create background tasks. +However, in most cases, it's recommended to segregate functionality and perform operations in the background, while promptly returning an empty response to Nextcloud. + +An application can implement multiple bots concurrently, but each bot's endpoints must be unique. + +All authentication is facilitated by the Python SDK, ensuring you needn't concern yourself with anything other than writing useful functionality. + +Currently, bots have access only to three methods: + +* :py:meth:`~nc_py_api.talk_bot.TalkBot.send_message` +* :py:meth:`~nc_py_api.talk_bot.TalkBot.react_to_message` +* :py:meth:`~nc_py_api.talk_bot.TalkBot.delete_reaction` + +.. note:: **The usage of system application functionality for user impersonation in bot development is strongly discouraged**. + All bot messages should only be sent using the ``send_message`` method! + +All other rules and algorithms remain consistent with regular external applications. + +Full source of bot example can be found here: +`TalkBot `_ + +Wishing success with your Nextcloud bot integration! May the force be with you! diff --git a/examples/as_app/talk_bot/HOW_TO_INSTALL.md b/examples/as_app/talk_bot/HOW_TO_INSTALL.md new file mode 100644 index 00000000..eced096b --- /dev/null +++ b/examples/as_app/talk_bot/HOW_TO_INSTALL.md @@ -0,0 +1,28 @@ +How To Install +============== + +Currently, while AppEcosystem hasn't been published on the App Store, and App Store support hasn't been added yet, +installation is a little bit tricky. + +Steps to Install: + +1. [Install AppEcosystem](https://cloud-py-api.github.io/app_ecosystem_v2/Installation.html) +2. Create a deployment daemon according to the [instructions](https://cloud-py-api.github.io/app_ecosystem_v2/CreationOfDeployDaemon.html#create-deploy-daemon) of the Appecosystem +3. php occ app_ecosystem_v2:app:deploy talk_bot "daemon_deploy_name" \ +--info-xml https://raw.githubusercontent.com/cloud-py-api/nc_py_api/main/examples/as_app/talk_bot/appinfo/info.xml + + to deploy a docker image with Bot to docker. + +4. php occ app_ecosystem_v2:app:register talk_bot "daemon_deploy_name" -e --force-scopes \ +--info-xml https://raw.githubusercontent.com/cloud-py-api/nc_py_api/main/examples/as_app/talk_bot/appinfo/info.xml + + to call its **enable** handler and accept all required API scopes by default. + + +In a few months +=============== + +1. Install AppEcosystem from App Store +2. Configure Deploy Daemon with GUI provided by AppEcosystem +3. Go to External Applications page in Nextcloud UI +4. Find this bot in a list and press "Install" and "Enable" buttons, like with usual Applications. diff --git a/examples/as_app/talk_bot/Makefile b/examples/as_app/talk_bot/Makefile index a6e93517..f1e40cad 100644 --- a/examples/as_app/talk_bot/Makefile +++ b/examples/as_app/talk_bot/Makefile @@ -11,8 +11,8 @@ help: @echo " " @echo " deploy deploy example to registered 'docker_dev'" @echo " " - @echo " run28 install ToGif for Nextcloud 28" - @echo " run27 install ToGif for Nextcloud 27" + @echo " run28 install TalkBot for Nextcloud 28" + @echo " run27 install TalkBot for Nextcloud 27" @echo " " @echo " For development of this example use PyCharm run configurations. Development is always set for last Nextcloud." @echo " First run 'TalkBot' and then 'make manual_register', after that you can use/debug/develop it and easy test." diff --git a/examples/as_app/talk_bot/screenshots/talk_bot.png b/examples/as_app/talk_bot/screenshots/talk_bot.png new file mode 100644 index 00000000..bb2c8088 Binary files /dev/null and b/examples/as_app/talk_bot/screenshots/talk_bot.png differ diff --git a/examples/as_app/talk_bot/src/main.py b/examples/as_app/talk_bot/src/main.py index 693a5fe2..606ef34c 100644 --- a/examples/as_app/talk_bot/src/main.py +++ b/examples/as_app/talk_bot/src/main.py @@ -10,10 +10,13 @@ from nc_py_api.ex_app import run_app, set_handlers, talk_bot_app APP = FastAPI() +# We define bot globally, so if no `multiprocessing` module is used, it can be reused by calls. +# All stuff in it works only with local variables, so in the case of multithreading, there should not be problems. CURRENCY_BOT = talk_bot.TalkBot("/currency_talk_bot", "Currency convertor", "Usage: `@currency convert 100 EUR to USD`") def convert_currency(amount, from_currency, to_currency): + """Payload of bot, simplest currency convertor.""" base_url = "https://api.exchangerate-api.com/v4/latest/" # Fetch latest exchange rates @@ -37,17 +40,21 @@ def convert_currency(amount, from_currency, to_currency): def currency_talk_bot_process_request(message: talk_bot.TalkBotMessage): try: + # Ignore `system` messages if message.object_name != "message": return + # We use a wildcard search to only respond to messages sent to us. r = re.search( - r"@currency\s(convert\s)?(\d*)\s(\w*)\sto\s(\w*)", message.object_content["message"], re.IGNORECASE + r"@currency\s(convert\s)?(\d*)\s(\w*)\sto\s(\w*)\s?", message.object_content["message"], re.IGNORECASE ) if r is None: return converted_amount = convert_currency(int(r.group(2)), r.group(3), r.group(4)) converted_amount = round(converted_amount, 2) + # Send reply to chat CURRENCY_BOT.send_message(f"{r.group(2)} {r.group(3)} is equal to {converted_amount} {r.group(4)}", message) except Exception as e: + # In production, it is better to write to log, than in the chat ;) CURRENCY_BOT.send_message(f"Exception: {str(e)}", message) @@ -56,19 +63,23 @@ async def currency_talk_bot( message: Annotated[talk_bot.TalkBotMessage, Depends(talk_bot_app)], background_tasks: BackgroundTasks, ): + # As during converting, we do not process converting locally, we perform this in background, in the background task. background_tasks.add_task(currency_talk_bot_process_request, message) + # Return Response immediately for Nextcloud, that we are ok. return requests.Response() def enabled_handler(enabled: bool, nc: NextcloudApp) -> str: print(f"enabled={enabled}") try: + # `enabled_handler` will install or uninstall bot on the server, depending on ``enabled`` parameter. CURRENCY_BOT.enabled_handler(enabled, nc) except Exception as e: return str(e) return "" +# The same stuff as for usual External Applications @APP.on_event("startup") def initialization(): set_handlers(APP, enabled_handler)