From fd6c02e4ce3d3a6603860c7fe1d27a457f8e5022 Mon Sep 17 00:00:00 2001 From: miro Date: Fri, 14 Feb 2025 13:47:12 +0000 Subject: [PATCH] modernize --- .github/workflows/conventional-label.yml | 10 ++ .github/workflows/publish_stable.yml | 72 +++++++++++ .github/workflows/release_workflow.yml | 108 ++++++++++++++++ LICENSE | 2 +- README.md | 31 +++++ enroll.py | 10 -- hivemind_chatroom/__init__.py | 81 ------------ hivemind_chatroom/__main__.py | 81 +++++++++--- hivemind_chatroom/templates/room.html | 156 ++++++++++++++++------- hivemind_chatroom/version.py | 6 + readme.md | 54 -------- requirements.txt | 2 +- setup.py | 54 +++++++- 13 files changed, 451 insertions(+), 216 deletions(-) create mode 100644 .github/workflows/conventional-label.yml create mode 100644 .github/workflows/publish_stable.yml create mode 100644 .github/workflows/release_workflow.yml create mode 100644 README.md delete mode 100644 enroll.py create mode 100644 hivemind_chatroom/version.py delete mode 100644 readme.md diff --git a/.github/workflows/conventional-label.yml b/.github/workflows/conventional-label.yml new file mode 100644 index 0000000..9894c1b --- /dev/null +++ b/.github/workflows/conventional-label.yml @@ -0,0 +1,10 @@ +# auto add labels to PRs +on: + pull_request_target: + types: [ opened, edited ] +name: conventional-release-labels +jobs: + label: + runs-on: ubuntu-latest + steps: + - uses: bcoe/conventional-release-labels@v1 diff --git a/.github/workflows/publish_stable.yml b/.github/workflows/publish_stable.yml new file mode 100644 index 0000000..bded776 --- /dev/null +++ b/.github/workflows/publish_stable.yml @@ -0,0 +1,72 @@ +name: Stable Release +on: + push: + branches: [master] + workflow_dispatch: + +jobs: + publish_stable: + uses: TigreGotico/gh-automations/.github/workflows/publish-stable.yml@master + secrets: inherit + with: + branch: 'master' + version_file: 'hivemind_chatroom/version.py' + setup_py: 'setup.py' + publish_release: true + + publish_pypi: + needs: publish_stable + if: success() # Ensure this job only runs if the previous job succeeds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: dev + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: "3.11" + - name: Install Build Tools + run: | + python -m pip install build wheel + - name: version + run: echo "::set-output name=version::$(python setup.py --version)" + id: version + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + with: + tag_name: V${{ steps.version.outputs.version }} + release_name: Release ${{ steps.version.outputs.version }} + body: | + Changes in this Release + ${{ steps.changelog.outputs.changelog }} + draft: false + prerelease: true + commitish: dev + - name: Build Distribution Packages + run: | + python setup.py sdist bdist_wheel + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{secrets.PYPI_TOKEN}} + + + sync_dev: + needs: publish_stable + if: success() # Ensure this job only runs if the previous job succeeds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + ref: master + - name: Push master -> dev + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: dev \ No newline at end of file diff --git a/.github/workflows/release_workflow.yml b/.github/workflows/release_workflow.yml new file mode 100644 index 0000000..3ff3790 --- /dev/null +++ b/.github/workflows/release_workflow.yml @@ -0,0 +1,108 @@ +name: Release Alpha and Propose Stable + +on: + pull_request: + types: [closed] + branches: [dev] + +jobs: + publish_alpha: + if: github.event.pull_request.merged == true + uses: TigreGotico/gh-automations/.github/workflows/publish-alpha.yml@master + secrets: inherit + with: + branch: 'dev' + version_file: 'hivemind_chatroom/version.py' + setup_py: 'setup.py' + update_changelog: true + publish_prerelease: true + changelog_max_issues: 100 + + notify: + if: github.event.pull_request.merged == true + needs: publish_alpha + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Send message to Matrix bots channel + id: matrix-chat-message + uses: fadenb/matrix-chat-message@v0.0.6 + with: + homeserver: 'matrix.org' + token: ${{ secrets.MATRIX_TOKEN }} + channel: '!jImYhOcJWVpvCRsDjc:matrix.org' + message: | + new ${{ github.event.repository.name }} PR merged! https://github.com/${{ github.repository }}/pull/${{ github.event.number }} + + publish_pypi: + needs: publish_alpha + if: success() # Ensure this job only runs if the previous job succeeds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: dev + fetch-depth: 0 # otherwise, there would be errors pushing refs to the destination repository. + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: "3.11" + - name: Install Build Tools + run: | + python -m pip install build wheel + - name: version + run: echo "::set-output name=version::$(python setup.py --version)" + id: version + - name: Build Distribution Packages + run: | + python setup.py sdist bdist_wheel + - name: Publish to Test PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{secrets.PYPI_TOKEN}} + + + propose_release: + needs: publish_alpha + if: success() # Ensure this job only runs if the previous job succeeds + runs-on: ubuntu-latest + steps: + - name: Checkout dev branch + uses: actions/checkout@v3 + with: + ref: dev + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: '3.10' + + - name: Get version from setup.py + id: get_version + run: | + VERSION=$(python setup.py --version) + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Create and push new branch + run: | + git checkout -b release-${{ env.VERSION }} + git push origin release-${{ env.VERSION }} + + - name: Open Pull Request from dev to master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Variables + BRANCH_NAME="release-${{ env.VERSION }}" + BASE_BRANCH="master" + HEAD_BRANCH="release-${{ env.VERSION }}" + PR_TITLE="Release ${{ env.VERSION }}" + PR_BODY="Human review requested!" + + # Create a PR using GitHub API + curl -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: token $GITHUB_TOKEN" \ + -d "{\"title\":\"$PR_TITLE\",\"body\":\"$PR_BODY\",\"head\":\"$HEAD_BRANCH\",\"base\":\"$BASE_BRANCH\"}" \ + https://api.github.com/repos/${{ github.repository }}/pulls + diff --git a/LICENSE b/LICENSE index 6b0b127..cb212c4 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2025 Casimiro Ferreira Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..91c6fbd --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +# HiveMind - Flask Chat Room + +![logo](./flask.png) + +A multi-user Flask chatroom template for [HiveMind](https://github.com/OpenJarbas/HiveMind-core). + +This simple WebUI allows you to securely interact with your OpenVoiceOS instance through [HiveMind](https://github.com/OpenJarbas/HiveMind-core). + +![chatroom](./chatroom.png) + +This is a reference implementation for integrating the HiveMind server-side without requiring users to provide credentials. + +For client-side (JavaScript) connection, check out [HiveMind-webchat](https://github.com/OpenJarbas/HiveMind-webchat). + +## Installation + +Install the package using `pip`: + +```bash +$ pip install hivemind-flask-chatroom +``` + +## Running the Application + +To run the chatroom, use the following command: + +```bash +$ hivemind-flask-chatroom --port 8985 +``` + +> Note: HiveMind credentials are read from your identity file. diff --git a/enroll.py b/enroll.py deleted file mode 100644 index 990401d..0000000 --- a/enroll.py +++ /dev/null @@ -1,10 +0,0 @@ -from jarbas_hive_mind.database import ClientDatabase - -name = "JarbasChatRoomTerminal" -access_key = "RESISTENCEisFUTILE" -crypto_key = "resistanceISfutile" -mail = "chatroom@hivemind.com" - - -with ClientDatabase() as db: - db.add_client(name, mail, access_key, crypto_key=crypto_key) diff --git a/hivemind_chatroom/__init__.py b/hivemind_chatroom/__init__.py index ad42118..e69de29 100644 --- a/hivemind_chatroom/__init__.py +++ b/hivemind_chatroom/__init__.py @@ -1,81 +0,0 @@ -from twisted.internet import reactor -from jarbas_hive_mind import HiveMindConnection -from jarbas_hive_mind.slave.terminal import HiveMindTerminal -from jarbas_utils import create_daemon -from jarbas_utils.log import LOG -from jarbas_utils.messagebus import Message - -platform = "JarbasFlaskChatRoomV0.1" - - -class MessageHandler: - messages = {} - - @staticmethod - def append_message(incoming, message, username, room): - if room not in MessageHandler.messages: - MessageHandler.messages[room] = [] - MessageHandler.messages[room].append({'incoming': incoming, - 'username': username, - 'message': message}) - - @staticmethod - def get_messages(room): - return MessageHandler.messages.get(room, []) - - -class JarbasWebTerminal(HiveMindTerminal): - _autorun = False - - # terminal - def say(self, utterance, username="Anon", room="general"): - MessageHandler.append_message(False, utterance, username, room) - msg = {"data": {"utterances": [utterance], - "lang": "en-us"}, - "type": "recognizer_loop:utterance", - "context": {"source": platform, - "room": room, - "user": username, - "destination": "hive_mind", - "platform": platform}} - self.send_to_hivemind_bus(msg) - - def speak(self, utterance, username="Mycroft", room="general"): - MessageHandler.append_message(True, utterance, username, room) - - # parsed protocol messages - def handle_incoming_mycroft(self, message): - assert isinstance(message, Message) - if message.msg_type == "speak": - room = message.context["room"] - user = message.context["user"] - utterance = message.data["utterance"] - self.speak(utterance, room=room) - elif message.msg_type == "hive.complete_intent_failure": - LOG.error("complete intent failure") - room = message.context["room"] - user = message.context["username"] - utterance = "I don't know how to answer that" - self.speak(utterance, room=room) - - def run(self): - reactor.run() - - def run_threaded(self): - create_daemon(reactor.run, args=(False,)) - - -def get_connection(host="wss://127.0.0.1", - port=5678, name="JarbasChatRoomTerminal", - access_key="RESISTENCEisFUTILE", - crypto_key="resistanceISfutile", - useragent=platform): - con = HiveMindConnection(host, port) - # internal flag, avoid starting twisted reactor - con._autorun = False - - terminal = JarbasWebTerminal(crypto_key=crypto_key, - headers=con.get_headers(name, access_key), - useragent=useragent) - - return con.connect(terminal) diff --git a/hivemind_chatroom/__main__.py b/hivemind_chatroom/__main__.py index e95b3ee..18bea8e 100644 --- a/hivemind_chatroom/__main__.py +++ b/hivemind_chatroom/__main__.py @@ -1,11 +1,59 @@ -from hivemind_chatroom import get_connection -from hivemind_chatroom import MessageHandler +from hivemind_bus_client import HiveMessageBusClient +from ovos_bus_client.message import Message +from ovos_utils.fakebus import FakeBus from flask import Flask, render_template, request, redirect, url_for, \ jsonify, Response + +platform = "JarbasFlaskChatRoomV0.2" +bot_name = "HiveMind" + app = Flask(__name__) -hivemind = None + + + +class MessageHandler: + messages = {} + hivemind: HiveMessageBusClient = None + + @classmethod + def connect(cls): + cls.hivemind = HiveMessageBusClient(useragent=platform, + internal_bus=FakeBus()) + cls.hivemind.connect(site_id="flask") + cls.hivemind.on_mycroft("speak", cls.handle_speak) + + @classmethod + def handle_speak(cls, message: Message): + room = message.context["room"] + utterance = message.data["utterance"] + cls.append_message(True, utterance, bot_name, room) + + @classmethod + def say(cls, utterance, username="Anon", room="general"): + MessageHandler.append_message(False, utterance, username, room) + msg = Message("recognizer_loop:utterance", + {"utterances": [utterance]}, + {"source": platform, + "room": room, + "user": username, + "destination": "skills", + "platform": platform} + ) + cls.hivemind.emit_mycroft(msg) + + @classmethod + def append_message(cls, incoming, message, username, room): + if room not in MessageHandler.messages: + MessageHandler.messages[room] = [] + MessageHandler.messages[room].append({'incoming': incoming, + 'username': username, + 'message': message}) + + @classmethod + def get_messages(cls, room): + return MessageHandler.messages.get(room, []) @app.route('/', methods=['GET']) @@ -26,33 +74,24 @@ def messages(room): @app.route('/send_message/', methods=['POST']) def send_message(room): - hivemind.say(request.form['message'], request.form['username'], room) + MessageHandler.say(request.form['message'], + request.form['username'], + room) return redirect(url_for("chatroom", room=room)) def main(): - global hivemind import argparse parser = argparse.ArgumentParser() - parser.add_argument("--access_key", help="access key", - default="RESISTENCEisFUTILE") - parser.add_argument("--crypto_key", help="payload encryption key", - default="resistanceISfutile") - parser.add_argument("--name", help="human readable device name", - default="JarbasChatRoomTerminal") - parser.add_argument("--host", help="HiveMind host", - default="wss://127.0.0.1") - parser.add_argument("--port", help="HiveMind port number", default=5678) - parser.add_argument("--flask-port", help="Chatroom port number", - default=8081) - parser.add_argument("--flask-host", help="Chatroom host", + parser.add_argument("--port", help="Chatroom port number", + default=8985) + parser.add_argument("--host", help="Chatroom host", default="0.0.0.0") args = parser.parse_args() - hivemind = get_connection(args.host, args.port, args.name, - args.access_key, args.crypto_key) - hivemind.run_threaded() - app.run(args.flask_host, args.flask_port) + + MessageHandler.connect() + app.run(args.flask_host, args.flask_port, debug=True) if __name__ == "__main__": diff --git a/hivemind_chatroom/templates/room.html b/hivemind_chatroom/templates/room.html index 429a030..78bc7d7 100644 --- a/hivemind_chatroom/templates/room.html +++ b/hivemind_chatroom/templates/room.html @@ -1,54 +1,124 @@ - + -Mycroft HiveMind Chatroom - + + + HiveMind Flask Chatroom +
- - -

