Compare commits

..

No commits in common. "99ab0f00f8f54c9b2a15380ec93fc400dab743fb" and "37ca764806f08d381cb20828193ff336ff803ac9" have entirely different histories.

11 changed files with 65 additions and 459 deletions

7
.gitignore vendored
View File

@ -128,12 +128,5 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/
.vscode/
# toml config # toml config
config.toml config.toml
# test
quick.py
logging.json
tests/

View File

@ -1,30 +0,0 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
- [x]
## [1.0.0] - 2023-06-28
The only potential breaking change, a new error class raised if the initial connection fails.
### Added
- tomllib/tomli now lazy loaded in _token_from_toml(). Allows possibility to run package without tomli.
- module level logger
- debug example
- raw kwarg for receiving the raw json data.
- `Path.home() / ".config" / "streamlabsio" / "config.toml"` added to config.toml filepaths.
### Changed
- Python minimum required version changed to 3.8
- new error class
- `SteamlabsSIOConnectionError` raised on a connection error

View File

@ -5,15 +5,11 @@
# A Python client for Streamlabs Socket API # A Python client for Streamlabs Socket API
For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
### Requirements ### Requirements
- A Streamlabs Socket API key. - A Streamlabs Socket API key.
- You can acquire this by logging into your Streamlabs.com dashboard then `Settings->Api Settings->API Tokens` - You can acquire this by logging into your Streamlabs.com dashboard then `Settings->Api Settings->API Tokens`
- Python 3.8 or greater
### How to install using pip ### How to install using pip
``` ```
@ -41,8 +37,8 @@ Example `__main__.py`:
import streamlabsio import streamlabsio
def on_twitch_event(event, data): def on_twitch_event(event, msg):
print(f"{event}: {data.attrs()}") print(f"{event}: {msg.attrs()}")
def main(): def main():
@ -62,33 +58,17 @@ if __name__ == "__main__":
From the [SocketIO docs](https://python-socketio.readthedocs.io/en/latest/client.html#managing-background-tasks), `client.sio.wait()` may be used if your application has nothing to do in the main thread. From the [SocketIO docs](https://python-socketio.readthedocs.io/en/latest/client.html#managing-background-tasks), `client.sio.wait()` may be used if your application has nothing to do in the main thread.
### Client class
`streamlabsio.connect(token="<apikey>", raw=False)`
The following keyword arguments may be passed:
- `token`: str Streamlabs SocketIO api token.
- `raw`: boolean=False Receive raw data messages as json objects.
### Attributes ### Attributes
For event data you may inspect the available attributes using `attrs()`. For event messages you may inspect the available attributes using `attrs()`.
example: example:
```python ```python
def on_twitch_event(event, data): def on_twitch_event(event, msg):
print(f"{event}: {data.attrs()}") print(f"{event}: {msg.attrs()}")
``` ```
### Errors
- `SteamlabsSIOConnectionError`: Exception raised when connection errors occur
### Logging
To view raw incoming event data set logging level to DEBUG. Check `debug` example.
### Official Documentation ### Official Documentation
- [Streamlabs Socket API](https://dev.streamlabs.com/docs/socket-api) - [Streamlabs Socket API](https://dev.streamlabs.com/docs/socket-api)

View File

@ -1,19 +1,21 @@
import logging
import streamlabsio import streamlabsio
logging.basicConfig(level=logging.INFO)
def on_youtube_event(event, data):
print(f"{event}: {data.attrs()}")
def on_twitch_event(event, data): def on_youtube_event(event, msg):
print(f"{event}: {msg.attrs()}")
def on_twitch_event(event, msg):
if event == "follow": if event == "follow":
print(f"Received follow from {data.name}") print(f"Received follow from {msg.name}")
elif event == "bits": elif event == "bits":
print(f"{data.name} donated {data.amount} bits! With message: {data.message}") print(f"{msg.name} donated {msg.amount} bits! With message: {msg.message}")
elif event == "donation": elif event == "donation":
print( print(f"{msg.name} donated {msg.formatted_amount}! With message: {msg.message}")
f"{data.name} donated {data.formatted_amount}! With message: {data.message}"
)
def main(): def main():

View File

@ -1,9 +0,0 @@
## About
The underlying socketio and engineio packages emit a lot of logs so it may be useful to filter out streamlabsio logs.
This example prints raw messages whenever Client.event_handler() receives data.
## Use
Run the script and trigger any of the events with `Test Widgets` in the Streamlabs GUI.

View File

@ -1,50 +0,0 @@
from logging import config
import streamlabsio
config.dictConfig(
{
"version": 1,
"formatters": {
"standard": {
"format": "%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s"
}
},
"handlers": {
"stream": {
"level": "DEBUG",
"class": "logging.StreamHandler",
"formatter": "standard",
}
},
"loggers": {"streamlabsio.client": {"handlers": ["stream"], "level": "DEBUG"}},
}
)
def on_youtube_event(event, data):
print(f"{event}: {data.attrs()}")
def on_twitch_event(event, data):
if event == "follow":
print(f"Received follow from {data.name}")
elif event == "bits":
print(f"{data.name} donated {data.amount} bits! With message: {data.message}")
elif event == "donation":
print(
f"{data.name} donated {data.formatted_amount}! With message: {data.message}"
)
def main():
with streamlabsio.connect() as client:
client.obs.on("streamlabs", on_twitch_event)
client.obs.on("twitch_account", on_twitch_event)
client.obs.on("youtube_account", on_youtube_event)
client.sio.sleep(30)
if __name__ == "__main__":
main()

283
poetry.lock generated
View File

@ -1,6 +1,6 @@
[[package]] [[package]]
name = "black" name = "black"
version = "22.12.0" version = "22.10.0"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
category = "dev" category = "dev"
optional = false optional = false
@ -12,8 +12,6 @@ mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0" pathspec = ">=0.9.0"
platformdirs = ">=2" platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""} tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""}
typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}
[package.extras] [package.extras]
colorama = ["colorama (>=0.4.3)"] colorama = ["colorama (>=0.4.3)"]
@ -21,37 +19,24 @@ d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"] uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "cachetools"
version = "5.3.1"
description = "Extensible memoizing collections and decorators"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2023.5.7" version = "2022.9.24"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
[[package]]
name = "chardet"
version = "5.1.0"
description = "Universal encoding detector for Python 3"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "3.1.0" version = "2.1.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7.0" python-versions = ">=3.6.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]] [[package]]
name = "click" name = "click"
@ -63,7 +48,6 @@ python-versions = ">=3.7"
[package.dependencies] [package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""} colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]] [[package]]
name = "colorama" name = "colorama"
@ -73,37 +57,6 @@ category = "dev"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
[[package]]
name = "distlib"
version = "0.3.6"
description = "Distribution utilities"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "exceptiongroup"
version = "1.1.1"
description = "Backport of PEP 654 (exception groups)"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
test = ["pytest (>=6)"]
[[package]]
name = "filelock"
version = "3.12.2"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2023.5.20)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)", "sphinx (>=7.0.1)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)", "pytest (>=7.3.1)"]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.4" version = "3.4"
@ -112,52 +65,27 @@ category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
version = "6.7.0"
description = "Read metadata from Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"]
perf = ["ipython"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-ruff", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"]
[[package]]
name = "iniconfig"
version = "2.0.0"
description = "brain-dead simple config-ini parsing"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]] [[package]]
name = "isort" name = "isort"
version = "5.11.5" version = "5.10.1"
description = "A Python utility / library to sort Python imports." description = "A Python utility / library to sort Python imports."
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.7.0" python-versions = ">=3.6.1,<4.0"
[package.extras] [package.extras]
pipfile-deprecated-finder = ["pipreqs", "requirementslib", "pip-shims (>=0.5.2)"] pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
requirements-deprecated-finder = ["pipreqs", "pip-api"] requirements_deprecated_finder = ["pipreqs", "pip-api"]
colors = ["colorama (>=0.4.3,<0.5.0)"] colors = ["colorama (>=0.4.3,<0.5.0)"]
plugins = ["setuptools"] plugins = ["setuptools"]
[[package]] [[package]]
name = "mypy-extensions" name = "mypy-extensions"
version = "1.0.0" version = "0.4.3"
description = "Type system extensions for programs checked with the mypy type checker." description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = "*"
[[package]] [[package]]
name = "observable" name = "observable"
@ -167,17 +95,9 @@ category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "packaging"
version = "23.1"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]] [[package]]
name = "pathspec" name = "pathspec"
version = "0.11.1" version = "0.10.2"
description = "Utility library for gitignore style pattern matching of file paths." description = "Utility library for gitignore style pattern matching of file paths."
category = "dev" category = "dev"
optional = false optional = false
@ -185,69 +105,15 @@ python-versions = ">=3.7"
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "3.8.0" version = "2.5.4"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev" category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = ">=4.6.3", markers = "python_version < \"3.8\""}
[package.extras] [package.extras]
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)", "sphinx (>=7.0.1)"] docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.4)", "sphinx (>=5.3)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest (>=7.3.1)"] test = ["appdirs (==1.4.4)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"]
[[package]]
name = "pluggy"
version = "1.2.0"
description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
[package.extras]
dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pyproject-api"
version = "1.5.2"
description = "API to interact with the python pyproject.toml based projects"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
packaging = ">=23.1"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
[package.extras]
docs = ["furo (>=2023.5.20)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)", "sphinx (>=7.0.1)"]
testing = ["covdefaults (>=2.3)", "importlib-metadata (>=6.6)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest (>=7.3.1)", "setuptools (>=67.8)", "wheel (>=0.40)"]
[[package]]
name = "pytest"
version = "7.4.0"
description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
iniconfig = "*"
packaging = "*"
pluggy = ">=0.12,<2.0"
tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
[package.extras]
testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
[[package]] [[package]]
name = "python-engineio" name = "python-engineio"
@ -284,17 +150,17 @@ client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.31.0" version = "2.28.1"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7, <4"
[package.dependencies] [package.dependencies]
certifi = ">=2017.4.17" certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4" charset-normalizer = ">=2,<3"
idna = ">=2.5,<4" idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3" urllib3 = ">=1.21.1,<1.27"
[package.extras] [package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"] socks = ["PySocks (>=1.5.6,!=1.5.7)"]
@ -316,83 +182,22 @@ category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
[[package]]
name = "tox"
version = "4.6.3"
description = "tox is a generic virtualenv management and test command line tool"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
cachetools = ">=5.3.1"
chardet = ">=5.1"
colorama = ">=0.4.6"
filelock = ">=3.12.2"
importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""}
packaging = ">=23.1"
platformdirs = ">=3.5.3"
pluggy = ">=1"
pyproject-api = ">=1.5.2"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.6.3", markers = "python_version < \"3.8\""}
virtualenv = ">=20.23.1"
[package.extras]
docs = ["furo (>=2023.5.20)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.23.2,!=1.23.4)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinx (>=7.0.1)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.1.1)", "devpi-process (>=0.3.1)", "diff-cover (>=7.6)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.17.1)", "psutil (>=5.9.5)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-xdist (>=3.3.1)", "pytest (>=7.3.2)", "re-assert (>=1.1)", "time-machine (>=2.10)", "wheel (>=0.40)"]
[[package]]
name = "typed-ast"
version = "1.5.4"
description = "a fork of Python 2 and 3 ast modules with type comment support"
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "typing-extensions"
version = "4.6.3"
description = "Backported and Experimental Type Hints for Python 3.7+"
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.0.3" version = "1.26.12"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
[package.extras] [package.extras]
brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
zstd = ["zstandard (>=0.18.0)"]
[[package]]
name = "virtualenv"
version = "20.23.1"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
distlib = ">=0.3.6,<1"
filelock = ">=3.12,<4"
importlib-metadata = {version = ">=6.6", markers = "python_version < \"3.8\""}
platformdirs = ">=3.5.1,<4"
[package.extras]
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx-argparse (>=0.4)", "sphinx (>=7.0.1)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage-enable-subprocess (>=1)", "coverage (>=7.2.7)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest-env (>=0.8.1)", "pytest-freezer (>=0.4.6)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "pytest (>=7.3.1)", "setuptools (>=67.8)", "time-machine (>=2.9)"]
[[package]] [[package]]
name = "websocket-client" name = "websocket-client"
version = "1.6.1" version = "1.4.2"
description = "WebSocket client for Python with low level API options" description = "WebSocket client for Python with low level API options"
category = "main" category = "main"
optional = false optional = false
@ -403,55 +208,27 @@ docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"]
optional = ["python-socks", "wsaccel"] optional = ["python-socks", "wsaccel"]
test = ["websockets"] test = ["websockets"]
[[package]]
name = "zipp"
version = "3.15.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "jaraco.functools", "more-itertools", "big-o", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.10"
content-hash = "9d75c174c1d055013bab4669a426bbbcd1d5fe31243e1569c8aff0524c143496" content-hash = "fd2bc058c27869f25132aa1baf2599c482e6f926ee75743cfe7ac1fa1042d4bd"
[metadata.files] [metadata.files]
black = [] black = []
cachetools = []
certifi = [] certifi = []
chardet = []
charset-normalizer = [] charset-normalizer = []
click = [] click = []
colorama = [] colorama = []
distlib = []
exceptiongroup = []
filelock = []
idna = [] idna = []
importlib-metadata = []
iniconfig = []
isort = [] isort = []
mypy-extensions = [] mypy-extensions = []
observable = [] observable = []
packaging = []
pathspec = [] pathspec = []
platformdirs = [] platformdirs = []
pluggy = []
pyproject-api = []
pytest = []
python-engineio = [] python-engineio = []
python-socketio = [] python-socketio = []
requests = [] requests = []
six = [] six = []
tomli = [] tomli = []
tox = []
typed-ast = []
typing-extensions = []
urllib3 = [] urllib3 = []
virtualenv = []
websocket-client = [] websocket-client = []
zipp = []

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "streamlabsio" name = "streamlabsio"
version = "1.0.1" version = "0.1.3"
description = "Get real time Twitch/Youtube events through Streamlabs SocketIO API" description = "Get real time Twitch/Youtube events through Streamlabs SocketIO API"
authors = ["onyx-and-iris <code@onyxandiris.online>"] authors = ["onyx-and-iris <code@onyxandiris.online>"]
license = "MIT" license = "MIT"
@ -8,7 +8,7 @@ readme = "README.md"
repository = "https://github.com/onyx-and-iris/streamlabs-socketio-py" repository = "https://github.com/onyx-and-iris/streamlabs-socketio-py"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.8" python = "^3.10"
tomli = { version = "^2.0.1", python = "<3.11" } tomli = { version = "^2.0.1", python = "<3.11" }
python-engineio = "3.14.2" python-engineio = "3.14.2"
python-socketio = {version = "4.6.0", extras = ["client"]} python-socketio = {version = "4.6.0", extras = ["client"]}
@ -17,25 +17,7 @@ observable = "^1.0.3"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
black = "^22.10.0" black = "^22.10.0"
isort = "^5.10.1" isort = "^5.10.1"
tox = "^4.6.3"
pytest = "^7.4.0"
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
debug = "scripts:ex_debug"
[tool.tox]
legacy_tox_ini = """
[tox]
envlist = py38,py39,py310,py311
[testenv]
allowlist_externals = poetry
commands =
poetry install -v
poetry run pytest tests/
"""

