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, get_active_users, approve_channel, is_channel_approved, revoke_channel_approval from .utils import generate_voting_blocks, is_user_cm 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)\n*botnet revoke *: revoke approval for Botnet to unction in a channel (must be CM)\n*botnet oauth*: sends you an OAuth link to join the botnet", 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": user_id = message.get("user") if not args: await say(f"Please provide a channel to approve!") return channel_pattern = re.compile(r"^<#([A-Z0-9]+)(?:\|[^>]+)?>$") match = channel_pattern.match(args[0].strip()) channel_id = match.group(1) if not channel_id: await say(f"Please provide a valid channel.") return if not await is_user_cm(channel_id, user_id): await say(f"You must be a CM of that channel to perform this action!") return await approve_channel(channel_id, user_id) await say(f"^_^ Approved channel {args[0].strip()}!") elif command == "revoke": user_id = message.get("user") if not args: await say(f"Please provide a channel to revoke approval!") return channel_pattern = re.compile(r"^<#([A-Z0-9]+)(?:\|[^>]+)?>$") match = channel_pattern.match(args[0].strip()) channel_id = match.group(1) if not channel_id: await say(f"Please provide a valid channel.") return if not await is_channel_approved(channel_id): await say(f"T_T Channel {args[0].strip()} is not currently approved for Botnet!") return if not await is_user_cm(channel_id, user_id): await say(f"You must be a CM of that channel to perform this action!") return await revoke_channel_approval(channel_id) await say(f"T_T Channel {args[0].strip()} has been revoked approval for Botnet!") elif command == "oauth": await say(f"{os.environ.get('SLAKC_OAUTH_PREFIX')}/slack/oauth/install") 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 print(f"DEBUG: Threshold is {THRESHOLD}") 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())