initial commit

This commit is contained in:
onyx-and-iris 2022-11-13 10:07:08 +00:00
parent 2fb9fe3c0d
commit d132ee7563
8 changed files with 453 additions and 0 deletions

3
.gitignore vendored
View File

@ -127,3 +127,6 @@ dmypy.json
# Pyre type checker
.pyre/
# toml config
config.toml

76
README.md Normal file
View File

@ -0,0 +1,76 @@
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
# A Python client for Streamlabs SocketIO API
### Requirements
- A Streamlabs SocketIO API key.
- You can acquire this by logging into your Streamlabs.com dashboard then `Settings->Api Settings->API Tokens`
### How to install using pip
```
pip install streamlabsio
```
### How to Use
You may store your api key in a `config.toml` file, its contents should resemble:
```toml
[streamlabs]
token = "<apikey>"
```
Place it next to your `__main__.py` file.
#### Otherwise:
You may pass it as a keyword argument.
Example `__main__.py`:
```python
from threading import Thread
import streamlabsio
def on_twitch_event(event, msg):
print(f"{event}: {msg.attrs()}")
def register_callbacks(client):
client.obs.on("streamlabs", on_twitch_event)
client.obs.on("twitch_account", on_twitch_event)
def main():
with streamlabsio.connect(token="<apikey>") as client:
worker = Thread(target=register_callbacks, args=(client,), daemon=True)
worker.start()
while cmd := input("<Enter> to exit\n"):
if not cmd:
break
if __name__ == "__main__":
main()
```
### Attributes
For event messages you may inspect the available attributes using `attrs()`.
example:
```python
def on_twitch_event(event, msg):
print(f"{event}: {msg.attrs()}")
```
### Official Documentation
- [Streamlabs SocketIO API](https://dev.streamlabs.com/docs/socket-api)

37
__main__.py Normal file
View File

@ -0,0 +1,37 @@
import logging
from threading import Thread
import streamlabsio
logging.basicConfig(level=logging.INFO)
def on_youtube_event(event, msg):
print(f"{event}: {msg.attrs()}")
def on_twitch_event(event, msg):
if event == "follow":
print(f"Received follow from {msg.name}")
elif event == "bits":
print(f"{msg.name} donated {msg.amount} bits! With message: {msg.message}")
def register_callbacks(client):
client.obs.on("streamlabs", on_twitch_event)
client.obs.on("twitch_account", on_twitch_event)
client.obs.on("youtube_account", on_youtube_event)
def main():
with streamlabsio.connect() as client:
worker = Thread(target=register_callbacks, args=(client,), daemon=True)
worker.start()
while cmd := input("<Enter> to exit\n"):
if not cmd:
break
if __name__ == "__main__":
main()

234
poetry.lock generated Normal file
View File

@ -0,0 +1,234 @@
[[package]]
name = "black"
version = "22.10.0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
version = "2022.9.24"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = ">=3.6"
[[package]]
name = "charset-normalizer"
version = "2.1.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
python-versions = ">=3.6.0"
[package.extras]
unicode_backport = ["unicodedata2"]
[[package]]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
[[package]]
name = "idna"
version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "isort"
version = "5.10.1"
description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false
python-versions = ">=3.6.1,<4.0"
[package.extras]
pipfile_deprecated_finder = ["pipreqs", "requirementslib"]
requirements_deprecated_finder = ["pipreqs", "pip-api"]
colors = ["colorama (>=0.4.3,<0.5.0)"]
plugins = ["setuptools"]
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "observable"
version = "1.0.3"
description = "minimalist event system"
category = "main"
optional = false
python-versions = "*"
[[package]]
name = "pathspec"
version = "0.10.2"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "platformdirs"
version = "2.5.4"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2022.9.29)", "proselint (>=0.13)", "sphinx-autodoc-typehints (>=1.19.4)", "sphinx (>=5.3)"]
test = ["appdirs (==1.4.4)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest (>=7.2)"]
[[package]]
name = "python-engineio"
version = "3.14.2"
description = "Engine.IO server"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
six = ">=1.9.0"
[package.extras]
asyncio_client = ["aiohttp (>=3.4)"]
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
[[package]]
name = "python-socketio"
version = "4.6.0"
description = "Socket.IO server"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
python-engineio = ">=3.13.0"
requests = {version = ">=2.21.0", optional = true, markers = "extra == \"client\""}
six = ">=1.9.0"
websocket-client = {version = ">=0.54.0", optional = true, markers = "extra == \"client\""}
[package.extras]
asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"]
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
[[package]]
name = "requests"
version = "2.28.1"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=3.7, <4"
[package.dependencies]
certifi = ">=2017.4.17"
charset-normalizer = ">=2,<3"
idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<1.27"
[package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "urllib3"
version = "1.26.12"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
[package.extras]
brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"]
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)"]
[[package]]
name = "websocket-client"
version = "1.4.2"
description = "WebSocket client for Python with low level API options"
category = "main"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"]
optional = ["python-socks", "wsaccel"]
test = ["websockets"]
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "fd2bc058c27869f25132aa1baf2599c482e6f926ee75743cfe7ac1fa1042d4bd"
[metadata.files]
black = []
certifi = []
charset-normalizer = []
click = []
colorama = []
idna = []
isort = []
mypy-extensions = []
observable = []
pathspec = []
platformdirs = []
python-engineio = []
python-socketio = []
requests = []
six = []
tomli = []
urllib3 = []
websocket-client = []