View File

@ -1,7 +0,0 @@
import subprocess
from pathlib import Path
def ex_debug():
path = Path.cwd() / "examples" / "debug" / "."
subprocess.run(["py", str(path)])

View File

@ -1,21 +1,22 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Optional
import socketio import socketio
from observable import Observable from observable import Observable
from .error import SteamlabsSIOConnectionError try:
from .models import as_dataclass import tomllib
except ModuleNotFoundError:
import tomli as tomllib
logger = logging.getLogger(__name__) from .models import as_dataclass
class Client: class Client:
def __init__(self, token=None, raw=False): logger = logging.getLogger("socketio.client")
self.logger = logger.getChild(self.__class__.__name__)
def __init__(self, token=None):
self.token = token or self._token_from_toml() self.token = token or self._token_from_toml()
self._raw = raw
self.sio = socketio.Client() self.sio = socketio.Client()
self.sio.on("connect", self.connect_handler) self.sio.on("connect", self.connect_handler)
self.sio.on("event", self.event_handler) self.sio.on("event", self.event_handler)
@ -26,43 +27,15 @@ class Client:
self.youtube = ("follow", "subscription", "superchat") self.youtube = ("follow", "subscription", "superchat")
def __enter__(self): def __enter__(self):
try:
self.sio.connect(f"https://sockets.streamlabs.com?token={self.token}") self.sio.connect(f"https://sockets.streamlabs.com?token={self.token}")
except socketio.exceptions.ConnectionError as e:
self.logger.exception(f"{type(e).__name__}: {e}")
raise SteamlabsSIOConnectionError(
"no connection could be established to the Streamlabs SIO server"
) from e
return self return self
def _token_from_toml(self) -> str: def _token_from_toml(self) -> str:
try: filepath = Path.cwd() / "config.toml"
import tomllib
except ModuleNotFoundError:
import tomli as tomllib
def get_filepath() -> Optional[Path]:
filepaths = (
Path.cwd() / "config.toml",
Path.home() / ".config" / "streamlabsio" / "config.toml",
)
for filepath in filepaths:
if filepath.exists():
return filepath
try:
filepath = get_filepath()
if not filepath:
raise FileNotFoundError("config.toml was not found")
with open(filepath, "rb") as f: with open(filepath, "rb") as f:
conn = tomllib.load(f) conn = tomllib.load(f)
assert "token" in conn.get( assert "token" in conn.get("streamlabs")
"streamlabs"
), "token not found in config.toml"
return conn["streamlabs"].get("token") return conn["streamlabs"].get("token")
except (FileNotFoundError, tomllib.TOMLDecodeError) as e:
self.logger.error(f"{type(e).__name__}: {e}")
raise
def connect_handler(self): def connect_handler(self):
self.logger.info("Connected to Streamlabs Socket API") self.logger.info("Connected to Streamlabs Socket API")
@ -74,11 +47,8 @@ class Client:
self.obs.trigger( self.obs.trigger(
data["for"], data["for"],
data["type"], data["type"],
*data["message"] as_dataclass(data["type"], *data["message"]),
if self._raw
else as_dataclass(data["type"], *data["message"]),
) )
self.logger.debug(data)
def disconnect_handler(self): def disconnect_handler(self):
self.logger.info("Disconnected from Streamlabs Socket API") self.logger.info("Disconnected from Streamlabs Socket API")

View File

@ -1,2 +0,0 @@
class SteamlabsSIOConnectionError(Exception):
"""Exception raised when connection errors occur"""