commit 12c2d3ff9f16efcd745b94e739f2e648adab9d2c Author: dsc Date: Wed Jul 20 04:48:32 2022 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..312222f --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea +*.pyc +settings.py +*.xcf diff --git a/README.md b/README.md new file mode 100644 index 0000000..5fc23f6 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# craiyon + +AI model drawing images from any IRC shitpost! \ No newline at end of file diff --git a/bg-pools.jpg b/bg-pools.jpg new file mode 100644 index 0000000..e7aa802 Binary files /dev/null and b/bg-pools.jpg differ diff --git a/bg-wownero.jpg b/bg-wownero.jpg new file mode 100644 index 0000000..a4b807c Binary files /dev/null and b/bg-wownero.jpg differ diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/font-bold.ttf b/font-bold.ttf new file mode 100644 index 0000000..7bd6665 Binary files /dev/null and b/font-bold.ttf differ diff --git a/font.ttf b/font.ttf new file mode 100644 index 0000000..fdd309d Binary files /dev/null and b/font.ttf differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..94c64ab --- /dev/null +++ b/main.py @@ -0,0 +1,225 @@ +import time +import os +import random +import json +from base64 import b64decode +from typing import Optional, List +from datetime import datetime +import logging +import asyncio +from io import BytesIO +import bottom +from PIL import Image, ImageDraw, ImageFont +from PIL.JpegImagePlugin import JpegImageFile +import aiohttp + +from settings import * + +TASK_QUEUE = asyncio.Queue() +TASK_AUTHORS = {} +FONT = ImageFont.truetype('font-bold.ttf', 22) +bot = bottom.Client(host=IRC_HOST, port=IRC_PORT, ssl=IRC_TLS, loop=asyncio.get_event_loop()) + +USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36' +HEADERS = { + 'User-Agent': USER_AGENT, + 'Accept': 'application/json', + 'Accept-Language': 'en-US,en;q=0.5', + 'Referer': 'https://www.craiyon.com/', + 'Origin': 'https://www.craiyon.com', + 'Connection': 'keep-alive', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-site', + 'Pragma': 'no-cache', + 'Cache-Control': 'no-cache', +} + + +class Task: + def __init__(self, author: str, channel: str, term: str, dt: int): + self.author = author + self.channel = channel + self.term = term + self.dt = dt + + async def process_images(self, images: List[JpegImageFile]) -> JpegImageFile: + if len(images) <= 0: + raise Exception("Not enough images") + + if MODE == "monero": + bg = Image.open('bg-pools.jpg') + term_max = 60 + term_x_start = 100 + else: + bg = Image.open('bg-wownero.jpg') + term_max = 52 + term_x_start = 230 + + d1 = ImageDraw.Draw(bg) + + term = self.term + if len(term) >= term_max: + term = self.term[:term_max] + ".." + + d1.text((term_x_start, 160), term, font=FONT, fill=(255, 255, 255)) + + x_start = 41 + y_start = 232 + for i, img in enumerate(images): + x_offset = (i % 3) * (img.width + 20) + y_offset = int(i / 3) * (img.width + 20) + bg.paste(img, (x_start + x_offset, y_start + y_offset)) + return bg + + async def post_image(self, image: BytesIO) -> str: + url = 'https://uguu.se/upload.php' + timeout = aiohttp.ClientTimeout(total=5) + + data = aiohttp.FormData() + data.add_field('files[]', image, filename='lol.jpg', content_type='image/jpg') + + try: + async with aiohttp.ClientSession( + timeout=timeout, + headers={"User-Agent": "w0w"}) as session: + async with session.post(url, data=data) as resp: + blob = await resp.json() + return blob['files'][0]['url'] + except Exception as ex: + raise Exception("Could not upload file to uguu.se :((") + + async def get_images(self) -> Optional[List[JpegImageFile]]: + url = 'https://backend.craiyon.com/generate' + timeout_secs = 120 + timeout = aiohttp.ClientTimeout(total=timeout_secs) + images = [] + + try: + async with aiohttp.ClientSession( + timeout=timeout, + headers=HEADERS) as session: + async with session.post(url, json={'prompt': self.term}) as resp: + if resp.status != 200: + raise Exception("POST did not return status 200") + blob = await resp.json() + if 'images' not in blob: + raise Exception("result did not return images") + images = blob['images'] + except asyncio.TimeoutError: + raise Exception(f"image generation timeout exceeded ({timeout_secs} secs)") + + try: + images = [b64decode(i) for i in images] + return [Image.open(BytesIO(i)) for i in images] + except Exception as ex: + raise Exception("could not decode results") + + +@bot.on('CLIENT_CONNECT') +async def connect(**kwargs): + bot.send('NICK', nick=IRC_NICK) + bot.send('USER', user=IRC_NICK, realname=IRC_NICK) + done, pending = await asyncio.wait( + [bot.wait("RPL_ENDOFMOTD"), bot.wait("ERR_NOMOTD")], + loop=bot.loop, + return_when=asyncio.FIRST_COMPLETED + ) + + for future in pending: + future.cancel() + for c in IRC_CHANNELS: + bot.send('JOIN', channel=c) + + +@bot.on('PING') +def keepalive(message, **kwargs): + bot.send('PONG', message=message) + + +@bot.on('client_disconnect') +def reconnect(**kwargs): + logging.warning("Lost IRC server connection") + time.sleep(3) + bot.loop.create_task(bot.connect()) + logging.warning("Reconnecting to IRC server") + + +@bot.on('PRIVMSG') +async def message_received(nick, target, message, **kwargs): + if nick == IRC_NICK: + return + if target == IRC_NICK: + target = nick + if target not in IRC_CHANNELS: + return + + msg = message + now = datetime.now() + now = time.mktime(now.timetuple()) + await handle_msg(nick, msg, target, now) + + +async def handle_msg(nick, msg, target, now: float): + global TASK_AUTHORS, TASK_QUEUE + if not msg.startswith("!dall "): + return + msg = msg[6:].strip() + + if len(msg) <= 8: + err = f"{nick}: longer query required." + bot.send("PRIVMSG", target=target, message=err) + return + + TASK_AUTHORS.setdefault(nick, 0) + if TASK_AUTHORS[nick] >= 3: + err = f"{nick}: you already queued 3 thingies, patient!!11" + bot.send("PRIVMSG", target=target, message=err) + return + + TASK_AUTHORS[nick] += 1 + task = Task(channel=target, author=nick, term=msg, dt=int(now)) + await TASK_QUEUE.put(task) + bot.send("PRIVMSG", target=target, message=f"{nick}: generating...") + + +async def main(): + global TASK_AUTHORS, TASK_QUEUE + bot.loop.create_task(bot.connect()) + + # main loop + while True: + random.shuffle(TASK_QUEUE._queue) + task = await TASK_QUEUE.get() + + try: + images = await task.get_images() + except Exception as ex: + bot.send("PRIVMSG", target=task.channel, message=f"could not fetch images: {ex}") + continue + + try: + image = await task.process_images(images) + except Exception as ex: + bot.send("PRIVMSG", target=task.channel, message=f"could not process images: {ex}") + continue + + image_bytes = BytesIO() + image.save(image_bytes, 'JPEG') + image.save(os.path.join('data', f"{task.dt}_{task.author}.jpg"), 'JPEG') + image_bytes.seek(0) + + try: + url = await task.post_image(image_bytes) + except Exception as ex: + bot.send("PRIVMSG", target=task.channel, message=str(ex)) + continue + + TASK_AUTHORS.setdefault(task.author, 1) + TASK_AUTHORS[task.author] -= 1 + + bot.send("PRIVMSG", target=task.channel, message=f"{url} ({task.author})") + await asyncio.sleep(10) + +loop = asyncio.get_event_loop() +loop.run_until_complete(main()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..282e988 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +bottom +aiohttp +pillow \ No newline at end of file diff --git a/settings.py_example.py b/settings.py_example.py new file mode 100644 index 0000000..bf29827 --- /dev/null +++ b/settings.py_example.py @@ -0,0 +1,6 @@ +IRC_NICK = "wtfbbq" +IRC_HOST = "localhost" +IRC_PORT = 6667 +IRC_TLS = False +IRC_CHANNELS = ["#lol"] +MODE = 'monero' \ No newline at end of file