Initial commit

This commit is contained in:
Casey 2024-07-03 22:01:42 +03:00
parent 5dc2fc5881
commit d23df9c51c
Signed by: hkc
GPG Key ID: F0F6CFE11CDB0960
17 changed files with 338 additions and 6 deletions

129
async-bot.py Normal file
View File

@ -0,0 +1,129 @@
import asyncio
from socketio import AsyncSimpleClient
from aiohttp import ClientSession
from PIL import Image, ImageFont, ImageDraw, ImageFilter
from base64 import b64decode
from random import choice
from json import load
class AsyncBotManager:
def __init__(self, base: str = "https://onemillioncheckboxes.com"):
self.base = base
self.canvas = Image.new("1", (1000, 1000))
self.font = ImageFont.load_default(8)
self.difference: dict[int, bool] = {}
self._shutdown: bool = False
def put_text(self, x: int, y: int, text: str):
with Image.new("LA", (int(self.font.getlength(text) + 12), 16)) as im:
draw = ImageDraw.Draw(im)
draw.rectangle((0, 0, im.width, im.height), (0, 0))
draw.text((6, 5), text, font=self.font, fill=(255, 0))
alpha = im.convert("L").filter(ImageFilter.MaxFilter(3))
im.putalpha(alpha)
self.put_image(x, y, im)
def put_image(self, ox: int, oy: int, im: Image.Image):
for y in range(im.height):
for x in range(im.width):
l, a = im.getpixel((x, y)) # type: ignore
if a:
self.difference[x + ox + (y + oy) * 1000] = l > 0
async def listener(self):
async with ClientSession() as http:
async with http.get(f"{self.base}/api/initial-state") as req:
data = await req.json()
buffer = b64decode(data["full_state"].encode() + b"=")
self.canvas.paste(Image.frombytes("1", (1000, 1000), buffer))
async with AsyncSimpleClient(http_session=http) as sio:
await sio.connect(f"{self.base}/socket.io")
while not self._shutdown:
event, data = await sio.receive()
if event == "full_state":
buffer = b64decode(data["full_state"].encode() + b"=")
image = Image.frombytes("1", (1000, 1000), buffer)
self.canvas.paste(image)
image.close()
elif event == "batched_bit_toggles":
bits_on, bits_off, timestamp = data
for ndx in bits_on:
y, x = divmod(ndx, 1000)
self.canvas.putpixel((x, y), 255)
for ndx in bits_off:
y, x = divmod(ndx, 1000)
self.canvas.putpixel((x, y), 0)
else:
print("unknown event", event, data)
async def writer(self):
async with ClientSession() as http:
async with AsyncSimpleClient(http_session=http) as sio:
await sio.connect(f"{self.base}/socket.io")
while not self._shutdown:
try:
event, data = await sio.receive(0.1)
if event not in ("full_state", "batched_bit_toggles"):
print("!!!", event, data)
except Exception:
pass
index, target = choice(list(self.difference.items()))
y, x = divmod(index, 1000)
curr = self.canvas.getpixel((x, y)) > 0 # type: ignore
if curr != target:
print("swap", x, y, index)
await sio.emit("toggle_bit", {
"index": index
})
await asyncio.sleep(0.25)
else:
await asyncio.sleep(0.001)
async def __aenter__(self):
self._listener_task = asyncio.create_task(self.listener())
return self
async def __aexit__(self, a, b, c):
self._shutdown = True
await self._listener_task
async def amain():
with open("settings.json", "r") as fp:
settings = load(fp)
async with AsyncBotManager() as mgr:
mgr.font = ImageFont.truetype(settings["font"], 8)
for elem in settings["elements"]:
if elem["type"] == "text":
mgr.put_text(elem["text"], elem["x"], elem["y"])
elif elem["type"] == "image":
with Image.open(elem["path"]).convert("LA") as im:
mgr.put_image(elem["x"], elem["y"], im)
elif elem["type"] == "rgb111":
ox, oy = elem["x"], elem["y"]
with Image.open(elem["path"]).convert("RGB") as im:
for y in range(im.height):
for x in range(im.width):
r, g, b = im.getpixel((x, y)) # type: ignore
pocket_x = x + ox
pocket_y = y + oy
ndx_start = pocket_x * 3 + pocket_y * 577 * 3
mgr.difference[ndx_start] = r > 128
mgr.difference[ndx_start + 1] = g > 128
mgr.difference[ndx_start + 2] = b > 128
await asyncio.gather(*[
mgr.writer() for _ in range(settings["n_bots"])
], return_exceptions=True)
if __name__ == "__main__":
asyncio.run(amain())

BIN
boykisser.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
casey.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
casey111.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

BIN
colon3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 B

BIN
fedi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
glibc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
ic8x8u.ttf Executable file

Binary file not shown.

BIN
kangel.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
lawbymike.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

111
live-viewer.py Normal file
View File