21
pyproject.toml Normal file
View File

@ -0,0 +1,21 @@
[tool.poetry]
name = "streamlabsio"
version = "0.1.0"
description = "Get real time Twitch/Youtube events through Streamlabs SocketIO API"
authors = ["onyx-and-iris <code@onyxandiris.online>"]
license = "MIT"
[tool.poetry.dependencies]
python = "^3.10"
tomli = { version = "^2.0.1", python = "<3.11" }
python-engineio = "3.14.2"
python-socketio = {version = "4.6.0", extras = ["client"]}
observable = "^1.0.3"
[tool.poetry.dev-dependencies]
black = "^22.10.0"
isort = "^5.10.1"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

1
streamlabsio/__init__.py Normal file
View File

@ -0,0 +1 @@
from .client import connect

59
streamlabsio/client.py Normal file
View File

@ -0,0 +1,59 @@
import logging
import pprint
from pathlib import Path
import socketio
from observable import Observable
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib
from .models import as_dataclass
pp = pprint.PrettyPrinter(indent=4)
class Client:
logger = logging.getLogger("socketio.socketio")
def __init__(self, token=None):
self.token = token or self._token_from_toml()
self.sio = socketio.Client()
self.sio.on("connect", self.connect_handler)
self.sio.on("event", self.event_handler)
self.sio.on("disconnect", self.disconnect_handler)
self.obs = Observable()
def __enter__(self):
self.sio.connect(f"https://sockets.streamlabs.com?token={self.token}")
return self
def _token_from_toml(self) -> str:
filepath = Path.cwd() / "config.toml"
with open(filepath, "rb") as f:
conn = tomllib.load(f)
assert "token" in conn.get("streamlabs")
return conn["streamlabs"].get("token")
def connect_handler(self):
self.logger.info("Connected to Twitch Socketio")
def event_handler(self, data):
self.obs.trigger(
data.get("for"),
data["type"],
as_dataclass(data["type"], *data["message"]),
)
def disconnect_handler(self):
self.logger.info("Disconnected from Twitch Socketio")
def __exit__(self, exc_type, exc_val, exc_tb):
self.sio.disconnect()
def connect(**kwargs):
SIO_cls = Client
return SIO_cls(**kwargs)

22
streamlabsio/models.py Normal file
View File

@ -0,0 +1,22 @@
import re
from dataclasses import dataclass
def to_snake_case(s):
return re.sub(r"(?<!^)(?=[A-Z])", "_", s).lower()
def as_dataclass(identifier, data):
def attrs():
return list(to_snake_case(k) for k in data.keys())
return dataclass(
type(
f"{identifier.capitalize()}Dataclass",
(),
{
"attrs": attrs,
**{to_snake_case(k): v for k, v in data.items()},
},
)
)