diff options
| author | Arslaan Pathan <[email protected]> | 2026-07-17 00:03:03 +1200 |
|---|---|---|
| committer | Arslaan Pathan <[email protected]> | 2026-07-17 00:03:03 +1200 |
| commit | 2eb0e1197b40c07de4a57701504f77345d77dc36 (patch) | |
| tree | 53e6fe85f9ce6be91ab0cddc9a1dfb44969d35af | |
| parent | bc715b12204714dc8046f07387210852026b47b9 (diff) | |
| download | slack-botnet-2eb0e1197b40c07de4a57701504f77345d77dc36.tar.xz slack-botnet-2eb0e1197b40c07de4a57701504f77345d77dc36.zip | |
IT WORKS YES YES YES IM SAVED
| -rw-r--r-- | src/botnet.py | 6 | ||||
| -rw-r--r-- | src/db.py | 39 | ||||
| -rw-r--r-- | src/main.py | 59 | ||||
| -rw-r--r-- | src/utils.py | 67 |
4 files changed, 143 insertions, 28 deletions
diff --git a/src/botnet.py b/src/botnet.py index 225f7e9..c9c6fee 100644 --- a/src/botnet.py +++ b/src/botnet.py @@ -76,7 +76,7 @@ async def deploy_message(user_id: str, message: str): } daily_count = await get_daily_user_deployment_count(user_id) - if daily_count >= DAILY_LIMIT and user_id not in ["U0AKCBZHHMH"]: + if daily_count >= DAILY_LIMIT: return { "success": False, "error": f"You've reached your daily limit of {DAILY_LIMIT} messages!" @@ -122,12 +122,12 @@ async def execute_deployment(deployment_id: int): for user in users: try: client = AsyncWebClient(token=user["oauth_token"]) - channel = await get_random_channel(user["oauth_token"]) + channel = await get_random_channel(user["oauth_token"], user["slack_id"]) if not channel: print(f"No channels found for {user['slack_id']}, using #botnet") channel_id = "C0BGMV1CAQG" else: - channel_id = channel["id"] + channel_id = channel response = await client.chat_postMessage( channel=channel_id, # put it in #botnet for now, make it detect recent thread with CM approval yada yada later text=message, @@ -47,6 +47,14 @@ async def init_db(): PRIMARY KEY (deployment_id, voter_id) ) """) + await db.execute(""" + CREATE TABLE IF NOT EXISTS ChannelApprovals ( + channel_id TEXT PRIMARY KEY, + approved_by TEXT NOT NULL, + approved_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (approved_by) REFERENCES Users(slack_id) + ) + """) await db.commit() async def add_user(slack_id: str, oauth_token: str): @@ -175,3 +183,34 @@ async def update_voting_message_ts(deployment_id: int, message_ts: str): (message_ts, deployment_id) ) await db.commit() + +async def approve_channel(channel_id: str, approver_id: str): + async with aiosqlite.connect(DB_PATH) as db: + await db.execute( + "INSERT OR REPLACE INTO ChannelApprovals (channel_id, approved_by) VALUES (?, ?)", + (channel_id, approver_id) + ) + await db.commit() + +async def is_channel_approved(channel_id: str) -> bool: + async with aiosqlite.connect(DB_PATH) as db: + cursor = await db.execute( + "SELECT channel_id FROM ChannelApprovals WHERE channel_id = ?", + (channel_id,) + ) + row = await cursor.fetchone() + return row is not None + +async def get_approved_channels() -> list[str]: + async with aiosqlite.connect(DB_PATH) as db: + cursor = await db.execute("SELECT channel_id FROM ChannelApprovals") + rows = await cursor.fetchall() + return [row[0] for row in rows] + +async def revoke_channel_approval(channel_id: str): + async with aiosqlite.connect(DB_PATH) as db: + await db.execute( + "DELETE FROM ChannelApprovals WHERE channel_id = ?", + (channel_id,) + ) + await db.commit() diff --git a/src/main.py b/src/main.py index 0d63190..c2980a7 100644 --- a/src/main.py +++ b/src/main.py @@ -4,8 +4,8 @@ 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 -from .utils import generate_voting_blocks +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 @@ -31,9 +31,9 @@ async def handle_messages(message, say): 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 + # 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() @@ -51,7 +51,7 @@ async def handle_messages(message, say): args = parts[2:] if command == "help": - await say("*Botnet Commands*:\n---\n*botnet help*: shows this message\n*botnet deploy <message>*: sends a message into the deploy queue for review/voting\n*botnet approve <channel>*: approve Botnet to function in a channel (must be CM)", mrkdwn=True) + await say("*Botnet Commands*:\n---\n*botnet help*: shows this message\n*botnet deploy <message>*: sends a message into the deploy queue for review/voting\n*botnet approve <channel>*: approve Botnet to function in a channel (must be CM)\n*botnet revoke <channel>*: revoke approval for Botnet to unction in a channel (must be CM)", mrkdwn=True) elif command == "deploy": if not args: await say("Please provide a message to deploy!") @@ -63,7 +63,49 @@ async def handle_messages(message, say): else: await say(f"T_T {result['error']}") elif command == "approve": - await say("Not implemented yet") + 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!") else: await say(f"Unknown command: {command}") except Exception as e: @@ -100,8 +142,7 @@ async def handle_vote(ack, respond, logger, context, body, client): 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 - THRESHOLD = 2 # Debug hardcoded + 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: diff --git a/src/utils.py b/src/utils.py index a42539f..ec614d6 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,5 +1,7 @@ import random +import httpx from slack_sdk.web.async_client import AsyncWebClient +from .db import get_approved_channels def generate_voting_blocks(deployment_id: int, message: str, approve_count: int, reject_count: int, voting_enabled: bool): blocks = [ @@ -47,24 +49,57 @@ def generate_voting_blocks(deployment_id: int, message: str, approve_count: int, return blocks -async def get_random_channel(oauth_token: str): - client = AsyncWebClient(token=oauth_token) +async def get_random_channel(user_oauth_token: str, user_id: str) -> str | None: + approved = await get_approved_channels() + print(f"DEBUG get_random_channel: approved = {approved}") + + if not approved: + print("DEBUG get_random_channel: No approved channels") + return None + + random.shuffle(approved) + print(f"DEBUG get_random_channel: shuffled approved = {approved}") + + for channel_id in approved: + print(f"DEBUG get_random_channel: checking channel {channel_id} for user {user_id}") + is_member = await user_is_in_channel(user_oauth_token, channel_id, user_id) + print(f"DEBUG get_random_channel: is_member = {is_member}") + if is_member: + print(f"DEBUG get_random_channel: FOUND! returning {channel_id}") + return channel_id + + print("DEBUG get_random_channel: No channels found") + return None +async def user_is_in_channel(oauth_token: str, channel_id: str, user_id: str) -> bool: + client = AsyncWebClient(token=oauth_token) + try: - response = await client.conversations_list(types="public_channel", limit=200, exclude_archived=True) - - channels = [c for c in response.get("channels", []) if c.get("is_member", False)] + print(f"DEBUG user_is_in_channel: calling conversations_members for {channel_id}") + response = await client.conversations_members(channel=channel_id) + print(f"DEBUG user_is_in_channel: response ok = {response.get('ok')}") + + members = response.get("members", []) + print(f"DEBUG user_is_in_channel: members count = {len(members)}") + print(f"DEBUG user_is_in_channel: looking for {user_id} in members") + + return user_id in members + except Exception as e: + print(f"DEBUG user_is_in_channel: EXCEPTION - {e}") + return False - if not channels: - return None +async def is_user_cm(channel_id: str, user_id: str): + try: + async with httpx.AsyncClient() as client: + response = await client.get(f"https://flaron.halceon.dev/channel/{channel_id}") + + if response.status_code != 200: + return False - channel_approved = False - while not channel_approved: - channel = random.choice(channels) - # FIXME: Check if CM-approved here - channel_approved = True + data = response.json() - return channel - except Exception as e: - print(f"random channel is broken, we might be cooked: {e}") - return None + managers = data.get("managers", []) + return user_id in managers + except Exception in e: + print(f"flaron api failed T_T {e}") + return False |
