diff options
| author | david <[email protected]> | 2026-03-10 12:57:05 -0300 |
|---|---|---|
| committer | david <[email protected]> | 2026-03-10 12:57:05 -0300 |
| commit | a8fdeb52f075d3267a938f5298dd1549feeeafbc (patch) | |
| tree | be8ebba8699e0738c77e0c29ebc0a90e322ae323 /bot.py | |
| download | faggot-chan-a8fdeb52f075d3267a938f5298dd1549feeeafbc.tar.gz faggot-chan-a8fdeb52f075d3267a938f5298dd1549feeeafbc.zip | |
fixed a couple bugs, moved a couple of this. also made main stateless so i can do funny things
Diffstat (limited to 'bot.py')
| -rw-r--r-- | bot.py | 387 |
1 files changed, 387 insertions, 0 deletions
@@ -0,0 +1,387 @@ +#!/usr/bin/env python3 +import os +import asyncio +import json +import time +import random +import traceback +import discord +import websockets +from datetime import datetime +from pathlib import Path + +def ignore_connection_closed_ok(loop, context): #this doesn't fucking work + exc = context.get("exception") + if isinstance(exc, websockets.exceptions.ConnectionClosedOK): + return + loop.default_exception_handler(context) + +system_prompt = "you are a funny discord user that uses discrd" + +system_prompt = "you are a funny discord user that uses discrd" + +FILES_DIR = Path("files") +LOG_DIR = FILES_DIR / "log" +MEDIA_DIR = FILES_DIR / "media" +CONFIG_DIR = FILES_DIR / "config" + +ERROR_FILE = LOG_DIR / "errorlog.txt" +MEMORY_FILE = LOG_DIR / "memory.txt" +MEDIA_FILE = MEDIA_DIR / "media.txt" +THREADS_FILE = CONFIG_DIR / "threads.txt" + +def init_files(): + for d in (LOG_DIR, MEDIA_DIR, CONFIG_DIR): + d.mkdir(parents=True, exist_ok=True) + for f in (ERROR_FILE, MEMORY_FILE, MEDIA_FILE): + f.touch(exist_ok=True) + if not THREADS_FILE.exists(): + THREADS_FILE.write_text("6\n") + +init_files() + +TOKEN= "your token goes here" + + +server = "put your serevr id here" + + +intents = discord.Intents.default() +intents.message_content = True +intents.members = True +client = discord.Client(intents=intents) + +block_id = ["1000000000000003", "1000000000000004"] #fuck these two in particular + +last_server_message = 0.0 # unix timestamp of last message anyone sent in the server +dm_sent = False +interaction_count = 0 + +QUIET_COOLDOWN = 600 # seconds of silence before she's allowed to send a random message (10 mins) + +def timestamp() -> str: + return datetime.now().strftime("%Y-%m-%d %H:%M:%S") + +def log_error(text: str): + with ERROR_FILE.open("a", encoding="utf-8") as f: + f.write(text + "\n") + +def log_message(text: str): + with MEMORY_FILE.open("a", encoding="utf-8") as f: + f.write(text + "\n") + +def nots(line: str) -> str: + if line.startswith("[") and "]" in line: + return line.split("]", 1)[1].lstrip() + return line + +def load_shortterm() -> list[str]: + try: + with MEMORY_FILE.open("r", encoding="utf-8", errors="ignore") as f: + lines = f.readlines()[-15:] # reads bottom 15 lines + return [line.strip() for line in lines if line.strip()] + except Exception as e: + log_error(f"[{timestamp()}] error reading shortterm: {e}") + return [] + +def load_longterm() -> list[str]: + try: + with MEMORY_FILE.open("r", encoding="utf-8", errors="ignore") as f: + lines = [nots(line.strip()) for line in f if line.strip()] + return lines[-515:-15] if len(lines) > 15 else [] + except Exception as e: + log_error(f"[{timestamp()}] error reading longterm: {e}") + return [] + +def build_payload(sys_prompt: str, user_input: str, rebuild: bool = False) -> str: + return json.dumps({ + "system_prompt": sys_prompt, + "user_input": user_input, + "shortterm": load_shortterm(), + "longterm": load_longterm(), + "rebuild_vectors": rebuild, + }) + +async def call_main(payload: str) -> tuple[bool, str]: + uri = "ws://0.0.0.0:8765" + try: + async with websockets.connect(uri, open_timeout=None, ping_timeout=None) as websocket: + await websocket.send(payload) + raw = await websocket.recv() + data = json.loads(raw) + if "error" in data: + return False, data["error"] + return True, data["ok"] + except websockets.exceptions.ConnectionClosedOK: + return False, f"[{timestamp()}] websocket closed cleanly (no reply)" + except Exception as e: + return False, f"[{timestamp()}] websocket error: {e}" + +async def handle_error(error_msg: str, notify_channel=None, notify_user=None): + print(error_msg) + log_error(error_msg) + notice = "someone tell <@1000000000000001> there is a problem with my ai" + try: + if notify_channel: + await notify_channel.send(notice) + elif notify_user: + dm = await notify_user.create_dm() + await dm.send(notice) + except Exception: + pass + +saved_media_urls = set() + +with MEDIA_FILE.open("r", encoding="utf-8") as f: + for line in f: + if "saved media:" in line: + url = line.split("saved media:", 1)[1].strip() + saved_media_urls.add(url) + +def steal_media(text: str): + if "saved media:" not in text: + return + url = text.split("saved media:", 1)[1].strip() + if url in saved_media_urls: + return + saved_media_urls.add(url) + with MEDIA_FILE.open("a", encoding="utf-8") as f: + f.write(text + "\n") + +def is_member_in_server(member: discord.Member) -> bool: + guild = client.get_guild(server) + return guild and guild.get_member(member.id) is not None + +async def random_dm(): + global dm_sent + await client.wait_until_ready() + while not client.is_closed(): + await asyncio.sleep(300) #every 5 minutes + for guild in client.guilds: + if random.random() < 0.01: #should be 1% + members = [m for m in guild.members if not m.bot] + if dm_sent: + dm_sent = False + continue + if not members: + continue + victim = random.choice(members) + payload = build_payload( + system_prompt, + f"System: you are sending a dm to {victim}, write a long, playful story or an over-the-top pun monologue about anything. " + "It should sound like something faggot-chan would post when she's bored — chaotic, funny, and full of personality (at least 20 words)." + "keep in mind this is a dm so make it a bit personal" + ) + ok, result = await call_main(payload) + if not ok: + await handle_error(result, notify_user=victim) + continue + try: #sends the dm + dm = await victim.create_dm() + async with dm.typing(): + await dm.send(result) + dm_sent = True + log_message(f"[{timestamp()}]Faggot-chan sent a random dm to {victim}: {result}") + print(f"[{timestamp()}] random dm to {victim}: {result}") + except Exception as e: + await handle_error(f"[{timestamp()}] failed to send dm to {victim}: {e}", notify_user=victim) + +async def random_message(): + await client.wait_until_ready() + while not client.is_closed(): + await asyncio.sleep(240) #every 4 minutes + if time.time() - last_server_message < QUIET_COOLDOWN: #only send if server has been quiet for 10 mins + continue + if random.random() < 0.01: #1% + for guild in client.guilds: + channel = discord.utils.get(guild.text_channels, name="general") + if channel: + payload = build_payload( + system_prompt, + "System: write a long, playful story or an over-the-top pun monologue about cheese. or anything random" + "it should sound like something faggot-chan would post when she's bored." + "chaotic, funny, and full of personality (at least 15 words and no more than 20)" + ) + ok, result = await call_main(payload) + if not ok: + await handle_error(result, notify_channel=channel) + continue + async with channel.typing(): + await channel.send(result) + log_message(f"[{timestamp()}] random message: {result}") + print(f"[{timestamp()}] random message: {result}") + +async def on_ready(): + client.loop.create_task(random_message()) + client.loop.create_task(random_dm()) + print(f"faggot-chan is active as {client.user}") + + +async def on_random(message): + + RNG = random.randint(1,3) + + if message.author == client.user: + return + + + if not is_member_in_server(message.author): + return + + if str(message.author.id) in block_id: + return + + if not message.guild or message.guild.id != server: + return + + if message.channel.name not in ["general", "announcements"]: + return + + mentioned = client.user.mentioned_in(message) + + #steal images and gifs and stuff (always do this regardless of what else fires) + media_urls = [ + a.url for a in message.attachments + if a.content_type and a.content_type.startswith(("image/", "video/")) + ] + + for embed in message.embeds: + if embed.image and embed.image.url: + media_urls.append(embed.image.url) + if embed.video and embed.video.url: + media_urls.append(embed.video.url) + + for url in media_urls: + steal_media(f"[{timestamp()}] saved media: {url}") + + #stickers - early exit, skip ai generation + stickers = list(message.guild.stickers) + if stickers and not mentioned and random.random() < 0.15: #15% chance of sending a sticker + chosen = random.choice(stickers) + try: + await message.channel.send(stickers=[chosen]) + log_message(f"[{timestamp()}] Faggot-chan sent sticker: {chosen.name}") + print(f"[{timestamp()}] Faggot-chan sent sticker: {chosen.name}") + except websockets.exceptions.ConnectionClosedOK: + pass + except Exception as e: + log_error(f"[{timestamp()}] error: {e}") + await message.reply("someone tell <@1000000000000001> there is a problem with my ai") + print(f"Sticker send failed: {e}") + return + + emojis = [str(e) for e in message.guild.emojis] + if emojis and not mentioned and random.random() < 0.2: #20% of reacting - early exit + try: + await message.add_reaction(random.choice(emojis)) + except websockets.exceptions.ConnectionClosedOK: + pass + except Exception as e: + log_error(f"[{timestamp()}] error: {e}") + await message.reply("someone tell <@1000000000000001> there is a problem with my ai") + print(f"Emoji reaction failed: {e}") + return + + if random.random() < 0.1 and saved_media_urls: #10% of sending a stolen image - early exit, uses live set + chosen = random.choice(list(saved_media_urls)) + await message.channel.send(chosen) + log_message(f"[{timestamp()}] Faggot-chan sent media: {chosen}") + print(f"[{timestamp()}] Faggot-chan sent media: {chosen}") + return + + +async def on_message(message): + global dm_sent, interaction_count, last_server_message + if message.author == client.user: + return + + if not is_member_in_server(message.author): + return + + # update last activity so random_message knows the server isn't quiet + last_server_message = time.time() + + if str(message.author.id) in block_id: + return + + # always detect replies + replied_message = None + replied_to_bot = False + if message.reference: + try: + replied_message = message.reference.resolved or await message.channel.fetch_message(message.reference.message_id) + if replied_message: + if replied_message.author == client.user: + replied_to_bot = True + log_message(f'[{timestamp()}] {message.author} replied to {replied_message.author} saying: "{replied_message.content}" with reply: "{message.content}"') + else: + log_message(f'[{timestamp()}] {message.author} said: {message.content}') + except Exception as e: + log_error(f"[{timestamp()}] failed to resolve reply: {e}") + log_message(f'[{timestamp()}] {message.author} said: {message.content}') + else: + log_message(f'[{timestamp()}] {message.author} said: {message.content}') + + interaction_count += 1 + rebuild = (interaction_count % 150 == 0) + + if message.guild is None: #dms now should work + if str(message.author.id) in block_id: + return + dm_sent = False + if replied_message: + user_input = f'{message.author} replied to {replied_message.author} saying: "{replied_message.content}" with reply: "{message.content}"' + else: + user_input = f"{message.author} said in DM: {message.content}" + payload = build_payload(system_prompt, user_input, rebuild) + ok, result = await call_main(payload) + if not ok: + await handle_error(result, notify_user=message.author) + return + try: + async with message.channel.typing(): + await message.reply(result) + print(f"[{timestamp()}] DM from {message.author}: {message.content} Bot replied in DM: {result}") + log_message(f"[{timestamp()}] DM from {message.author}: {message.content}") + log_message(f"[{timestamp()}] Faggot-chan replied in DM: {result}") + except Exception as e: + await handle_error(f"[{timestamp()}] failed to send DM reply: {e}", notify_user=message.author) + return + + if message.channel.name not in ["general", "announcements"]: + return + + mentioned = client.user.mentioned_in(message) + + # if it replied to bot, treat it as a mention automatically + if replied_to_bot: + mentioned = True + + if not mentioned and random.random() > 0.05: #5% chance of replying if not mentiond + return + + if replied_message: + user_input = f'{message.author} replied to {replied_message.author} saying: "{replied_message.content}" with reply: "{message.content}"' + else: + user_input = f"{message.author} said: {message.content}" + + payload = build_payload(system_prompt, user_input, rebuild) + ok, result = await call_main(payload) + if not ok: + await handle_error(result, notify_channel=message.channel) + return + + try: + async with message.channel.typing(): + await message.reply(result) + print(f"[{timestamp()}] {message.author} said: {message.content}\n[{timestamp()}] Faggot-chan said: {result}") + log_message(f"[{timestamp()}] {message.author} said: {message.content}\n[{timestamp()}] Faggot-chan said: {result}") + except Exception as e: + await handle_error(f"[{timestamp()}] failed to send reply: {e}", notify_channel=message.channel) + + await on_random(message) + +client.run(TOKEN) |
