import os import re import math from slack_bolt.app.async_app import AsyncApp from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler from dotenv import load_dotenv from .db import init_db, get_user, add_vote, get_vote_counts, get_user_vote, get_deployment, update_deployment_status from .utils import generate_voting_blocks from .auth import app as fastapi_app from .botnet import deploy_message, execute_deployment import threading import asyncio import uvicorn load_dotenv() VOTE_PATTERN = re.compile(r"^vote_(approve|reject)_(\d+)$") selfbot_token = os.environ.get("SLACK_SELFBOT_OAUTH_XOXP") app_token = os.environ.get("SLACK_APP_TOKEN") selfbot_user_id = os.environ.get("SELFBOT_USER_ID") app = AsyncApp(token=selfbot_token) @app.message("") async def handle_messages(message, say): try: if message.get("channel_type") != "im": return if message.get("user") == selfbot_user_id: return if message.get("user") not in ["U0AKCBZHHMH"]: await say("Botnet is temporarily disabled for users other than <@U0AKCBZHHMH> for debugging and the safety of all OAuth-authorized users. Please DM <@U0AKCBZHHMH> if this is not resolved within a few days.") return text = message.get("text", "").strip() if not text.startswith("botnet"): # yay more scuffed arbitrary stuff text = "botnet help" parts = text.split(" ") if len(parts) == 1: # scuffed as hell, just sets it to help arbitrarily so it gets caught later LMAO # too bad, if it works dont touch it parts = ['botnet', 'help'] command = parts[1] args = parts[2:] if command == "help": await say("*Botnet Commands*:\n---\n*botnet help*: shows this message\n*botnet deploy *: sends a message into the deploy queue for review/voting\n*botnet approve *: approve Botnet to function in a channel (must be CM)", mrkdwn=True) elif command == "deploy": if not args: await say("Please provide a message to deploy!") return message_text = " ".join(args) result = await deploy_message(message.get("user"), message_text) if result["success"]: await say(f"^_^ {result['message']} (Deployment #{result['deployment_id']})") else: await say(f"T_T {result['error']}") elif command == "approve": await say("Not implemented yet") else: await say(f"Unknown command: {command}") except Exception as e: print(f"something broke, good luck LMAO {e}") @app.action(VOTE_PATTERN) async def handle_vote(ack, respond, logger, context, body, client): await ack() action_id = body["actions"][0]["action_id"] match = VOTE_PATTERN.match(action_id) if not match: return vote_type = match.group(1) deployment_id = int(match.group(2)) user_id = body["user"]["id"] channel_id = body["channel"]["id"] user = await get_user(user_id) if not user: # await respond(f"You must join the botnet first!", response_type="ephemeral") await client.chat_postEphemeral(channel=channel_id, user=user_id, text="You must join the botnet first!") return deployment = await get_deployment(deployment_id) if not deployment or deployment["status"] != "pending": await client.chat_postEphemeral(channel=channel_id,user=user_id,text="Voting for this deployment has expired!") await add_vote(deployment_id, user_id, vote_type) approve_count, reject_count = await get_vote_counts(deployment_id) active_users = await get_active_users() total_members = len(active_users) THRESHOLD = math.ceil(max(3, min(15, int(total_members * 0.2)))) # min 3, max 15 - use 20% total members if approve_count >= THRESHOLD: await update_deployment_status(deployment_id, "approved") await execute_deployment(deployment_id) blocks = generate_voting_blocks(deployment_id, deployment["message"], approve_count, reject_count, False) await respond(blocks=blocks,text="An error occurred rendering this text.") await client.chat_postMessage(channel=channel_id, text=f"Deployment #{deployment_id} was approved! ^_^") elif reject_count >= THRESHOLD: await update_deployment_status(deployment_id, "rejected") blocks = generate_voting_blocks(deployment_id, deployment["message"], approve_count, reject_count, False) await respond(blocks=blocks,text="An error occurred rendering this text.") await client.chat_postMessage(channel=channel_id, text=f"Deployment #{deployment_id} was rejected! T_T") else: blocks = generate_voting_blocks(deployment_id, deployment["message"], approve_count, reject_count, True) await respond(blocks=blocks,text="An error occurred rendering this text.") @app.event("message") async def shut_up_slack_bolt(): # shut up that annoying debug warning that this event is unhandled pass async def main(): await init_db() def run_uvicorn(): uvicorn.run(fastapi_app, host="0.0.0.0", port=8000) thread = threading.Thread(target=run_uvicorn, daemon=True) thread.start() handler = AsyncSocketModeHandler(app, app_token=app_token) await handler.start_async() if __name__ == "__main__": # handler = SocketModeHandler(app, app_token=app_token) # handler.start() asyncio.run(main())