From be199d9763efc25c910824efc2848762b4dcb07d Mon Sep 17 00:00:00 2001 From: JarbasAi Date: Fri, 15 Sep 2023 18:47:44 +0100 Subject: [PATCH] gitignore --- .gitignore | 160 +++++++++++ build/lib/hivemind_ggwave/__init__.py | 262 ------------------ build/lib/hivemind_ggwave/version.py | 7 - dist/hivemind_ggwave-0.0.0a0-py3-none-any.whl | Bin 8720 -> 0 bytes hivemind_ggwave.egg-info/PKG-INFO | 10 - hivemind_ggwave.egg-info/SOURCES.txt | 10 - hivemind_ggwave.egg-info/dependency_links.txt | 1 - hivemind_ggwave.egg-info/requires.txt | 4 - hivemind_ggwave.egg-info/top_level.txt | 1 - 9 files changed, 160 insertions(+), 295 deletions(-) create mode 100644 .gitignore delete mode 100644 build/lib/hivemind_ggwave/__init__.py delete mode 100644 build/lib/hivemind_ggwave/version.py delete mode 100644 dist/hivemind_ggwave-0.0.0a0-py3-none-any.whl delete mode 100644 hivemind_ggwave.egg-info/PKG-INFO delete mode 100644 hivemind_ggwave.egg-info/SOURCES.txt delete mode 100644 hivemind_ggwave.egg-info/dependency_links.txt delete mode 100644 hivemind_ggwave.egg-info/requires.txt delete mode 100644 hivemind_ggwave.egg-info/top_level.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6769e21 --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ \ No newline at end of file diff --git a/build/lib/hivemind_ggwave/__init__.py b/build/lib/hivemind_ggwave/__init__.py deleted file mode 100644 index 918e391..0000000 --- a/build/lib/hivemind_ggwave/__init__.py +++ /dev/null @@ -1,262 +0,0 @@ -import os -import time -import wave -from distutils.spawn import find_executable -from os.path import isfile, expanduser -from threading import Thread - -import pexpect -import requests -from hivemind_bus_client.identity import NodeIdentity -from ovos_utils.file_utils import get_temp_path -from ovos_utils.log import LOG -from ovos_utils.messagebus import FakeBus, Message -from ovos_utils.network_utils import get_ip -from ovos_utils.sound import play_audio - - -class GGWave(Thread): - """ - - master emits a password via ggwave (periodically until an access key is received) - - devices wanting to connect grab password, generate an access key and send it via ggwave - - master adds a client with key + password, send an ack (containing host) via ggwave - - slave devices keep emitting message until they get the ack (then connect to received host) - """ - - def __init__(self, config=None, callbacks=None, debug=False): - super().__init__(daemon=True) - self.config = config or {} - self.debug = debug - self.rx = self.config.get("ggwave-rx") or \ - find_executable("ggwave-rx") or \ - expanduser("~/.local/bin/ggwave-rx") - self.tx = self.config.get("ggwave-cli") or \ - find_executable("ggwave-cli") or \ - expanduser("~/.local/bin/ggwave-cli") - if not isfile(self.rx): - raise ValueError(f"ggwave-rx not found in {self.rx}, " - f"please install from https://github.com/ggerganov/ggwave") - - self.OPCODES = callbacks or {} - - self.running = False - self.remote = self.config.get("remote", False) - if not isfile(self.tx): - LOG.warning("ggwave-cli not found, forcing remote usage") - self.remote = True - - def stop(self): - self.running = False - - def run(self): - self.running = True - child = pexpect.spawn(self.rx) - marker = "Received sound data successfully: " - while self.running: - try: - txt = child.readline().decode("utf-8").strip() - if self.debug and txt in ["Receiving sound data ...", - "Analyzing captured data .."]: - LOG.debug(txt) - if txt.startswith(marker): - payload = txt.split(marker)[-1][1:-1] - for opcode, handler in self.OPCODES.items(): - if payload.startswith(opcode): - p = payload.split(opcode, 1)[-1] - if self.debug: - LOG.debug(f"OPCODE: {opcode} PAYLOAD: {p}") - handler(p) - break - else: - LOG.error(f"invalid ggwave payload: {payload}") - except pexpect.exceptions.EOF: - # exited - LOG.debug("Exited ggwave-rx process") - break - except pexpect.exceptions.TIMEOUT: - # nothing happened for a while - pass - except KeyboardInterrupt: - break - child.close(True) - - def handle_host(self, payload): - pass - - def handle_key(self, payload): - pass - - def handle_pswd(self, payload): - pass - - def emit(self, payload): - if self.remote: - tmp = get_temp_path("ggwave") - wav = self.encode2wave(payload, - f'{tmp}/{payload.replace(":", "_").replace("/", "_")}.wav') - play_audio(wav).wait() - else: - p = pexpect.spawn(f"{self.tx}") - p.expect("Enter text:") - p.sendline(payload + "\n") - p.expect("Enter text:") - time.sleep(5) - p.close(True) - - def encode2wave(self, message: str, - wav_path: str, - protocolId: int = 1, - sampleRate: float = 48000, - volume: int = 50, - payloadLength: int = -1, - useDSS: int = 0): - url = 'https://ggwave-to-file.ggerganov.com/' - params = { - 'm': message, # message to encode - 'p': protocolId, # transmission protocol to use - 's': sampleRate, # output sample rate - 'v': volume, # output volume - 'l': payloadLength, # if positive - use fixed-length encoding - 'd': useDSS, # if positive - use DSS - } - - response = requests.get(url, params=params) - if response == '' or b'Usage: ggwave-to-file' in response.content: - raise SyntaxError('Request failed') - - with wave.open(wav_path, 'wb') as f: - f.setnchannels(1) - f.setframerate(sampleRate) - f.setsampwidth(2) - f.writeframes(response.content) - return wav_path - - -class GGWaveMaster(Thread): - """ run on hivemind-core device - when loading this class share self.bus to react to events if needed - eg, start/stop on demand - - if in silent mode the password is assumed to be transmited out of band - might be a string emitted by the user with another ggwave implementation - """ - - def __init__(self, bus=None, pswd=None, host=None, silent_mode=False, config=None): - super().__init__(daemon=True) - self.bus = bus or FakeBus() - self.host = host - self.pswd = pswd - - callbacks = { - "HMKEY:": self.handle_key - } - - self.ggwave = GGWave(config, callbacks) - self.ggwave.handle_key = self.handle_key - - # if in silent mode the password is assumed to be transmited out of band - # might be a string emited by the user with another ggwave implementation - self.silent_mode = silent_mode - - def add_client(self, access_key): - - from hivemind_core.database import ClientDatabase - - key = os.urandom(8).hex() - - with ClientDatabase() as db: - name = f"HiveMind-Node-{db.total_clients()}" - db.add_client(name, access_key, crypto_key=key, password=self.pswd) - - # verify - user = db.get_client_by_api_key(access_key) - node_id = db.get_item_id(user) - - LOG.info(f"Credentials added to database! {access_key}") - - self.bus.emit(Message("hm.ggwave.client_registered", - {"key": access_key, - "pswd": self.pswd, - "id": node_id, - "name": name})) - - def run(self): - self.ggwave.start() - LOG.info("ggwave activated") - self.pswd = self.pswd or os.urandom(8).hex() - self.host = self.host or get_ip() - self.bus.emit(Message("hm.ggwave.activated")) - if self.silent_mode: - LOG.info(f"to enrol a new device using ggwave emit the code HMPSWD:{self.pswd}") - while self.ggwave.running: - time.sleep(5) - if not self.silent_mode: - LOG.info("broadcasting password") - self.ggwave.emit(f"HMPSWD:{self.pswd}") - self.bus.emit(Message("hm.ggwave.pswd_emitted")) - - def stop(self): - self.ggwave.stop() - self.bus.emit(Message("hm.ggwave.deactivated")) - LOG.info("ggwave deactivated") - - def handle_key(self, payload): - if self.ggwave.running: - self.bus.emit(Message("hm.ggwave.key_received")) - self.add_client(payload) - self.ggwave.emit(f"HMHOST:{self.host}") - self.bus.emit(Message("hm.ggwave.host_emitted")) - - -class GGWaveSlave: - """ run on satellite devices - when loading this class share self.bus to react to events if needed, - eg connect once entity created """ - - def __init__(self, key=None, bus=None, config=None): - self.bus = bus or FakeBus() - self.pswd = None - self.key = key or os.urandom(8).hex() - callbacks = { - "HMPSWD:": self.handle_pswd, - "HMHOST:": self.handle_host - } - self.ggwave = GGWave(config, callbacks) - - def start(self): - self.ggwave.start() - self.bus.emit(Message("hm.ggwave.activated")) - LOG.info("ggwave activated") - - def stop(self): - self.ggwave.stop() - self.pswd = None - self.key = None - self.bus.emit(Message("hm.ggwave.deactivated")) - LOG.info("ggwave deactivated") - - def handle_pswd(self, payload): - LOG.info(f"ggwave password received: {payload}") - if self.ggwave.running: - #LOG.info(f"ggwave password received: {payload}") - self.pswd = payload - self.bus.emit(Message("hm.ggwave.pswd_received")) - self.ggwave.emit(f"HMKEY:{self.key}") - self.bus.emit(Message("hm.ggwave.key_emitted")) - - def handle_host(self, payload): - if self.ggwave.running: - host = payload - LOG.info(f"ggwave host received: {payload}") - self.bus.emit(Message("hm.ggwave.host_received")) - if host and self.pswd and self.key: - identity = NodeIdentity() - identity.password = self.pswd - identity.access_key = self.key - if not host.startswith("ws://") and not host.startswith("wss://"): - host = "ws://" + host - identity.default_master = host - identity.save() - LOG.info(f"identity saved: {identity.IDENTITY_FILE.path}") - self.bus.emit(Message("hm.ggwave.identity_updated")) - self.stop() diff --git a/build/lib/hivemind_ggwave/version.py b/build/lib/hivemind_ggwave/version.py deleted file mode 100644 index 5e39609..0000000 --- a/build/lib/hivemind_ggwave/version.py +++ /dev/null @@ -1,7 +0,0 @@ -# The following lines are replaced during the release process. -# START_VERSION_BLOCK -VERSION_MAJOR = 0 -VERSION_MINOR = 0 -VERSION_BUILD = 0 -VERSION_ALPHA = 0 -# END_VERSION_BLOCK diff --git a/dist/hivemind_ggwave-0.0.0a0-py3-none-any.whl b/dist/hivemind_ggwave-0.0.0a0-py3-none-any.whl deleted file mode 100644 index 750c7e8171e869b519f4fd26183ecd474a58ff00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8720 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