From 1911b2de0b3ccc5fa9864878311ab19275c1be42 Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 05:37:40 +0100 Subject: [PATCH 01/16] mal n bisschen mit fastapi rumspielen --- application/api/__init__.py | 0 application/api/commands.py | 23 ++++++++ application/api/config.py | 28 +++++++++ application/api/connection_manager.py | 25 +++++++++ application/api/main.py | 81 +++++++++++++++++++++++++++ 5 files changed, 157 insertions(+) create mode 100644 application/api/__init__.py create mode 100644 application/api/commands.py create mode 100644 application/api/config.py create mode 100644 application/api/connection_manager.py create mode 100644 application/api/main.py diff --git a/application/api/__init__.py b/application/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/application/api/commands.py b/application/api/commands.py new file mode 100644 index 0000000..98482c1 --- /dev/null +++ b/application/api/commands.py @@ -0,0 +1,23 @@ +from application.canvas.canvas import Canvas +from application.color import get_color_from_index + + +async def request_pixel(canvas: Canvas): + pixel = await canvas.pop_mismatched_pixel() + if pixel: + return { + 'x': pixel['x'], + 'y': pixel['y'], + 'color': get_color_from_index(pixel['color_index']).value['id'] + } + + +# no-op +async def handshake(): + pass + + +async def ping(): + return { + 'pong': True + } diff --git a/application/api/config.py b/application/api/config.py new file mode 100644 index 0000000..8c40257 --- /dev/null +++ b/application/api/config.py @@ -0,0 +1,28 @@ +from pydantic import BaseSettings + + +class ServerConfig(BaseSettings): + host: str = '0.0.0.0' + port: int = 5000 + remote_config_url: str = 'https://placede.github.io/pixel/pixel.json' + canvas_update_interval: int = 10 + + +def get_graphql_config(): + return { + "id": "1", + "type": "start", + "payload": { + "variables": { + "input": { + "channel": { + "teamOwner": "AFD2022", + "category": "CONFIG", + } + } + }, + "extensions": {}, + "operationName": "configuration", + "query": "subscription configuration($input: SubscribeInput!) {\n subscribe(input: $input) {\n id\n ... on BasicMessage {\n data {\n __typename\n ... on ConfigurationMessageData {\n colorPalette {\n colors {\n hex\n index\n __typename\n }\n __typename\n }\n canvasConfigurations {\n index\n dx\n dy\n __typename\n }\n canvasWidth\n canvasHeight\n __typename\n }\n }\n __typename\n }\n __typename\n }\n}\n", + }, + } \ No newline at end of file diff --git a/application/api/connection_manager.py b/application/api/connection_manager.py new file mode 100644 index 0000000..e7ca333 --- /dev/null +++ b/application/api/connection_manager.py @@ -0,0 +1,25 @@ +from typing import List + +from fastapi import WebSocket + + +class ConnectionManager: + def __init__(self): + self.active_connections: List[WebSocket] = [] + + async def connect(self, websocket: WebSocket): + await websocket.accept() + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def send_message_to(self, message: str, websocket: WebSocket): + await websocket.send_text(message) + + async def broadcast(self, message: str): + for connection in self.active_connections: + await connection.send_text(message) + + def connection_count(self): + return len(self.active_connections) diff --git a/application/api/main.py b/application/api/main.py new file mode 100644 index 0000000..0f5e8af --- /dev/null +++ b/application/api/main.py @@ -0,0 +1,81 @@ +import asyncio +from functools import lru_cache + +from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, BackgroundTasks +from fastapi.responses import JSONResponse + +from application.api.commands import request_pixel, handshake, ping +from application.canvas.canvas import Canvas +from application.target_configuration.target_configuration import TargetConfiguration +from connection_manager import ConnectionManager +from config import ServerConfig + +app = FastAPI() +connection_manager = ConnectionManager() + + +@lru_cache +def get_config(): + return ServerConfig() + + +async def update_canvas(monalisa: Canvas): + if await monalisa.update_board(): + await monalisa.calculate_mismatched_pixels() + await asyncio.sleep(30) + + +canvas: Canvas + + +@app.on_event('startup') +async def startup(background_tasks: BackgroundTasks, config: ServerConfig = Depends(get_config)): + global canvas + + target_config = TargetConfiguration(config) + canvas = Canvas(target_config) + + background_tasks.add_task(update_canvas, canvas) + + +@app.websocket('/live') +async def live_endpoint(websocket: WebSocket): + await connection_manager.connect(websocket) + + try: + while True: + data = await websocket.receive_json() + if 'operation' in data: + op = data['operation'] + response = None + + if op == 'request-pixel': + response = format_response( + 'place-pixel', + data.get('user', ''), + await request_pixel(canvas) + ) + elif op == 'handshake': + pass + elif op == 'ping': + response = ping() + + if response is not None: + await websocket.send_json(response) + except WebSocketDisconnect: + connection_manager.disconnect(websocket) + + +@app.get('/users/count') +async def get_users_count(): + return JSONResponse(content={ + 'count': connection_manager.connection_count() + }) + + +def format_response(op: str, user: str, data: dict): + return { + 'operation': op, + 'data': data, + 'user': user + } From 8eccc0aa3f15a86ef620514ef6279f96ad2c33e7 Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 06:05:28 +0100 Subject: [PATCH 02/16] vroooom --- .gitignore | 2 + application/api/main.py | 81 ---------------- application/frontend/frontend.py | 4 +- .../target_configuration.py | 11 ++- main.py | 93 +++++++++++++------ requirements.txt | 10 +- 6 files changed, 82 insertions(+), 119 deletions(-) delete mode 100644 application/api/main.py diff --git a/.gitignore b/.gitignore index 65b49c1..efbb171 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ ./old_bot +.idea/ +__pycache__ diff --git a/application/api/main.py b/application/api/main.py deleted file mode 100644 index 0f5e8af..0000000 --- a/application/api/main.py +++ /dev/null @@ -1,81 +0,0 @@ -import asyncio -from functools import lru_cache - -from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends, BackgroundTasks -from fastapi.responses import JSONResponse - -from application.api.commands import request_pixel, handshake, ping -from application.canvas.canvas import Canvas -from application.target_configuration.target_configuration import TargetConfiguration -from connection_manager import ConnectionManager -from config import ServerConfig - -app = FastAPI() -connection_manager = ConnectionManager() - - -@lru_cache -def get_config(): - return ServerConfig() - - -async def update_canvas(monalisa: Canvas): - if await monalisa.update_board(): - await monalisa.calculate_mismatched_pixels() - await asyncio.sleep(30) - - -canvas: Canvas - - -@app.on_event('startup') -async def startup(background_tasks: BackgroundTasks, config: ServerConfig = Depends(get_config)): - global canvas - - target_config = TargetConfiguration(config) - canvas = Canvas(target_config) - - background_tasks.add_task(update_canvas, canvas) - - -@app.websocket('/live') -async def live_endpoint(websocket: WebSocket): - await connection_manager.connect(websocket) - - try: - while True: - data = await websocket.receive_json() - if 'operation' in data: - op = data['operation'] - response = None - - if op == 'request-pixel': - response = format_response( - 'place-pixel', - data.get('user', ''), - await request_pixel(canvas) - ) - elif op == 'handshake': - pass - elif op == 'ping': - response = ping() - - if response is not None: - await websocket.send_json(response) - except WebSocketDisconnect: - connection_manager.disconnect(websocket) - - -@app.get('/users/count') -async def get_users_count(): - return JSONResponse(content={ - 'count': connection_manager.connection_count() - }) - - -def format_response(op: str, user: str, data: dict): - return { - 'operation': op, - 'data': data, - 'user': user - } diff --git a/application/frontend/frontend.py b/application/frontend/frontend.py index 979b131..3076c02 100644 --- a/application/frontend/frontend.py +++ b/application/frontend/frontend.py @@ -3,6 +3,6 @@ app = FastAPI() -@app.get("/") +@app.get("/users/count") async def user_count(): - return + return dict(count=0); \ No newline at end of file diff --git a/application/target_configuration/target_configuration.py b/application/target_configuration/target_configuration.py index 3cbd37e..829a2f1 100644 --- a/application/target_configuration/target_configuration.py +++ b/application/target_configuration/target_configuration.py @@ -1,12 +1,11 @@ import json import random import time -import asyncio import aiohttp -import requests from application import static_stuff +from application.api.config import ServerConfig UPDATE_INTERVAL = 60 @@ -17,16 +16,18 @@ class TargetConfiguration: Is refreshed periodically by pulling it from a server """ - def __init__(self): + def __init__(self, settings: ServerConfig): self.last_update = 0 self.config = {} self.pixels = [] + self.settings = settings + print(settings) async def get_config(self, ignore_time: bool = False): """ Get the config and refresh it first if necessary """ - if self.last_update + UPDATE_INTERVAL < time.time() and not ignore_time: + if self.last_update + self.settings.canvas_update_interval < time.time() and not ignore_time: await self.refresh_config() self.last_update = time.time() @@ -47,7 +48,7 @@ async def refresh_config(self): """ print("\nRefreshing target configuration...\n") - url = static_stuff.target_configuration_url + url = self.settings.remote_config_url if url.startswith("http"): async with aiohttp.ClientSession() as session: diff --git a/main.py b/main.py index b22d750..ac8e331 100644 --- a/main.py +++ b/main.py @@ -1,37 +1,76 @@ -# library imports - -from __future__ import annotations - import asyncio +from functools import lru_cache + +from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends +from fastapi.responses import JSONResponse -# our imports +from application.api.commands import request_pixel, ping +from application.api.connection_manager import ConnectionManager +from application.api.config import ServerConfig +from application.canvas.canvas import Canvas from application.target_configuration.target_configuration import TargetConfiguration -from application.canvas import canvas -from application.connections import websocket_server -# from fastapi import +app = FastAPI() +connection_manager = ConnectionManager() + + +config = ServerConfig() +canvas: Canvas + + +async def update_canvas(monalisa: Canvas): + if await monalisa.update_board(): + await monalisa.calculate_mismatched_pixels() + await asyncio.sleep(30) + + +@app.on_event('startup') +async def startup(): + global canvas + target_config = TargetConfiguration(config) + canvas = Canvas(target_config) + print('Scheduling canvas update') + asyncio.create_task(update_canvas(canvas)) + + +@app.websocket('/live') +async def live_endpoint(websocket: WebSocket): + await connection_manager.connect(websocket) + + try: + while True: + data = await websocket.receive_json() + if 'operation' in data: + op = data['operation'] + response = None + if op == 'request-pixel': + response = format_response( + 'place-pixel', + data.get('user', ''), + await request_pixel(canvas) + ) + elif op == 'handshake': + pass + elif op == 'ping': + response = ping() + if response is not None: + await websocket.send_json(response) + except WebSocketDisconnect: + connection_manager.disconnect(websocket) -# create target_configuration -target_configuration = TargetConfiguration() -# manage r/place canvas -monalisa = canvas.Canvas(target_configuration) -# server for remote bot connections -server = websocket_server.Server(monalisa, {"host": "0.0.0.0", "port": 8080}) -async def main_loop(): - while True: - # update board if it needs to be updated - if await monalisa.update_board(): - await monalisa.calculate_mismatched_pixels() - await asyncio.sleep(30) +@app.get('/users/count') +async def get_users_count(): + return JSONResponse(content={ + 'count': connection_manager.connection_count() + }) -looper = asyncio.new_event_loop() -looper.create_task(main_loop()) -looper.create_task(server.run(looper)) -try: - looper.run_forever() -except (KeyboardInterrupt, RuntimeError): - print("Exiting!") +def format_response(op: str, user: str, data: dict): + return { + 'operation': op, + 'data': data, + 'user': user + } diff --git a/requirements.txt b/requirements.txt index 64d5164..bd2f52f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,9 @@ -fastapi +fastapi~=0.75.1 uvicorn -pillow +pillow~=9.1.0 requests -websockets +websockets~=10.2 websocket-client -aiohttp +aiohttp~=3.8.1 + +pydantic~=1.9.0 \ No newline at end of file From 00ee4d51b4965e3561b53b7986ed656d3c9fe89c Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 06:07:07 +0100 Subject: [PATCH 03/16] Docker? Never heard of her --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f7b9476..c241e4e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,4 @@ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt COPY ./application /code/application COPY ./main.py /code/ -ENTRYPOINT ["python", "/code/main.py"] +ENTRYPOINT ["uvicorn", "main:app"] From 3b8a4568a99e666e0294afd01b9949c702a7b3ff Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 06:13:09 +0100 Subject: [PATCH 04/16] bisserl debugging muss sein --- main.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main.py b/main.py index ac8e331..f45619a 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import asyncio +import json from functools import lru_cache from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends @@ -40,6 +41,7 @@ async def live_endpoint(websocket: WebSocket): try: while True: data = await websocket.receive_json() + print(f'RX: {json.dumps(data)}') if 'operation' in data: op = data['operation'] response = None @@ -56,6 +58,7 @@ async def live_endpoint(websocket: WebSocket): response = ping() if response is not None: + print(f'TX: {json.dumps(response)}') await websocket.send_json(response) except WebSocketDisconnect: connection_manager.disconnect(websocket) From 0b89c928bcc3eca93987da9de6055f81b18c0e6a Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 06:13:56 +0100 Subject: [PATCH 05/16] =?UTF-8?q?host=3F=20port=3F=20n=C3=B6,=20macht=20uv?= =?UTF-8?q?icorn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/api/config.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/application/api/config.py b/application/api/config.py index 8c40257..506cddb 100644 --- a/application/api/config.py +++ b/application/api/config.py @@ -2,8 +2,6 @@ class ServerConfig(BaseSettings): - host: str = '0.0.0.0' - port: int = 5000 remote_config_url: str = 'https://placede.github.io/pixel/pixel.json' canvas_update_interval: int = 10 From 7db9a204a35f4c0b714e2770a797f53d4c0ba3a9 Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 06:42:50 +0100 Subject: [PATCH 06/16] connection count: overrated; bot count: activated --- application/api/commands.py | 2 ++ application/api/connection_manager.py | 8 ++++++++ main.py | 7 +++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/application/api/commands.py b/application/api/commands.py index 98482c1..8379958 100644 --- a/application/api/commands.py +++ b/application/api/commands.py @@ -21,3 +21,5 @@ async def ping(): return { 'pong': True } + + diff --git a/application/api/connection_manager.py b/application/api/connection_manager.py index e7ca333..ad00c92 100644 --- a/application/api/connection_manager.py +++ b/application/api/connection_manager.py @@ -6,13 +6,18 @@ class ConnectionManager: def __init__(self): self.active_connections: List[WebSocket] = [] + self.advertised_accounts: dict = {} async def connect(self, websocket: WebSocket): await websocket.accept() self.active_connections.append(websocket) + def set_advertised_accounts(self, websocket: WebSocket, count): + self.advertised_accounts[websocket] = count + def disconnect(self, websocket: WebSocket): self.active_connections.remove(websocket) + self.advertised_accounts.pop(websocket) async def send_message_to(self, message: str, websocket: WebSocket): await websocket.send_text(message) @@ -23,3 +28,6 @@ async def broadcast(self, message: str): def connection_count(self): return len(self.active_connections) + + def advertised_account_count(self): + return sum(self.advertised_accounts.values()) diff --git a/main.py b/main.py index f45619a..1d502f6 100644 --- a/main.py +++ b/main.py @@ -53,7 +53,9 @@ async def live_endpoint(websocket: WebSocket): await request_pixel(canvas) ) elif op == 'handshake': - pass + metadata = data.get('data', {}) + advertised_count = metadata.get('useraccounts', 1) + connection_manager.set_advertised_accounts(websocket, advertised_count) elif op == 'ping': response = ping() @@ -67,7 +69,8 @@ async def live_endpoint(websocket: WebSocket): @app.get('/users/count') async def get_users_count(): return JSONResponse(content={ - 'count': connection_manager.connection_count() + 'count': connection_manager.connection_count(), + 'advertised_accounts': connection_manager.advertised_account_count() }) From 56b7c77e16d595fcd2eaacd2d92853aa94dbf0b3 Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 06:47:13 +0100 Subject: [PATCH 07/16] Defaults sollen her! --- Dockerfile | 2 +- main.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index c241e4e..3a24e04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,4 @@ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt COPY ./application /code/application COPY ./main.py /code/ -ENTRYPOINT ["uvicorn", "main:app"] +ENTRYPOINT ["uvicorn", "main:app", "--host 0.0.0.0", "--port 5000"] diff --git a/main.py b/main.py index 1d502f6..15c771d 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,5 @@ import asyncio import json -from functools import lru_cache from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends from fastapi.responses import JSONResponse From b1452b564853fa2056c512a9b78c342f616f6840 Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 07:06:09 +0100 Subject: [PATCH 08/16] get-botcount soll her --- application/api/commands.py | 2 ++ main.py | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/application/api/commands.py b/application/api/commands.py index 8379958..70b2f2b 100644 --- a/application/api/commands.py +++ b/application/api/commands.py @@ -10,6 +10,8 @@ async def request_pixel(canvas: Canvas): 'y': pixel['y'], 'color': get_color_from_index(pixel['color_index']).value['id'] } + else: + return {} # no-op diff --git a/main.py b/main.py index 15c771d..e7ed559 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import asyncio +import hashlib import json from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends @@ -57,7 +58,9 @@ async def live_endpoint(websocket: WebSocket): connection_manager.set_advertised_accounts(websocket, advertised_count) elif op == 'ping': response = ping() - + # eigtl. durch /users/count deprecated + elif op == 'get-botcount' and password_check(data.get("pw", '')): + response = {'amount': connection_manager.advertised_account_count()} if response is not None: print(f'TX: {json.dumps(response)}') await websocket.send_json(response) @@ -79,3 +82,6 @@ def format_response(op: str, user: str, data: dict): 'data': data, 'user': user } + +def password_check(password): + return hashlib.sha3_512(password.encode()).hexdigest() == "bea976c455d292fdd15256d3263cb2b70f051337f134b0fa9678d5eb206b4c45ebd213694af9cf6118700fc8488809be9195c7eae44a882c6be519ba09b68e47" \ No newline at end of file From c49b6184688634919e71684a076979a1c9f3d45a Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 07:11:10 +0100 Subject: [PATCH 09/16] oops, docker kann keine leerzeichen --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 3a24e04..8c88462 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,4 @@ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt COPY ./application /code/application COPY ./main.py /code/ -ENTRYPOINT ["uvicorn", "main:app", "--host 0.0.0.0", "--port 5000"] +ENTRYPOINT ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"] From 997254f9814bb549a7efda0e49ce985de57e534d Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 08:29:22 +0100 Subject: [PATCH 10/16] ping ist doch nicht async... --- application/api/commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/api/commands.py b/application/api/commands.py index 70b2f2b..262bd6e 100644 --- a/application/api/commands.py +++ b/application/api/commands.py @@ -19,7 +19,7 @@ async def handshake(): pass -async def ping(): +def ping(): return { 'pong': True } From e01dbc8142a065408a0a617afc59884c70aa7e7c Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 08:56:13 +0100 Subject: [PATCH 11/16] alter code? weg damit :) --- application/canvas/canvas.py | 3 +- application/connections/__init__.py | 0 application/connections/websocket_server.py | 99 ------------------- application/frontend/__init__.py | 0 application/frontend/frontend.py | 8 -- application/static_stuff.py | 21 ---- .../target_configuration.py | 3 +- 7 files changed, 2 insertions(+), 132 deletions(-) delete mode 100644 application/connections/__init__.py delete mode 100644 application/connections/websocket_server.py delete mode 100644 application/frontend/__init__.py delete mode 100644 application/frontend/frontend.py delete mode 100644 application/static_stuff.py diff --git a/application/canvas/canvas.py b/application/canvas/canvas.py index c37d6df..f524d3c 100644 --- a/application/canvas/canvas.py +++ b/application/canvas/canvas.py @@ -10,7 +10,6 @@ from PIL import Image from application.color import get_matching_color, Color, get_color_from_index -from application.static_stuff import CANVAS_UPDATE_INTERVAL from application.target_configuration.target_configuration import TargetConfiguration BOARD_SIZE_X = 2000 @@ -95,7 +94,7 @@ async def update_board(self): """ Fetch the current state of the board/canvas for the requed areas """ - if self.last_update + CANVAS_UPDATE_INTERVAL >= time.time(): + if self.last_update + self.target_configuration.settings.canvas_update_interval >= time.time(): return False await self.update_access_token() diff --git a/application/connections/__init__.py b/application/connections/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/application/connections/websocket_server.py b/application/connections/websocket_server.py deleted file mode 100644 index cf3b774..0000000 --- a/application/connections/websocket_server.py +++ /dev/null @@ -1,99 +0,0 @@ -import asyncio -import hashlib -import json -import sys -import traceback -from typing import Dict, Any - -import websockets.server -from websockets import serve -from websockets.server import WebSocketServerProtocol - -from application.color import get_color_from_index -from application.canvas.canvas import Canvas - -DEFAULT_PORT = 5555 -DEFAULT_HOST = "localhost" - - -class Server: - """ - Websocket server, dieser managed die Verbindung zu den client Bots und teilt denen auf Anfrage neue Pixel zu. - """ - __slots__ = ("config", "port", "__server_loop", "__server", "host", "provider", "__client_count") - __client_count: int - config: Dict[str, Any] - port: int - host: str - provider: Canvas - - def __init__(self, provider: Canvas, config: Dict[str, Any]): - self.provider = provider - self.config = config - self.port = config.get("port", DEFAULT_PORT) - self.host = config.get("host", DEFAULT_HOST) - self.__server_loop = None - self.__server = None - self.__client_count = 0 - - async def run(self, looper: asyncio.AbstractEventLoop): - """ - erstellt den server und lässt diesen unendlich laufen. Sollte evtl. in einem eigenen Thread aufgerufen werden. - """ - async with serve(self.__handler, self.host, self.port): - while True: - await asyncio.sleep(1000000) - #await asyncio.Future() - - async def __handler(self, socket: WebSocketServerProtocol): - bot_count = 0 - - try: - # TODO: check for update availability. - - async for msg in socket: - req = json.loads(msg) - - if req.get("operation") == "request-pixel": - print(msg) - pixel = await self.provider.pop_mismatched_pixel() - if pixel: - data = { - "x": pixel["x"], - "y": pixel["y"], - "color": get_color_from_index(pixel["color_index"]).value["id"] - } - - await socket.send(json.dumps(Server.__wrap_data(data, req.get("user", "")))) - else: - await socket.send("{}") - - elif req.get("operation") == "handshake": - bot_count = abs(req["data"].get("useraccounts", 1)) - self.__client_count += bot_count - print(f"{bot_count} New Client(s) connected! New bot count: {self.__client_count}") - - elif req.get("operation") == "get-botcount" and hashlib.sha3_512(req.get("pw").encode()).hexdigest() == "bea976c455d292fdd15256d3263cb2b70f051337f134b0fa9678d5eb206b4c45ebd213694af9cf6118700fc8488809be9195c7eae44a882c6be519ba09b68e47": - await socket.send(json.dumps({"amount": self.__client_count})) - - elif req.get("operation") == "ping": - await socket.send(json.dumps({"pong": True})) - - except websockets.ConnectionClosed: - pass - except Exception as e: - traceback.print_exception(*sys.exc_info()) - finally: - self.__client_count -= bot_count - print(f"{bot_count} Client(s) lost! New bot count: {self.__client_count}") - - @staticmethod - def __wrap_data(data: dict, user: str, operation: str = "place-pixel") -> dict: - return { - "operation": operation, - "data": data, - "user": user - } - - def get_bot_count(self) -> int: - return self.__client_count diff --git a/application/frontend/__init__.py b/application/frontend/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/application/frontend/frontend.py b/application/frontend/frontend.py deleted file mode 100644 index 3076c02..0000000 --- a/application/frontend/frontend.py +++ /dev/null @@ -1,8 +0,0 @@ -from fastapi import FastAPI - -app = FastAPI() - - -@app.get("/users/count") -async def user_count(): - return dict(count=0); \ No newline at end of file diff --git a/application/static_stuff.py b/application/static_stuff.py deleted file mode 100644 index 5cebab5..0000000 --- a/application/static_stuff.py +++ /dev/null @@ -1,21 +0,0 @@ -GRAPHQL_GET_CONFIG = { - "id": "1", - "type": "start", - "payload": { - "variables": { - "input": { - "channel": { - "teamOwner": "AFD2022", - "category": "CONFIG", - } - } - }, - "extensions": {}, - "operationName": "configuration", - "query": "subscription configuration($input: SubscribeInput!) {\n subscribe(input: $input) {\n id\n ... on BasicMessage {\n data {\n __typename\n ... on ConfigurationMessageData {\n colorPalette {\n colors {\n hex\n index\n __typename\n }\n __typename\n }\n canvasConfigurations {\n index\n dx\n dy\n __typename\n }\n canvasWidth\n canvasHeight\n __typename\n }\n }\n __typename\n }\n __typename\n }\n}\n", - }, -} - - -target_configuration_url = "https://placede.github.io/pixel/pixel.json" -CANVAS_UPDATE_INTERVAL = 10 diff --git a/application/target_configuration/target_configuration.py b/application/target_configuration/target_configuration.py index 829a2f1..02f4ddd 100644 --- a/application/target_configuration/target_configuration.py +++ b/application/target_configuration/target_configuration.py @@ -4,7 +4,6 @@ import aiohttp -from application import static_stuff from application.api.config import ServerConfig UPDATE_INTERVAL = 60 @@ -55,7 +54,7 @@ async def refresh_config(self): async with session.get(url) as resp: json_data = await resp.json() if resp.status != 200: - print("Error: Could not get config file from " + static_stuff.target_configuration_url) + print("Error: Could not get config file from " + self.settings.remote_config_url) return # parse config file From 68b96ef097488d8a9ecc1e1c1a36989533dec885 Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 08:57:58 +0100 Subject: [PATCH 12/16] monalisa soll arbeiten --- main.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index e7ed559..b882a2e 100644 --- a/main.py +++ b/main.py @@ -20,9 +20,13 @@ async def update_canvas(monalisa: Canvas): - if await monalisa.update_board(): - await monalisa.calculate_mismatched_pixels() - await asyncio.sleep(30) + while True: + try: + if await monalisa.update_board(): + await monalisa.calculate_mismatched_pixels() + await asyncio.sleep(30) + finally: + print('There was an error updating the canvas.') @app.on_event('startup') From b392717b3d830122547a870db3999f7d1dd6379b Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 09:22:46 +0100 Subject: [PATCH 13/16] namen verbessern --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index b882a2e..1fd4839 100644 --- a/main.py +++ b/main.py @@ -75,7 +75,7 @@ async def live_endpoint(websocket: WebSocket): @app.get('/users/count') async def get_users_count(): return JSONResponse(content={ - 'count': connection_manager.connection_count(), + 'connections': connection_manager.connection_count(), 'advertised_accounts': connection_manager.advertised_account_count() }) From 1dd207e8db2360678a1762ac77feff1ec2fa2789 Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 11:05:26 +0100 Subject: [PATCH 14/16] versioncheck zeugs, vielleicht --- application/api/commands.py | 5 +++++ application/api/config.py | 1 + main.py | 24 ++++++++++++++++++------ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/application/api/commands.py b/application/api/commands.py index 262bd6e..28b8a26 100644 --- a/application/api/commands.py +++ b/application/api/commands.py @@ -1,3 +1,4 @@ +from application.api.config import ServerConfig from application.canvas.canvas import Canvas from application.color import get_color_from_index @@ -25,3 +26,7 @@ def ping(): } +def version_check(settings: ServerConfig, data: dict): + client_version = data.get('version', -1) + # wenn der client nix schickt nehmen wir an, dass er in ordnung ist + return client_version < 0 or client_version < settings.min_version diff --git a/application/api/config.py b/application/api/config.py index 506cddb..868a3aa 100644 --- a/application/api/config.py +++ b/application/api/config.py @@ -4,6 +4,7 @@ class ServerConfig(BaseSettings): remote_config_url: str = 'https://placede.github.io/pixel/pixel.json' canvas_update_interval: int = 10 + min_version: int = 0 def get_graphql_config(): diff --git a/main.py b/main.py index 1fd4839..3884031 100644 --- a/main.py +++ b/main.py @@ -2,10 +2,10 @@ import hashlib import json -from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Depends +from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi.responses import JSONResponse -from application.api.commands import request_pixel, ping +from application.api.commands import request_pixel, ping, version_check from application.api.connection_manager import ConnectionManager from application.api.config import ServerConfig from application.canvas.canvas import Canvas @@ -14,7 +14,6 @@ app = FastAPI() connection_manager = ConnectionManager() - config = ServerConfig() canvas: Canvas @@ -59,16 +58,27 @@ async def live_endpoint(websocket: WebSocket): elif op == 'handshake': metadata = data.get('data', {}) advertised_count = metadata.get('useraccounts', 1) + if not version_check(config, metadata): + response = format_response( + 'notify-update', + data.get('user', ''), + { + 'min_version', config.min_version + } + ) + await websocket.close(4001) + raise Exception('Client outdated, disconnecting websocket') connection_manager.set_advertised_accounts(websocket, advertised_count) elif op == 'ping': response = ping() # eigtl. durch /users/count deprecated elif op == 'get-botcount' and password_check(data.get("pw", '')): response = {'amount': connection_manager.advertised_account_count()} + if response is not None: print(f'TX: {json.dumps(response)}') await websocket.send_json(response) - except WebSocketDisconnect: + finally: connection_manager.disconnect(websocket) @@ -80,12 +90,14 @@ async def get_users_count(): }) -def format_response(op: str, user: str, data: dict): +def format_response(op: str, user: str, data: any): return { 'operation': op, 'data': data, 'user': user } + def password_check(password): - return hashlib.sha3_512(password.encode()).hexdigest() == "bea976c455d292fdd15256d3263cb2b70f051337f134b0fa9678d5eb206b4c45ebd213694af9cf6118700fc8488809be9195c7eae44a882c6be519ba09b68e47" \ No newline at end of file + return hashlib.sha3_512( + password.encode()).hexdigest() == "bea976c455d292fdd15256d3263cb2b70f051337f134b0fa9678d5eb206b4c45ebd213694af9cf6118700fc8488809be9195c7eae44a882c6be519ba09b68e47" From a587860f374dd4c6388a070bb7afe2be9e326eed Mon Sep 17 00:00:00 2001 From: trinitrotoluene Date: Mon, 4 Apr 2022 11:12:18 +0100 Subject: [PATCH 15/16] ups, version check kram war falsch --- application/api/commands.py | 2 +- main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application/api/commands.py b/application/api/commands.py index 28b8a26..78e8d1c 100644 --- a/application/api/commands.py +++ b/application/api/commands.py @@ -29,4 +29,4 @@ def ping(): def version_check(settings: ServerConfig, data: dict): client_version = data.get('version', -1) # wenn der client nix schickt nehmen wir an, dass er in ordnung ist - return client_version < 0 or client_version < settings.min_version + return client_version < 0 or client_version >= settings.min_version diff --git a/main.py b/main.py index 3884031..3b233b9 100644 --- a/main.py +++ b/main.py @@ -67,7 +67,7 @@ async def live_endpoint(websocket: WebSocket): } ) await websocket.close(4001) - raise Exception('Client outdated, disconnecting websocket') + return connection_manager.set_advertised_accounts(websocket, advertised_count) elif op == 'ping': response = ping() From 7c3cf9490c6cb90fa958246e280f2a5695b4d793 Mon Sep 17 00:00:00 2001 From: Kronox Date: Mon, 4 Apr 2022 11:21:24 +0200 Subject: [PATCH 16/16] updated README.md --- README.md | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index cfdbd22..0cc3a12 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,44 @@ ## Client-Server-Kommunikation -- der Client verbindet sich mit dem Server per websocket -- zu Beginn sendet der client seine Platform und version als json zum server: - ```json - {"platform":"python", "version":999} - ``` -- sobald der client in 0 Lage ist ein pixel zu setzen schickt dieser ein `request_pixel` an den server - - der Server antwortet dann mit dem zu setzenden pixel als json, e.g.: +- Der Client verbindet sich mit dem Server per websocket +- Zu Beginn sendet der client seine Platform und version als json zum server: ```json { - "operation":"pixel", - "data":{ + "operation": "handshake", + "data": { + "platform": "python", + "version": 999 + } +} +``` + +- Sollte der Server feststellen, dass der Client eine alte Version verwendet, sendet er diesem eine Update aufforderung zurück: +```json +{ + "operation": "notify-update" +} +``` + +- sobald der client in 0 Lage ist ein pixel zu setzen schickt dieser ein `request-pixel` an den server +```json +{ + "operation": "request-pixel", + "user": "" +} + ``` + +- der Server antwortet dann mit dem zu setzenden pixel als json, e.g.: +```json +{ + "operation": "place-pixel", + "data": { "x": 0, "y": 857, "color": 4, "priority": 1 - } + }, + "user": "" } ``` - - wenn kein Pixel existiert, wird `null` zurückgesendet. + +- wenn kein Pixel existiert, wird `{}` zurückgesendet.