@ -0,0 +1,111 @@
from base64 import b64decode
import tkinter as tk
from PIL import Image, ImageTk
from socketio import SimpleClient, exceptions
from threading import Thread
from requests import get
COLORS = [(0x33, 0x33, 0x66), (0x96, 0x96, 0xe0)]
COLORS_UNPACKED = COLORS[0] + ((0, 0, 0) * 254) + COLORS[1]
class App(tk.Tk):
def __init__(self, url: str = "https://onemillioncheckboxes.com") -> None:
self.url = url
super().__init__()
self.title("1M pixels")
self.sio = SimpleClient()
self._canvas = tk.Canvas(self)
self._canvas.config(width=1000, height=1000, borderwidth=0, highlightthickness=0)
self._canvas.pack()
self.config(width=1000, height=1000, borderwidth=0, highlightthickness=0)
self._running = False
self._image = Image.new("RGB", (1000, 1000), COLORS[0])
self._canvas.bind("<Motion>", self._on_mouse_move)
self._canvas.bind("<Button>", self._on_mouse_click)
self.bind("<KeyPress>", self._on_key_down)
def _on_mouse_move(self, event: tk.Event):
self.title(f"1M pixels: {event.x}, {event.y}")
def _on_mouse_click(self, event: tk.Event):
x, y = event.x, event.y
self.sio.emit("toggle_bit", { "index": x + y * 1000 })
def _on_key_down(self, event: tk.Event):
# <KeyPress event state=Control keysym=r keycode=27 char='\x12' x=538 y=556>
if event.keysym == "r":
print("FULL REFRESH")
with get(f"{self.url}/api/initial-state") as req:
buffer = b64decode(req.json()["full_state"].encode() + b"=")
img = Image.frombytes("1", (1000, 1000), buffer).convert("P")
img.putpalette(COLORS_UNPACKED)
self._image.paste(img.convert("RGB"))
print("FULL REFRESH DONE")
def _reader(self):
while self._running:
try:
name, data = self.sio.receive()
except exceptions.TimeoutError:
print("Timeout")
continue
if name == "batched_bit_toggles":
bits_on, bits_off, timestamp = data
for ndx in bits_on:
y, x = divmod(ndx, 1000)
self._image.putpixel((x, y), COLORS[1])
for ndx in bits_off:
y, x = divmod(ndx, 1000)
self._image.putpixel((x, y), COLORS[0])
self._tk_image.paste(self._image)
print("partial update", len(bits_on), len(bits_off))
elif name == "full_state":
print("full update")
buffer = b64decode(data["full_state"].encode() + b"=")
img = Image.frombytes("1", (1000, 1000), buffer).convert("P")
img.putpalette(COLORS_UNPACKED)
self._image.paste(img.convert("RGB"))
self._tk_image.paste(self._image)
else:
print(name, data)
self.sio.disconnect()
print("_reader exited")
def _close(self):
self._running = False
print("waiting for reader to close")
self._reader_thr.join()
self.destroy()
def start(self):
self.sio.connect(f"{self.url}/socket.io")
with get(f"{self.url}/api/initial-state") as req:
buffer = b64decode(req.json()["full_state"].encode() + b"=")
img = Image.frombytes("1", (1000, 1000), buffer).convert("P")
img.putpalette(COLORS_UNPACKED)
self._image.paste(img.convert("RGB"))
self._tk_image = ImageTk.PhotoImage(self._image)
self._tk_image_id = self._canvas.create_image(0, 0, anchor="nw", image=self._tk_image)
self._running = True
self._reader_thr = Thread(target=self._reader, name="Reader thread")
self._reader_thr.start()
self.protocol("WM_DELETE_WINDOW", self._close)
self.mainloop()
if __name__ == "__main__":
app = App()
app.start()

View File

@ -1,5 +0,0 @@
def main():
print('Hi!')
if __name__ == '__main__':
main()

View File

@ -1 +1,19 @@
aiohttp==3.9.5
aiosignal==1.3.1
attrs==23.2.0
bidict==0.23.1
certifi==2024.6.2
charset-normalizer==3.3.2
frozenlist==1.4.1
h11==0.14.0
idna==3.7
multidict==6.0.5
pillow==10.4.0
python-engineio==4.9.1
python-socketio==5.11.3
requests==2.32.3
simple-websocket==1.0.0
urllib3==2.2.2
websocket-client==1.8.0
wsproto==1.2.0
yarl==1.9.4

27
rgb111.py Normal file
View File

@ -0,0 +1,27 @@
from base64 import b64decode
from PIL import Image
from requests import get
BASE_URL = "https://onemillioncheckboxes.com"
with get(f"{BASE_URL}/api/initial-state") as req:
data = req.json()
buffer = b64decode(data["full_state"].encode() + b"=")
def getbit(b: bytes, i: int) -> bool:
return b[i // 8] & (0x80 >> (i % 8)) != 0
with Image.new("RGB", (577, 577)) as im:
for y in range(im.height):
for x in range(im.width):
ndx_start = (x + y * 577) * 3
im.putpixel(
( x, y ),
(
255 if getbit(buffer, ndx_start) else 0,
255 if getbit(buffer, ndx_start + 1) else 0,
255 if getbit(buffer, ndx_start + 2) else 0,
)
)
im.save("rgb111-full.png")

52
settings.json Normal file
View File

@ -0,0 +1,52 @@
{
"url": "https://onemillioncheckboxes.com",
"font": "./ic8x8u.ttf",
"proxies": [
],
"n_bots": 10,
"elements": [
{
"type": "rgb111",
"path": "./casey111.png",
"x": 240,
"y": 240
},
{
"type": "rgb111",
"path": "./trans.png",
"x": 200,
"y": 200
},
{
"type": "image",
"path": "./casey.png",
"x": 256,
"y": 256
},
{
"type": "image",
"path": "./kangel.png",
"x": 256,
"y": 360
},
{
"type": "image",
"path": "./boykisser.png",
"x": 69,
"y": 420
},
{
"type": "image",
"path": "./lawbymike.png",
"x": 150,
"y": 420
},
{
"type": "image",
"path": "./colon3.png",
"x": 333,
"y": 333
}
]
}

BIN
trans.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 B

BIN
transparen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB