Replace pip with Poetry and upgrades for Python 3.10
This commit is contained in:
parent
2dc3c8cd3c
commit
7d18f086e8
8 changed files with 1944 additions and 166 deletions
10
Jenkinsfile
vendored
10
Jenkinsfile
vendored
|
@ -15,8 +15,7 @@ pipeline {
|
||||||
}
|
}
|
||||||
stage('Python') {
|
stage('Python') {
|
||||||
steps {
|
steps {
|
||||||
sh '/usr/local/bin/python3.7 -m venv env'
|
sh 'poetry install'
|
||||||
sh 'env/bin/pip install -r requirements.ci.txt'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,12 +30,12 @@ pipeline {
|
||||||
}
|
}
|
||||||
stage('MyPy (stateserver)') {
|
stage('MyPy (stateserver)') {
|
||||||
steps {
|
steps {
|
||||||
sh 'env/bin/mypy stateserver.py'
|
sh 'poetry run mypy stateserver.py'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stage('MyPy (shittyserver)') {
|
stage('MyPy (shittyserver)') {
|
||||||
steps {
|
steps {
|
||||||
sh 'env/bin/mypy shittyserver.py'
|
sh 'poetry run mypy shittyserver.py'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +117,8 @@ pipeline {
|
||||||
sshagent(credentials: ['ury']) {
|
sshagent(credentials: ['ury']) {
|
||||||
sh 'scp -v -o StrictHostKeyChecking=no stateserver.py liquidsoap@dolby.ury:/opt/webstudioserver/stateserver.py'
|
sh 'scp -v -o StrictHostKeyChecking=no stateserver.py liquidsoap@dolby.ury:/opt/webstudioserver/stateserver.py'
|
||||||
sh 'scp -v -o StrictHostKeyChecking=no shittyserver.py liquidsoap@dolby.ury:/opt/webstudioserver/shittyserver.py'
|
sh 'scp -v -o StrictHostKeyChecking=no shittyserver.py liquidsoap@dolby.ury:/opt/webstudioserver/shittyserver.py'
|
||||||
sh 'scp -v -o StrictHostKeyChecking=no requirements.txt liquidsoap@dolby.ury:/opt/webstudioserver/requirements.txt'
|
sh 'scp -v -o StrictHostKeyChecking=no pyproject.toml liquidsoap@dolby.ury:/opt/webstudioserver/pyproject.toml'
|
||||||
|
sh 'scp -v -o StrictHostKeyChecking=no poetry.lock liquidsoap@dolby.ury:/opt/webstudioserver/poetry.lock'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
11
README.md
11
README.md
|
@ -10,11 +10,11 @@ The clientside is written in TypeScript using React and Redux, the serverside is
|
||||||
|
|
||||||
Client:
|
Client:
|
||||||
|
|
||||||
* Node.js and Yarn 1.x
|
- Node.js and Yarn 1.x
|
||||||
|
|
||||||
Server:
|
Server:
|
||||||
|
|
||||||
* Python 3.7-3.9 (note: Python 3.10 is not supported)
|
- Python 3.7-3.9 (note: Python 3.10 is not supported)
|
||||||
|
|
||||||
### Installing
|
### Installing
|
||||||
|
|
||||||
|
@ -22,12 +22,10 @@ Clone the repo and run `yarn`.
|
||||||
|
|
||||||
You'll probably want to change the values in `.env` to reflect the MyRadio environment and/or where the server is running (e.g. if you're running the server locally, change `REACT_APP_WS_URL` to `ws://localhost:8079/stream`).
|
You'll probably want to change the values in `.env` to reflect the MyRadio environment and/or where the server is running (e.g. if you're running the server locally, change `REACT_APP_WS_URL` to `ws://localhost:8079/stream`).
|
||||||
|
|
||||||
If you want to hack on the server, create a virtualenv and install Python packages:
|
If you want to hack on the server, use [Poetry](https://python-poetry.org/docs/) create a virtualenv and install Python packages:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ python3 -m venv venv
|
$ poetry install
|
||||||
$ source venv/bin/activate
|
|
||||||
$ pip install -r requirements.txt
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Versions
|
### Versions
|
||||||
|
@ -65,6 +63,7 @@ This is done via the BAPSicle project by updating the `/presenter` submodule, si
|
||||||
If you want to demo build a BAPS Presenter release, run `npm run build-baps` and the result will be in the `build` directory.
|
If you want to demo build a BAPS Presenter release, run `npm run build-baps` and the result will be in the `build` directory.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
![Mic Live With Main Screen](images/HomeWithMic.png?raw=true "Mic Live on Main Screen")
|
![Mic Live With Main Screen](images/HomeWithMic.png?raw=true "Mic Live on Main Screen")
|
||||||
|
|
||||||
![Home Page of webstudio](images/Home.png?raw=true "Home Page of WebStudio")
|
![Home Page of webstudio](images/Home.png?raw=true "Home Page of WebStudio")
|
||||||
|
|
1767
poetry.lock
generated
Normal file
1767
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
29
pyproject.toml
Normal file
29
pyproject.toml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
[tool.poetry]
|
||||||
|
name = "webstudio"
|
||||||
|
version = "1.6.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Marks Polakovs <marks.polakovs@ury.org.uk>", "Matthew Stratford <matthew.stratford@ury.org.uk>"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.7"
|
||||||
|
aiohttp = "^3.8.4"
|
||||||
|
av = "^10.0.0"
|
||||||
|
jack-client = "^0.5.4"
|
||||||
|
websockets = "^10.4"
|
||||||
|
aiortc = "^1.4.0"
|
||||||
|
sentry-sdk = "^1.16.0"
|
||||||
|
requests = "^2.28.2"
|
||||||
|
flask = "^2.2.3"
|
||||||
|
flask-cors = "^3.0.10"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
black = "^23.1.0"
|
||||||
|
mypy = "^1.1.1"
|
||||||
|
types-jack-client = "^0.5.10.7"
|
||||||
|
types-requests = "^2.28.11.15"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
|
@ -1,21 +0,0 @@
|
||||||
aiohttp==3.7.4
|
|
||||||
async-timeout==3.0.1
|
|
||||||
attrs==19.3.0
|
|
||||||
certifi==2020.4.5.1
|
|
||||||
chardet==3.0.4
|
|
||||||
click==7.1.2
|
|
||||||
Flask==1.1.2
|
|
||||||
idna==2.9
|
|
||||||
itsdangerous==1.1.0
|
|
||||||
Jinja2==2.11.3
|
|
||||||
MarkupSafe==1.1.1
|
|
||||||
multidict==5.1.0
|
|
||||||
mypy==0.770
|
|
||||||
mypy-extensions==0.4.3
|
|
||||||
requests==2.26.0
|
|
||||||
typed-ast==1.4.1
|
|
||||||
typing-extensions==3.7.4.2
|
|
||||||
urllib3==1.26.5
|
|
||||||
websockets==9.1
|
|
||||||
Werkzeug==1.0.1
|
|
||||||
yarl==1.4.2
|
|
|
@ -1,56 +0,0 @@
|
||||||
aiohttp==3.7.4
|
|
||||||
aioice==0.6.18
|
|
||||||
aiortc==0.9.27
|
|
||||||
appdirs==1.4.4
|
|
||||||
async-timeout==3.0.1
|
|
||||||
attrs==19.3.0
|
|
||||||
av==7.0.1
|
|
||||||
black==20.8b1
|
|
||||||
blinker==1.4
|
|
||||||
certifi==2020.4.5.1
|
|
||||||
cffi==1.14.0
|
|
||||||
chardet==3.0.4
|
|
||||||
click==7.1.2
|
|
||||||
crc32c==2.0
|
|
||||||
cryptography==3.3.2
|
|
||||||
expiringdict==1.2.1
|
|
||||||
Flask==1.1.2
|
|
||||||
Flask-Cors==3.0.9
|
|
||||||
gunicorn==20.0.4
|
|
||||||
idna==2.9
|
|
||||||
itsdangerous==1.1.0
|
|
||||||
JACK-Client==0.5.2
|
|
||||||
jedi==0.15.2
|
|
||||||
Jinja2==2.11.3
|
|
||||||
jsonpickle==1.3
|
|
||||||
MarkupSafe==1.1.1
|
|
||||||
multidict==5.1.0
|
|
||||||
mypy==0.770
|
|
||||||
mypy-extensions==0.4.3
|
|
||||||
netifaces==0.10.9
|
|
||||||
parso==0.6.2
|
|
||||||
pathspec==0.8.1
|
|
||||||
pluggy==0.13.1
|
|
||||||
pycparser==2.20
|
|
||||||
pyee==7.0.1
|
|
||||||
PyJWT==1.7.1
|
|
||||||
pylibsrtp==0.6.6
|
|
||||||
pyls==0.1.6
|
|
||||||
pyls-mypy==0.1.8
|
|
||||||
python-dateutil==2.8.1
|
|
||||||
python-jsonrpc-server==0.3.4
|
|
||||||
python-language-server==0.31.9
|
|
||||||
pytz==2019.3
|
|
||||||
regex==2020.11.13
|
|
||||||
requests==2.26.0
|
|
||||||
sentry-sdk==1.0.0
|
|
||||||
six==1.14.0
|
|
||||||
toml==0.10.2
|
|
||||||
twilio==6.38.1
|
|
||||||
typed-ast==1.4.1
|
|
||||||
typing-extensions==3.7.4.2
|
|
||||||
ujson==1.35
|
|
||||||
urllib3==1.26.5
|
|
||||||
websockets==9.1
|
|
||||||
Werkzeug==1.0.1
|
|
||||||
yarl==1.4.2
|
|
|
@ -11,20 +11,18 @@ from typing import Optional, Any, Type, Dict, List
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import av # type: ignore
|
import av # type: ignore
|
||||||
import jack as Jack # type: ignore
|
import jack as Jack
|
||||||
import websockets
|
from jack import OwnPort
|
||||||
|
import websockets.exceptions, websockets.server, websockets.connection
|
||||||
from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription # type: ignore
|
from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription # type: ignore
|
||||||
from aiortc.mediastreams import MediaStreamError # type: ignore
|
from aiortc.mediastreams import MediaStreamError # type: ignore
|
||||||
import sentry_sdk # type: ignore
|
import sentry_sdk
|
||||||
|
|
||||||
config = configparser.RawConfigParser()
|
config = configparser.RawConfigParser()
|
||||||
config.read("serverconfig.ini")
|
config.read("serverconfig.ini")
|
||||||
|
|
||||||
if config.get("sentry", "enable") == "True":
|
if config.get("sentry", "enable") == "True":
|
||||||
sentry_sdk.init(
|
sentry_sdk.init(config.get("sentry", "dsn"), traces_sample_rate=1.0)
|
||||||
config.get("sentry", "dsn"),
|
|
||||||
traces_sample_rate=1.0
|
|
||||||
)
|
|
||||||
|
|
||||||
file_contents_ex = re.compile(r"^ws=\d$")
|
file_contents_ex = re.compile(r"^ws=\d$")
|
||||||
|
|
||||||
|
@ -53,7 +51,10 @@ def get_turn_credentials() -> TurnCredentials:
|
||||||
provider = config.get("shittyserver", "turn_provider")
|
provider = config.get("shittyserver", "turn_provider")
|
||||||
if provider == "twilio":
|
if provider == "twilio":
|
||||||
from twilio.rest import Client # type: ignore
|
from twilio.rest import Client # type: ignore
|
||||||
client = Client(config.get("twilio", "account_sid"), config.get("twilio", "auth_token"))
|
|
||||||
|
client = Client(
|
||||||
|
config.get("twilio", "account_sid"), config.get("twilio", "auth_token")
|
||||||
|
)
|
||||||
|
|
||||||
token = client.tokens.create()
|
token = client.tokens.create()
|
||||||
# Twilio's typedef is wrong, reee
|
# Twilio's typedef is wrong, reee
|
||||||
|
@ -65,19 +66,19 @@ def get_turn_credentials() -> TurnCredentials:
|
||||||
raise Exception("unknown provider " + provider)
|
raise Exception("unknown provider " + provider)
|
||||||
|
|
||||||
|
|
||||||
@Jack.set_error_function # type: ignore
|
@Jack.set_error_function
|
||||||
def error(msg: str) -> None:
|
def error(msg: str) -> None:
|
||||||
print("Error:", msg)
|
print("Error:", msg)
|
||||||
|
|
||||||
|
|
||||||
@Jack.set_info_function # type: ignore
|
@Jack.set_info_function
|
||||||
def info(msg: str) -> None:
|
def info(msg: str) -> None:
|
||||||
print("Info:", msg)
|
print("Info:", msg)
|
||||||
|
|
||||||
|
|
||||||
jack = Jack.Client("webstudio")
|
jack = Jack.Client("webstudio")
|
||||||
out1 = jack.outports.register("out_0")
|
out1: OwnPort = jack.outports.register("out_0") # type: ignore
|
||||||
out2 = jack.outports.register("out_1")
|
out2: OwnPort = jack.outports.register("out_1") # type: ignore
|
||||||
|
|
||||||
transfer_buffer1: Any = None
|
transfer_buffer1: Any = None
|
||||||
transfer_buffer2: Any = None
|
transfer_buffer2: Any = None
|
||||||
|
@ -92,19 +93,19 @@ def init_buffers() -> None:
|
||||||
init_buffers()
|
init_buffers()
|
||||||
|
|
||||||
|
|
||||||
@jack.set_process_callback # type: ignore
|
@jack.set_process_callback
|
||||||
def process(frames: int) -> None:
|
def process(frames: int) -> None:
|
||||||
buf1 = out1.get_buffer()
|
buf1 = out1.get_buffer()
|
||||||
if transfer_buffer1.read_space == 0:
|
if transfer_buffer1.read_space == 0:
|
||||||
for i in range(len(buf1)):
|
for i in range(len(buf1)):
|
||||||
buf1[i] = b'\x00'
|
buf1[i] = b"\x00" # type: ignore
|
||||||
else:
|
else:
|
||||||
piece1 = transfer_buffer1.read(len(buf1))
|
piece1 = transfer_buffer1.read(len(buf1))
|
||||||
buf1[: len(piece1)] = piece1
|
buf1[: len(piece1)] = piece1
|
||||||
buf2 = out2.get_buffer()
|
buf2 = out2.get_buffer()
|
||||||
if transfer_buffer2.read_space == 0:
|
if transfer_buffer2.read_space == 0:
|
||||||
for i in range(len(buf2)):
|
for i in range(len(buf2)):
|
||||||
buf2[i] = b'\x00'
|
buf2[i] = b"\x00" # type: ignore
|
||||||
else:
|
else:
|
||||||
piece2 = transfer_buffer2.read(len(buf2))
|
piece2 = transfer_buffer2.read(len(buf2))
|
||||||
buf2[: len(piece2)] = piece2
|
buf2[: len(piece2)] = piece2
|
||||||
|
@ -119,7 +120,9 @@ async def notify_mattserver_about_sessions() -> None:
|
||||||
data: Dict[str, Dict[str, str]] = {}
|
data: Dict[str, Dict[str, str]] = {}
|
||||||
for sid, sess in active_sessions.items():
|
for sid, sess in active_sessions.items():
|
||||||
data[sid] = sess.to_dict()
|
data[sid] = sess.to_dict()
|
||||||
async with session.post(config.get("shittyserver", "notify_url"), json=data) as response:
|
async with session.post(
|
||||||
|
config.get("shittyserver", "notify_url"), json=data
|
||||||
|
) as response:
|
||||||
print("Mattserver response", response)
|
print("Mattserver response", response)
|
||||||
|
|
||||||
|
|
||||||
|
@ -128,7 +131,7 @@ class NotReadyException(BaseException):
|
||||||
|
|
||||||
|
|
||||||
class Session(object):
|
class Session(object):
|
||||||
websocket: Optional[websockets.WebSocketServerProtocol]
|
websocket: Optional[websockets.server.WebSocketServerProtocol]
|
||||||
connection_state: Optional[str]
|
connection_state: Optional[str]
|
||||||
pc: Optional[Any]
|
pc: Optional[Any]
|
||||||
connection_id: str
|
connection_id: str
|
||||||
|
@ -153,7 +156,7 @@ class Session(object):
|
||||||
def to_dict(self) -> Dict[str, str]:
|
def to_dict(self) -> Dict[str, str]:
|
||||||
return {
|
return {
|
||||||
"connection_id": self.connection_id,
|
"connection_id": self.connection_id,
|
||||||
"connected_at": self.connected_at.strftime("%Y-%m-%dT%H:%M:%S%z")
|
"connected_at": self.connected_at.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||||
}
|
}
|
||||||
|
|
||||||
async def activate(self) -> None:
|
async def activate(self) -> None:
|
||||||
|
@ -169,7 +172,9 @@ class Session(object):
|
||||||
try:
|
try:
|
||||||
await self.websocket.send(json.dumps({"kind": "DEACTIVATED"}))
|
await self.websocket.send(json.dumps({"kind": "DEACTIVATED"}))
|
||||||
except websockets.exceptions.ConnectionClosed:
|
except websockets.exceptions.ConnectionClosed:
|
||||||
print(self.connection_id, "not sending DEACTIVATED as it's already closed")
|
print(
|
||||||
|
self.connection_id, "not sending DEACTIVATED as it's already closed"
|
||||||
|
)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def end(self) -> None:
|
async def end(self) -> None:
|
||||||
|
@ -189,13 +194,15 @@ class Session(object):
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.websocket is not None
|
self.websocket is not None
|
||||||
and self.websocket.state == websockets.protocol.State.OPEN
|
and self.websocket.state == websockets.connection.State.OPEN
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
await self.websocket.send(json.dumps({"kind": "DIED"}))
|
await self.websocket.send(json.dumps({"kind": "DIED"}))
|
||||||
await self.websocket.close(1008)
|
await self.websocket.close(1008)
|
||||||
except websockets.exceptions.ConnectionClosed:
|
except websockets.exceptions.ConnectionClosed:
|
||||||
print(self.connection_id, "socket already closed, no died message")
|
print(
|
||||||
|
self.connection_id, "socket already closed, no died message"
|
||||||
|
)
|
||||||
|
|
||||||
if self.connection_id in active_sessions:
|
if self.connection_id in active_sessions:
|
||||||
print(self.connection_id, "removing from active_sessions")
|
print(self.connection_id, "removing from active_sessions")
|
||||||
|
@ -253,7 +260,10 @@ class Session(object):
|
||||||
|
|
||||||
@track.on("ended") # type: ignore
|
@track.on("ended") # type: ignore
|
||||||
async def on_ended() -> None:
|
async def on_ended() -> None:
|
||||||
print(self.connection_id, "Ending due to {} track end".format(track.kind))
|
print(
|
||||||
|
self.connection_id,
|
||||||
|
"Ending due to {} track end".format(track.kind),
|
||||||
|
)
|
||||||
await self.end()
|
await self.end()
|
||||||
|
|
||||||
write_ob_status(True)
|
write_ob_status(True)
|
||||||
|
@ -314,7 +324,9 @@ class Session(object):
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def connect(self, websocket: websockets.WebSocketServerProtocol) -> None:
|
async def connect(
|
||||||
|
self, websocket: websockets.server.WebSocketServerProtocol
|
||||||
|
) -> None:
|
||||||
global active_sessions
|
global active_sessions
|
||||||
|
|
||||||
active_sessions[self.connection_id] = self
|
active_sessions[self.connection_id] = self
|
||||||
|
@ -326,7 +338,13 @@ class Session(object):
|
||||||
print(self.connection_id, "Obtained ICE")
|
print(self.connection_id, "Obtained ICE")
|
||||||
sentry_sdk.set_context("session", {"session_id": self.connection_id})
|
sentry_sdk.set_context("session", {"session_id": self.connection_id})
|
||||||
await websocket.send(
|
await websocket.send(
|
||||||
json.dumps({"kind": "HELLO", "connectionId": self.connection_id, "iceServers": ice_config})
|
json.dumps(
|
||||||
|
{
|
||||||
|
"kind": "HELLO",
|
||||||
|
"connectionId": self.connection_id,
|
||||||
|
"iceServers": ice_config,
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -350,7 +368,9 @@ class Session(object):
|
||||||
await self.end()
|
await self.end()
|
||||||
|
|
||||||
|
|
||||||
async def serve(websocket: websockets.WebSocketServerProtocol, path: str) -> None:
|
async def serve(
|
||||||
|
websocket: websockets.server.WebSocketServerProtocol, path: str
|
||||||
|
) -> None:
|
||||||
if path == "/stream":
|
if path == "/stream":
|
||||||
session = Session()
|
session = Session()
|
||||||
await session.connect(websocket)
|
await session.connect(websocket)
|
||||||
|
@ -358,11 +378,15 @@ async def serve(websocket: websockets.WebSocketServerProtocol, path: str) -> Non
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
start_server = websockets.serve(
|
start_server = websockets.server.serve(
|
||||||
serve, host=None, port=int(config.get("shittyserver", "websocket_port"))
|
serve, host=None, port=int(config.get("shittyserver", "websocket_port"))
|
||||||
)
|
)
|
||||||
|
|
||||||
print("Shittyserver WS starting on port {}.".format(config.get("shittyserver", "websocket_port")))
|
print(
|
||||||
|
"Shittyserver WS starting on port {}.".format(
|
||||||
|
config.get("shittyserver", "websocket_port")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def telnet_server(
|
async def telnet_server(
|
||||||
|
@ -444,7 +468,11 @@ async def run_telnet_server() -> None:
|
||||||
|
|
||||||
jack.activate()
|
jack.activate()
|
||||||
|
|
||||||
print("Shittyserver TELNET starting on port {}".format(config.get("shittyserver", "telnet_port")))
|
print(
|
||||||
|
"Shittyserver TELNET starting on port {}".format(
|
||||||
|
config.get("shittyserver", "telnet_port")
|
||||||
|
)
|
||||||
|
)
|
||||||
asyncio.get_event_loop().run_until_complete(notify_mattserver_about_sessions())
|
asyncio.get_event_loop().run_until_complete(notify_mattserver_about_sessions())
|
||||||
|
|
||||||
asyncio.get_event_loop().run_until_complete(
|
asyncio.get_event_loop().run_until_complete(
|
||||||
|
|
102
stateserver.py
102
stateserver.py
|
@ -25,7 +25,11 @@ SUSTAINER_AUTONEWS = config.get("stateserver", "sustainer_autonews") == "True"
|
||||||
|
|
||||||
def do_ws_srv_telnet(source: str) -> None:
|
def do_ws_srv_telnet(source: str) -> None:
|
||||||
HOST = "localhost"
|
HOST = "localhost"
|
||||||
print("telnet {} {} SEL {}".format(HOST, config.get("shittyserver", "telnet_port"), source))
|
print(
|
||||||
|
"telnet {} {} SEL {}".format(
|
||||||
|
HOST, config.get("shittyserver", "telnet_port"), source
|
||||||
|
)
|
||||||
|
)
|
||||||
tn = Telnet(HOST, int(config.get("shittyserver", "telnet_port")))
|
tn = Telnet(HOST, int(config.get("shittyserver", "telnet_port")))
|
||||||
tn.write(b"SEL " + str.encode(source) + b"\n")
|
tn.write(b"SEL " + str.encode(source) + b"\n")
|
||||||
try:
|
try:
|
||||||
|
@ -45,7 +49,7 @@ def genPayload(payload: Any) -> Any:
|
||||||
|
|
||||||
|
|
||||||
def myradioApiRequest(url: str) -> Any:
|
def myradioApiRequest(url: str) -> Any:
|
||||||
res = requests.get('https://ury.org.uk/api/v2/' + url + '?api_key=' + api_key)
|
res = requests.get("https://ury.org.uk/api/v2/" + url + "?api_key=" + api_key)
|
||||||
if res.ok:
|
if res.ok:
|
||||||
return res.json()["payload"]
|
return res.json()["payload"]
|
||||||
else:
|
else:
|
||||||
|
@ -85,7 +89,8 @@ wsSessions: Dict[str, Dict[str, str]] = {}
|
||||||
def getCurrentShowConnection() -> Optional[Connection]:
|
def getCurrentShowConnection() -> Optional[Connection]:
|
||||||
for connection in connections:
|
for connection in connections:
|
||||||
if (connection["startTimestamp"] <= datetime.datetime.now().timestamp()) and (
|
if (connection["startTimestamp"] <= datetime.datetime.now().timestamp()) and (
|
||||||
connection["endTimestamp"] >= getNextHourTimestamp()):
|
connection["endTimestamp"] >= getNextHourTimestamp()
|
||||||
|
):
|
||||||
return connection
|
return connection
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -159,11 +164,15 @@ def stateDecider() -> Dict[str, Any]:
|
||||||
newSelSource = currentConnection["sourceid"]
|
newSelSource = currentConnection["sourceid"]
|
||||||
newWSSource = currentConnection["wsid"]
|
newWSSource = currentConnection["wsid"]
|
||||||
elif SUSTAINER_AUTONEWS:
|
elif SUSTAINER_AUTONEWS:
|
||||||
print("There's no show on currently, so we're going to AutoNEWS on sustainer")
|
print(
|
||||||
|
"There's no show on currently, so we're going to AutoNEWS on sustainer"
|
||||||
|
)
|
||||||
# Jukebox -> NEWS -> Jukebox
|
# Jukebox -> NEWS -> Jukebox
|
||||||
newSelSource = SOURCE_JUKEBOX
|
newSelSource = SOURCE_JUKEBOX
|
||||||
else:
|
else:
|
||||||
print("There's no show on currently, but AutoNews on sustainer is disabled, so don't do news")
|
print(
|
||||||
|
"There's no show on currently, but AutoNews on sustainer is disabled, so don't do news"
|
||||||
|
)
|
||||||
# Jukebox -> Jukebox
|
# Jukebox -> Jukebox
|
||||||
newSelSource = SOURCE_JUKEBOX
|
newSelSource = SOURCE_JUKEBOX
|
||||||
switchAudioAtMin = 0
|
switchAudioAtMin = 0
|
||||||
|
@ -173,32 +182,27 @@ def stateDecider() -> Dict[str, Any]:
|
||||||
"autoNews": willRunAutoNews,
|
"autoNews": willRunAutoNews,
|
||||||
"switchAudioAtMin": switchAudioAtMin,
|
"switchAudioAtMin": switchAudioAtMin,
|
||||||
"selSource": newSelSource,
|
"selSource": newSelSource,
|
||||||
"wsSource": newWSSource
|
"wsSource": newWSSource,
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextState
|
return nextState
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/v1/status', methods=['GET'])
|
@app.route("/api/v1/status", methods=["GET"])
|
||||||
def get_status() -> Any:
|
def get_status() -> Any:
|
||||||
print(getNextHourTimestamp())
|
print(getNextHourTimestamp())
|
||||||
global connections
|
global connections
|
||||||
cleanOldConnections()
|
cleanOldConnections()
|
||||||
return genPayload(
|
return genPayload({"connections": connections, "wsSessions": wsSessions})
|
||||||
{
|
|
||||||
"connections": connections,
|
|
||||||
"wsSessions": wsSessions
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/v1/nextTransition', methods=['GET'])
|
@app.route("/api/v1/nextTransition", methods=["GET"])
|
||||||
def get_next_transition() -> Any:
|
def get_next_transition() -> Any:
|
||||||
cleanOldConnections()
|
cleanOldConnections()
|
||||||
return genPayload(stateDecider())
|
return genPayload(stateDecider())
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/v1/cancelTimeslot', methods=['POST'])
|
@app.route("/api/v1/cancelTimeslot", methods=["POST"])
|
||||||
def post_cancelCheck() -> Any:
|
def post_cancelCheck() -> Any:
|
||||||
global connections
|
global connections
|
||||||
content = request.json
|
content = request.json
|
||||||
|
@ -215,7 +219,13 @@ def post_cancelCheck() -> Any:
|
||||||
# but don't kill it during the news, or after the end time, to avoid unexpected jukeboxing
|
# but don't kill it during the news, or after the end time, to avoid unexpected jukeboxing
|
||||||
now = datetime.datetime.now().timestamp()
|
now = datetime.datetime.now().timestamp()
|
||||||
if now < (currentShow["endTimestamp"] - 15):
|
if now < (currentShow["endTimestamp"] - 15):
|
||||||
print("Jukeboxing due to {}'s ({}, {}) cancellation".format(currentShow["connid"], currentShow["timeslotid"], currentShow["wsid"]))
|
print(
|
||||||
|
"Jukeboxing due to {}'s ({}, {}) cancellation".format(
|
||||||
|
currentShow["connid"],
|
||||||
|
currentShow["timeslotid"],
|
||||||
|
currentShow["wsid"],
|
||||||
|
)
|
||||||
|
)
|
||||||
do_ws_srv_telnet("NUL")
|
do_ws_srv_telnet("NUL")
|
||||||
subprocess.Popen(["sel", str(SOURCE_JUKEBOX)])
|
subprocess.Popen(["sel", str(SOURCE_JUKEBOX)])
|
||||||
|
|
||||||
|
@ -227,7 +237,7 @@ def post_cancelCheck() -> Any:
|
||||||
return genFail("Connection not found.")
|
return genFail("Connection not found.")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/v1/registerTimeslot', methods=['POST'])
|
@app.route("/api/v1/registerTimeslot", methods=["POST"])
|
||||||
def post_registerCheck() -> Any:
|
def post_registerCheck() -> Any:
|
||||||
global connections
|
global connections
|
||||||
|
|
||||||
|
@ -274,7 +284,11 @@ def post_registerCheck() -> Any:
|
||||||
for conn in connections:
|
for conn in connections:
|
||||||
if content["timeslotid"] == conn["timeslotid"]:
|
if content["timeslotid"] == conn["timeslotid"]:
|
||||||
# they've already registered, return the existing session
|
# they've already registered, return the existing session
|
||||||
print("found existing connection {} for {}".format(conn["connid"], conn["timeslotid"]))
|
print(
|
||||||
|
"found existing connection {} for {}".format(
|
||||||
|
conn["connid"], conn["timeslotid"]
|
||||||
|
)
|
||||||
|
)
|
||||||
connection = conn
|
connection = conn
|
||||||
# make sure we update their wsID
|
# make sure we update their wsID
|
||||||
if "wsid" in content:
|
if "wsid" in content:
|
||||||
|
@ -282,37 +296,50 @@ def post_registerCheck() -> Any:
|
||||||
|
|
||||||
new_connection = False
|
new_connection = False
|
||||||
if connection is None:
|
if connection is None:
|
||||||
|
|
||||||
new_connection = True
|
new_connection = True
|
||||||
|
|
||||||
if start_time - now_time > datetime.timedelta(hours=1):
|
if start_time - now_time > datetime.timedelta(hours=1):
|
||||||
return genFail("This show too far away, please try again within an hour of starting your show.")
|
return genFail(
|
||||||
|
"This show too far away, please try again within an hour of starting your show."
|
||||||
|
)
|
||||||
|
|
||||||
if start_time + duration_time < now_time:
|
if start_time + duration_time < now_time:
|
||||||
return genFail("This show has already ended.")
|
return genFail("This show has already ended.")
|
||||||
|
|
||||||
if start_time - datetime.timedelta(minutes=1) < now_time < start_time + datetime.timedelta(minutes=2):
|
if (
|
||||||
return genFail("You registered too late. Please re-register after the news.")
|
start_time - datetime.timedelta(minutes=1)
|
||||||
|
< now_time
|
||||||
|
< start_time + datetime.timedelta(minutes=2)
|
||||||
|
):
|
||||||
|
return genFail(
|
||||||
|
"You registered too late. Please re-register after the news."
|
||||||
|
)
|
||||||
|
|
||||||
random.seed(a=timeslot["timeslot_id"], version=2)
|
random.seed(a=timeslot["timeslot_id"], version=2)
|
||||||
connection = {
|
connection = {
|
||||||
"connid": random.randint(0, 100000000), # TODO: this is horrible. I'll sort this later.
|
"connid": random.randint(
|
||||||
|
0, 100000000
|
||||||
|
), # TODO: this is horrible. I'll sort this later.
|
||||||
"timeslotid": timeslot["timeslot_id"],
|
"timeslotid": timeslot["timeslot_id"],
|
||||||
"startTimestamp": int(start_time.timestamp()),
|
"startTimestamp": int(start_time.timestamp()),
|
||||||
"endTimestamp": int(end_time.timestamp()),
|
"endTimestamp": int(end_time.timestamp()),
|
||||||
"sourceid": content["sourceid"],
|
"sourceid": content["sourceid"],
|
||||||
'autoNewsBeginning': True,
|
"autoNewsBeginning": True,
|
||||||
'autoNewsMiddle': True,
|
"autoNewsMiddle": True,
|
||||||
'autoNewsEnd': True,
|
"autoNewsEnd": True,
|
||||||
'wsid': content["wsid"]
|
"wsid": content["wsid"],
|
||||||
}
|
}
|
||||||
|
|
||||||
if start_time + datetime.timedelta(minutes=2) < now_time:
|
if start_time + datetime.timedelta(minutes=2) < now_time:
|
||||||
if connection["wsid"] is not None:
|
if connection["wsid"] is not None:
|
||||||
# they're late, bring them live now
|
# they're late, bring them live now
|
||||||
print("({}, {}) late, bringing on air now".format(connection["connid"], connection["wsid"]))
|
print(
|
||||||
|
"({}, {}) late, bringing on air now".format(
|
||||||
|
connection["connid"], connection["wsid"]
|
||||||
|
)
|
||||||
|
)
|
||||||
do_ws_srv_telnet(connection["wsid"])
|
do_ws_srv_telnet(connection["wsid"])
|
||||||
subprocess.Popen(['sel', '5'])
|
subprocess.Popen(["sel", "5"])
|
||||||
|
|
||||||
assert connection is not None
|
assert connection is not None
|
||||||
if new_connection:
|
if new_connection:
|
||||||
|
@ -322,7 +349,7 @@ def post_registerCheck() -> Any:
|
||||||
return genPayload(connection)
|
return genPayload(connection)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/v1/changeTimeslot', methods=['POST'])
|
@app.route("/api/v1/changeTimeslot", methods=["POST"])
|
||||||
def post_settingsCheck() -> Any:
|
def post_settingsCheck() -> Any:
|
||||||
global connections
|
global connections
|
||||||
content = request.json
|
content = request.json
|
||||||
|
@ -349,11 +376,12 @@ def post_settingsCheck() -> Any:
|
||||||
return genFail("No connection found.")
|
return genFail("No connection found.")
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/v1/updateWSSessions', methods=['POST'])
|
@app.route("/api/v1/updateWSSessions", methods=["POST"])
|
||||||
def post_wsSessions() -> Any:
|
def post_wsSessions() -> Any:
|
||||||
global connections
|
global connections
|
||||||
global wsSessions
|
global wsSessions
|
||||||
content = request.json
|
content = request.json
|
||||||
|
assert content is not None
|
||||||
# if not content:
|
# if not content:
|
||||||
# return genFail("No parameters provided.")
|
# return genFail("No parameters provided.")
|
||||||
oldSessions = wsSessions
|
oldSessions = wsSessions
|
||||||
|
@ -378,9 +406,13 @@ def post_wsSessions() -> Any:
|
||||||
if conn["wsid"] in wsids_to_add:
|
if conn["wsid"] in wsids_to_add:
|
||||||
if conn["startTimestamp"] + 120 < datetime.datetime.now().timestamp():
|
if conn["startTimestamp"] + 120 < datetime.datetime.now().timestamp():
|
||||||
# they're late, bring them on air now
|
# they're late, bring them on air now
|
||||||
print("({}, {}) late, bringing on air now".format(conn["connid"], conn["wsid"]))
|
print(
|
||||||
|
"({}, {}) late, bringing on air now".format(
|
||||||
|
conn["connid"], conn["wsid"]
|
||||||
|
)
|
||||||
|
)
|
||||||
do_ws_srv_telnet(conn["wsid"])
|
do_ws_srv_telnet(conn["wsid"])
|
||||||
subprocess.Popen(['sel', '5'])
|
subprocess.Popen(["sel", "5"])
|
||||||
|
|
||||||
if conn["wsid"] in wsids_to_remove:
|
if conn["wsid"] in wsids_to_remove:
|
||||||
print("({}, {}) gone".format(conn["connid"], conn["wsid"]))
|
print("({}, {}) gone".format(conn["connid"], conn["wsid"]))
|
||||||
|
@ -394,10 +426,10 @@ def post_wsSessions() -> Any:
|
||||||
now = datetime.datetime.now().timestamp()
|
now = datetime.datetime.now().timestamp()
|
||||||
if now < (currentShow["endTimestamp"] - 15):
|
if now < (currentShow["endTimestamp"] - 15):
|
||||||
print("jukeboxing due to their disappearance...")
|
print("jukeboxing due to their disappearance...")
|
||||||
subprocess.Popen(['sel', str(SOURCE_JUKEBOX)])
|
subprocess.Popen(["sel", str(SOURCE_JUKEBOX)])
|
||||||
do_ws_srv_telnet("NUL")
|
do_ws_srv_telnet("NUL")
|
||||||
return genPayload("Thx, K, bye.")
|
return genPayload("Thx, K, bye.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
app.run(debug=True, host="0.0.0.0")
|
app.run(debug=True, host="0.0.0.0")
|
||||||
|
|
Loading…
Reference in a new issue