Mycroft HiveMind Chatroom: {{ room }}

+

HiveMind Chatroom: {{ room }}

Loading messages...
- - - + + +
- + diff --git a/hivemind_chatroom/version.py b/hivemind_chatroom/version.py new file mode 100644 index 0000000..76c4342 --- /dev/null +++ b/hivemind_chatroom/version.py @@ -0,0 +1,6 @@ +# START_VERSION_BLOCK +VERSION_MAJOR = 0 +VERSION_MINOR = 0 +VERSION_BUILD = 1 +VERSION_ALPHA = 0 +# END_VERSION_BLOCK diff --git a/readme.md b/readme.md deleted file mode 100644 index aa3d69d..0000000 --- a/readme.md +++ /dev/null @@ -1,54 +0,0 @@ -# HiveMind - Flask Template (Mycroft Chat Room) - -![logo](./flask.png) - -Flask Room for [Mycroft HiveMind](https://github.com/JarbasSkills/skill-hivemind) - -A very simple WebUI for interacting with your Mycroft instance securely trough the [HiveMind](https://github.com/OpenJarbas/HiveMind-core) - -![](./chatroom.png) - - -Reference implementation on integration HiveMind with a flask app, hivemind connection is handled server side - -See [HiveMind-webchat](https://github.com/OpenJarbas/HiveMind-webchat) for client side (javascript) connection - - -## Install - -```bash -$ pip install HiveMind-chatroom -``` -## Usage - -```bash -$ HiveMind-chatroom --help -usage: HiveMind-chatroom [-h] [--access_key ACCESS_KEY] [--crypto_key CRYPTO_KEY] [--name NAME] [--host HOST] [--port PORT] [--flask-port FLASK_PORT] [--flask-host FLASK_HOST] - -optional arguments: - -h, --help show this help message and exit - --access_key ACCESS_KEY - access key - --crypto_key CRYPTO_KEY - payload encryption key - --name NAME human readable device name - --host HOST HiveMind host - --port PORT HiveMind port number - --flask-port FLASK_PORT - Chatroom port number - --flask-host FLASK_HOST - Chatroom host - -``` - -Default values are - -``` ---access_key - "RESISTENCEisFUTILE" ---crypto_key - "resistanceISfutile" ---name - "JarbasChatRoomTerminal" ---host - "wss://127.0.0.1" ---port - 5678 ---flask-port - 8081 - -``` \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b73b997..ff23be5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -jarbas_hive_mind>=0.10.1 +hivemind-bus-client flask \ No newline at end of file diff --git a/setup.py b/setup.py index c58c641..0f5f4a8 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,62 @@ from setuptools import setup +import os + +BASEDIR = os.path.abspath(os.path.dirname(__file__)) + +with open(f"{BASEDIR}/README.md", "r") as fh: + long_desc = fh.read() + + +def get_version(): + """ Find the version of the package""" + version_file = os.path.join(BASEDIR, 'hivemind_chatroom', 'version.py') + major, minor, build, alpha = (None, None, None, None) + with open(version_file) as f: + for line in f: + if 'VERSION_MAJOR' in line: + major = line.split('=')[1].strip() + elif 'VERSION_MINOR' in line: + minor = line.split('=')[1].strip() + elif 'VERSION_BUILD' in line: + build = line.split('=')[1].strip() + elif 'VERSION_ALPHA' in line: + alpha = line.split('=')[1].strip() + + if ((major and minor and build and alpha) or + '# END_VERSION_BLOCK' in line): + break + version = f"{major}.{minor}.{build}" + if int(alpha): + version += f"a{alpha}" + return version + + +def required(requirements_file): + """ Read requirements file and remove comments and empty lines. """ + with open(os.path.join(BASEDIR, requirements_file), 'r') as f: + requirements = f.read().splitlines() + if 'MYCROFT_LOOSE_REQUIREMENTS' in os.environ: + print('USING LOOSE REQUIREMENTS!') + requirements = [r.replace('==', '>=').replace('~=', '>=') for r in requirements] + return [pkg for pkg in requirements + if pkg.strip() and not pkg.startswith("#")] + setup( - name='HiveMind-chatroom', - version='0.0.1', + name='hivemind-flask-chatroom', + version=get_version(), packages=['hivemind_chatroom'], url='https://github.com/OpenJarbas/HiveMind-chatroom', license='MIT', author='jarbasAI', author_email='jarbasai@mailfence.com', - description='Mycroft Chatroom', - install_requires=["jarbas_hive_mind>=0.10.3", "flask"], + description='Hivemind OVOS Chatroom', + long_description=long_desc, + long_description_content_type='text/markdown', + install_requires=required("requirements.txt"), entry_points={ 'console_scripts': [ - 'HiveMind-chatroom=hivemind_chatroom.__main__:main' + 'hivemind-flask-chatroom=hivemind_chatroom.__main__:main' ] } )