mirror of
https://github.com/onyx-and-iris/streamlabs-socketio-py
synced 2024-11-21 15:00:48 +00:00
initial commit
This commit is contained in:
parent
2fb9fe3c0d
commit
d132ee7563
3
.gitignore
vendored
3
.gitignore
vendored
@ -127,3 +127,6 @@ dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# toml config
|
||||
config.toml
|
76
README.md
Normal file
76
README.md
Normal 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
37
__main__.py
Normal 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
234
poetry.lock
generated
Normal 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
21
pyproject.toml
Normal 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
1
streamlabsio/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .client import connect
|
59
streamlabsio/client.py
Normal file
59
streamlabsio/client.py
Normal 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
22
streamlabsio/models.py
Normal 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()},
|
||||
},
|
||||
)
|
||||
)
|
Loading…
Reference in New Issue
Block a user