Removed watchdog :/

This commit is contained in:
Casey 2024-07-07 19:27:41 +03:00
parent 29ee0546c3
commit 5df9dd4598
Signed by: hkc
GPG Key ID: F0F6CFE11CDB0960
2 changed files with 154 additions and 179 deletions

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
from typing import Callable, NewType, Optional from typing import Callable, NewType, Optional
from socketio import AsyncClient, AsyncSimpleClient from socketio import AsyncClient, AsyncSimpleClient
from aiohttp import ClientSession from aiohttp import ClientSession, ClientTimeout
from aiohttp_socks import ProxyConnector from aiohttp_socks import ProxyConnector
from PIL import Image, ImageFont, ImageDraw, ImageFilter, ImageSequence, ImageChops from PIL import Image, ImageFont, ImageDraw, ImageFilter, ImageSequence, ImageChops
from base64 import b64decode from base64 import b64decode
@ -15,6 +15,8 @@ PixelMap = NewType("PixelMap", dict[int, bool])
Animation = NewType("Animation", tuple[list[PixelMap], float]) Animation = NewType("Animation", tuple[list[PixelMap], float])
Font = ImageFont.FreeTypeFont | ImageFont.ImageFont Font = ImageFont.FreeTypeFont | ImageFont.ImageFont
TIMEOUT = ClientTimeout(120)
class AsyncBotManager: class AsyncBotManager:
def __init__(self, base: str = "https://onemillioncheckboxes.com"): def __init__(self, base: str = "https://onemillioncheckboxes.com"):
self.base = base self.base = base
@ -38,6 +40,7 @@ class AsyncBotManager:
self._active: set[int] = set() self._active: set[int] = set()
self.reader_event = asyncio.Event() self.reader_event = asyncio.Event()
self.ready_event = asyncio.Event()
@staticmethod @staticmethod
def get_text_image(text: str, font: ImageFont.ImageFont | ImageFont.FreeTypeFont) -> Image.Image: def get_text_image(text: str, font: ImageFont.ImageFont | ImageFont.FreeTypeFont) -> Image.Image:
@ -143,17 +146,19 @@ class AsyncBotManager:
async def listener(self) -> None: async def listener(self) -> None:
try: try:
async with ClientSession() as http: async with ClientSession(timeout=TIMEOUT) as http:
print("Getting initial state")
async with http.get(f"{self.base}/api/initial-state") as req: async with http.get(f"{self.base}/api/initial-state") as req:
data = await req.json() data = await req.json()
buffer = b64decode(data["full_state"].encode() + b"=") buffer = b64decode(data["full_state"].encode() + b"=")
self.canvas.paste(Image.frombytes("1", (1000, 1000), buffer)) self.canvas.paste(Image.frombytes("1", (1000, 1000), buffer))
self._last_update = data["timestamp"] self._last_update = data["timestamp"]
print("Initial state received")
async with AsyncSimpleClient(http_session=http) as sio: async with AsyncSimpleClient(http_session=http) as sio:
await sio.connect(f"{self.base}/socket.io") await sio.connect(f"{self.base}/socket.io")
while not self._shutdown: while not self._shutdown:
try: try:
async with asyncio.timeout(10): async with asyncio.timeout(20):
event, data = await sio.receive() event, data = await sio.receive()
except TimeoutError: except TimeoutError:
print("Reading failed") print("Reading failed")
@ -162,6 +167,7 @@ class AsyncBotManager:
await sio.connect(f"{self.base}/socket.io") await sio.connect(f"{self.base}/socket.io")
continue continue
self.reader_event.set() self.reader_event.set()
self.ready_event.set()
if event == "full_state": if event == "full_state":
buffer = b64decode(data["full_state"].encode() + b"=") buffer = b64decode(data["full_state"].encode() + b"=")
image = Image.frombytes("1", (1000, 1000), buffer) image = Image.frombytes("1", (1000, 1000), buffer)
@ -191,7 +197,7 @@ class AsyncBotManager:
print(f"I/O: {incoming:7.2f}/s | {outgoing:7.2f}/s") print(f"I/O: {incoming:7.2f}/s | {outgoing:7.2f}/s")
print(f"Alive workers: {len(self._active)}") print(f"Alive workers: {len(self._active)}")
if len(self._active) < 5: if len(self._active) < 2:
self._shutdown = True self._shutdown = True
return return
@ -230,9 +236,11 @@ class AsyncBotManager:
delay: float = 0.25, delay: float = 0.25,
): ):
proxy = ProxyConnector.from_url(proxy_url) if proxy_url else None proxy = ProxyConnector.from_url(proxy_url) if proxy_url else None
async with ClientSession(connector=proxy) as http: await self.ready_event.wait()
async with ClientSession(connector=proxy, timeout=TIMEOUT) as http:
async with AsyncSimpleClient(http_session=http) as sio: async with AsyncSimpleClient(http_session=http) as sio:
await sio.connect(f"{self.base}/socket.io") await sio.connect(f"{self.base}/socket.io")
print(f"Writer {bot_index} connected")
offset = 0 offset = 0
while not self._shutdown: while not self._shutdown:
diff = list(self.difference.items()) diff = list(self.difference.items())
@ -262,172 +270,156 @@ async def amain() -> None:
with open("settings.json", "r") as fp: with open("settings.json", "r") as fp:
settings = load(fp) settings = load(fp)
mgr = AsyncBotManager() async with AsyncBotManager() as mgr:
for avoid in settings["avoid"]:
if avoid["type"] == "rect":
mgr.add_avoid_rect(
avoid["x"], avoid["y"], avoid["w"], avoid["h"]
)
print("AVOID rectangle {w}x{h}+{x}+{y}".format(**avoid))
elif avoid["type"] == "range":
mgr.add_avoid_range(
avoid["start"], avoid["stop"], avoid.get("step", 1)
)
print("AVOID range {start}-{stop}".format(**avoid))
elif avoid["type"] == "image":
with Image.open(avoid["path"]).convert("LA") as im:
assert im.width == 1000 and im.height == 1000
for y in range(im.height):
for x in range(im.width):
l, a = im.getpixel((x, y)) # type: ignore
if a > 128:
mgr.add_avoid_index(x + y * 1000)
print("AVOID image", avoid["path"])
for elem in settings["elements"]:
async def watchdog(): if elem["type"] == "text":
listener_task = asyncio.create_task(mgr.listener(), name="Listener") mgr.put_text(elem["x"], elem["y"], elem["text"], elem.get("font", "default"), elem.get("size", 8))
while True: print("ADD text", elem)
try: elif elem["type"] == "text_anim":
async with asyncio.timeout(30):
await mgr.reader_event.wait()
except TimeoutError:
print("WATCHDOG TIMER PASSED")
listener_task.cancel()
listener_task = asyncio.create_task(mgr.listener())
watchdog_task = asyncio.create_task(watchdog(), name="Watchdog")
for avoid in settings["avoid"]:
if avoid["type"] == "rect":
mgr.add_avoid_rect(
avoid["x"], avoid["y"], avoid["w"], avoid["h"]
)
print("AVOID rectangle {w}x{h}+{x}+{y}".format(**avoid))
elif avoid["type"] == "range":
mgr.add_avoid_range(
avoid["start"], avoid["stop"], avoid.get("step", 1)
)
print("AVOID range {start}-{stop}".format(**avoid))
elif avoid["type"] == "image":
with Image.open(avoid["path"]).convert("LA") as im:
assert im.width == 1000 and im.height == 1000
for y in range(im.height):
for x in range(im.width):
l, a = im.getpixel((x, y)) # type: ignore
if a > 128:
mgr.add_avoid_index(x + y * 1000)
print("AVOID image", avoid["path"])
for elem in settings["elements"]:
if elem["type"] == "text":
mgr.put_text(elem["x"], elem["y"], elem["text"], elem.get("font", "default"), elem.get("size", 8))
print("ADD text", elem)
elif elem["type"] == "text_anim":
frames: list[Image.Image] = []
for text in elem["lines"]:
frames.append(mgr.get_text_image(text, mgr.get_font(elem.get("font", "default"), elem.get("size", 8))))
mgr.add_animation(elem["x"], elem["y"], frames, elem["spf"])
for frame in frames:
frame.close()
print("ADD text animation", elem)
elif elem["type"] == "image":
with Image.open(elem["path"]).convert("LA") as im:
mgr.put_image(elem["x"], elem["y"], im)
print("ADD image", elem)
elif elem["type"] == "time":
time_format = elem["format"]
pos_x, pos_y = elem["x"], elem["y"]
font = mgr.get_font(elem.get("font", "default"), elem.get("size", 8))
def update() -> PixelMap:
now = datetime.datetime.now(datetime.timezone.utc)
txt = now.strftime(time_format)
img = mgr.get_text_image(txt, font)
pixmap = mgr.get_image_diff(pos_x, pos_y, img)
img.close()
return pixmap
mgr.animation_functions.append(update)
elif elem["type"] == "tile":
with Image.open(elem["path"]).convert("LA") as im:
for oy in range(elem.get("h", im.height)):
for ox in range(elem.get("w", im.width)):
l, a = im.getpixel((ox % im.width, oy % im.height)) # type: ignore
if a:
x, y = elem["x"] + ox, elem["y"] + oy
index = x + y * 1000
mgr.put_bit(index, l > 0)
print("ADD tile", elem)
elif elem["type"] == "animation":
with Image.open(elem["path"]) as anim:
frames: list[Image.Image] = [] frames: list[Image.Image] = []
for frame in ImageSequence.Iterator(anim): for text in elem["lines"]:
frames.append(frame.copy()) frames.append(mgr.get_text_image(text, mgr.get_font(elem.get("font", "default"), elem.get("size", 8))))
mgr.add_animation(elem["x"], elem["y"], frames, elem["spf"]) mgr.add_animation(elem["x"], elem["y"], frames, elem["spf"])
for frame in frames: for frame in frames:
frame.close() frame.close()
print("ADD animation", elem) print("ADD text animation", elem)
elif elem["type"] == "rgb111": elif elem["type"] == "image":
ox, oy = elem["x"], elem["y"] with Image.open(elem["path"]).convert("LA") as im:
with Image.open(elem["path"]).convert("RGBA") as im: mgr.put_image(elem["x"], elem["y"], im)
for y in range(im.height): print("ADD image", elem)
for x in range(im.width):
r, g, b, a = im.getpixel((x, y)) # type: ignore
if a < 128:
continue
start_ndx = (x + ox + (y + oy) * 577) * 3 elif elem["type"] == "time":
mgr.put_bit(start_ndx, r > 128) time_format = elem["format"]
mgr.put_bit(start_ndx + 1, g > 128) pos_x, pos_y = elem["x"], elem["y"]
mgr.put_bit(start_ndx + 2, b > 128) font = mgr.get_font(elem.get("font", "default"), elem.get("size", 8))
elif elem["type"] == "rgb565": def update() -> PixelMap:
ox, oy = elem["x"], elem["y"] now = datetime.datetime.now(datetime.timezone.utc)
with Image.open(elem["path"]).convert("RGBA") as im: txt = now.strftime(time_format)
for y in range(im.height): img = mgr.get_text_image(txt, font)
for x in range(im.width): pixmap = mgr.get_image_diff(pos_x, pos_y, img)
r, g, b, a = im.getpixel((x, y)) # type: ignore img.close()
if a < 128: return pixmap
continue mgr.animation_functions.append(update)
elif elem["type"] == "tile":
with Image.open(elem["path"]).convert("LA") as im:
for oy in range(elem.get("h", im.height)):
for ox in range(elem.get("w", im.width)):
l, a = im.getpixel((ox % im.width, oy % im.height)) # type: ignore
if a:
x, y = elem["x"] + ox, elem["y"] + oy
index = x + y * 1000
mgr.put_bit(index, l > 0)
print("ADD tile", elem)
elif elem["type"] == "animation":
with Image.open(elem["path"]) as anim:
frames: list[Image.Image] = []
for frame in ImageSequence.Iterator(anim):
frames.append(frame.copy())
mgr.add_animation(elem["x"], elem["y"], frames, elem["spf"])
for frame in frames:
frame.close()
print("ADD animation", elem)
elif elem["type"] == "rgb111":
ox, oy = elem["x"], elem["y"]
with Image.open(elem["path"]).convert("RGBA") as im:
for y in range(im.height):
for x in range(im.width):
r, g, b, a = im.getpixel((x, y)) # type: ignore
if a < 128:
continue
offset: int = (x + ox + (y + oy) * 250) * 16 start_ndx = (x + ox + (y + oy) * 577) * 3
mgr.put_bit(start_ndx, r > 128)
mgr.put_bit(start_ndx + 1, g > 128)
mgr.put_bit(start_ndx + 2, b > 128)
elif elem["type"] == "rgb565":
ox, oy = elem["x"], elem["y"]
with Image.open(elem["path"]).convert("RGBA") as im:
for y in range(im.height):
for x in range(im.width):
r, g, b, a = im.getpixel((x, y)) # type: ignore
if a < 128:
continue
# pack rgb888 to rgb565 offset: int = (x + ox + (y + oy) * 250) * 16
rgb: int = (r >> 3) << 11
rgb |= (g >> 2) << 5
rgb |= (b >> 3)
# write to a bitstream # pack rgb888 to rgb565
for i in range(16): rgb: int = (r >> 3) << 11
mgr.put_bit( rgb |= (g >> 2) << 5
offset + i, ((rgb << i) & 0x8000) > 0 rgb |= (b >> 3)
)
elif elem["type"] == "rect": # write to a bitstream
for y in range(elem["y"], elem["y"] + elem["h"]): for i in range(16):
for x in range(elem["x"], elem["x"] + elem["w"]): mgr.put_bit(
mgr.put_pixel(x, y, elem["fill"]) offset + i, ((rgb << i) & 0x8000) > 0
elif elem["type"] == "blob": )
with open(elem["path"], "rb") as fp: elif elem["type"] == "rect":
offset = elem["offset"] for y in range(elem["y"], elem["y"] + elem["h"]):
length = elem.get("length", 1000000) for x in range(elem["x"], elem["x"] + elem["w"]):
written = 0 mgr.put_pixel(x, y, elem["fill"])
while (char := fp.read(1)): elif elem["type"] == "blob":
byte = char[0] with open(elem["path"], "rb") as fp:
if written > length: offset = elem["offset"]
break length = elem.get("length", 1000000)
for i in range(8): written = 0
while (char := fp.read(1)):
byte = char[0]
if written > length: if written > length:
break break
mgr.put_bit(offset, bool((byte >> (7 - i)) & 1)) for i in range(8):
written += 1 if written > length:
offset += 1 break
mgr.put_bit(offset, bool((byte >> (7 - i)) & 1))
written += 1
offset += 1
mgr.get_difference_image().save("result.png") mgr.get_difference_image().save("result.png")
mgr.get_avoid_image().save("avoid.png") mgr.get_avoid_image().save("avoid.png")
print("Starting writers...") print("Starting writers...")
if n_proxies := len(settings["proxies"]): if n_proxies := len(settings["proxies"]):
res = await asyncio.gather( res = await asyncio.gather(
*[ *[
mgr.writer( mgr.writer(
i, i,
settings["proxies"][i % n_proxies], settings["proxies"][i % n_proxies],
settings["delay"], settings["delay"],
) )
for i in range(settings["n_bots"]) for i in range(settings["n_bots"])
], ],
return_exceptions=True, return_exceptions=True,
) )
else: else:
res = await asyncio.gather( res = await asyncio.gather(
*[mgr.writer(i) for i in range(settings["n_bots"])], *[mgr.writer(i) for i in range(settings["n_bots"])],
return_exceptions=True, return_exceptions=True,
) )
for ret in res: for ret in res:
print("RETURN", repr(ret)) print("RETURN", repr(ret))
mgr._shutdown = True mgr._shutdown = True
watchdog_task.cancel()
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,9 +1,12 @@
{ {
"url": "https://onemillioncheckboxes.com", "url": "https://onemillioncheckboxes.com",
"font": "./ic8x8u.ttf", "font": "./ic8x8u.ttf",
"proxies": [], "proxies": [
"n_bots": 5, "",
"delay": 0.125, "socks5://127.0.0.1:9999"
],
"n_bots": 4,
"delay": 0.5,
"avoid": [ "avoid": [
{ {
"type": "rect", "type": "rect",
@ -57,26 +60,6 @@
"x": 0, "x": 0,
"y": 128 "y": 128
}, },
{
"type": "rgb565",
"path": "./pictures/niko_standing.png",
"x": 116,
"y": 132
},
{
"type": "rgb565",
"path": "./pictures/hello.png",
"x": 112,
"y": 203
},
{
"type": "text",
"font": "/usr/share/fonts/TTF/comic.ttf",
"size": 20,
"x": 250,
"y": 492,
"text": "You like kissing, don't you?"
},
{ {
"type": "animation", "type": "animation",
"path": "./pictures/neko.gif", "path": "./pictures/neko.gif",