aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/botnet.py67
-rw-r--r--src/db.py99
-rw-r--r--src/main.py76
3 files changed, 210 insertions, 32 deletions
diff --git a/src/botnet.py b/src/botnet.py
new file mode 100644
index 0000000..46b59d5
--- /dev/null
+++ b/src/botnet.py
@@ -0,0 +1,67 @@
+from .db import create_deployment, get_daily_user_deployment_count, get_deployment, update_deployment_status, get_active_users, get_user, add_deployment_message, get_deployment_messages
+from slack_sdk.web.async_client import AsyncWebClient
+import os
+from dotenv import load_dotenv
+
+DAILY_LIMIT = 2
+
+load_dotenv()
+selfbot_token = os.environ.get("SLACK_SELFBOT_OAUTH_XOXP")
+
+async def deploy_message(user_id: str, message: str):
+ user = await get_user(user_id)
+ if not user:
+ return {
+ "success": False,
+ "error": "You must be part of the botnet to deploy messages!"
+ }
+
+ daily_count = await get_daily_user_deployment_count(user_id)
+ if daily_count >= DAILY_LIMIT and user_id not in ["U0AKCBZHHMH"]:
+ return {
+ "success": False,
+ "error": f"You've reached your daily limit of {DAILY_LIMIT} messages!"
+ }
+
+ deployment_id = await create_deployment(user_id, message)
+
+ print(f"DEBUG: Executing the deployment {deployment_id} on arrival - REMOVE THIS LATER!")
+ await execute_deployment(deployment_id)
+
+ return {
+ "success": True,
+ "deployment_id": deployment_id,
+ "message": "Your message has been deployed!"
+ }
+
+async def execute_deployment(deployment_id: int):
+ deployment = await get_deployment(deployment_id)
+ if not deployment:
+ return
+
+ users = await get_active_users()
+ message = deployment["message"]
+
+ for user in users:
+ try:
+ client = AsyncWebClient(token=user["oauth_token"])
+ response = await client.chat_postMessage(
+ channel="C0BGMV1CAQG", # put it in #botnet for now, make it detect recent thread with CM approval yada yada later
+ text=message,
+ as_user=True
+ )
+ await add_deployment_message(deployment_id=deployment_id, user_id=user["slack_id"], channel_id="C0BGMV1CAQG", message_id=response["ts"])
+ except Exception as e:
+ print(f"Failed to send #{deployment_id} on behalf of {user['slack_id']}: {e}")
+
+ try:
+ client = AsyncWebClient(token=selfbot_token)
+ await client.chat_postMessage(
+ channel="C0BGMV1CAQG",
+ text=f"Deployment #{deployment_id} deployed.\n\nMessage: {message}",
+ as_user=True
+ )
+ except Exception as e:
+ print(f"Failed to send deployment status message: {e}")
+
+ await update_deployment_status(deployment_id, "deployed")
diff --git a/src/db.py b/src/db.py
index 6dd5cad..9e1619a 100644
--- a/src/db.py
+++ b/src/db.py
@@ -1,4 +1,5 @@
from slack_sdk.web.async_client import AsyncWebClient
+from datetime import datetime
import aiosqlite
DB_PATH = "botnet.db"
@@ -12,6 +13,28 @@ async def init_db():
joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
+ await db.execute("""
+ CREATE TABLE IF NOT EXISTS Deployments (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ message TEXT NOT NULL,
+ submitted_by TEXT NOT NULL,
+ status TEXT DEFAULT 'pending',
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ deployed_at TIMESTAMP
+ )
+ """)
+ await db.execute("""
+ CREATE TABLE IF NOT EXISTS DeploymentMessages (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ deployment_id INTEGER NOT NULL,
+ user_id TEXT NOT NULL,
+ message_id TEXT NOT NULL,
+ channel_id TEXT NOT NULL,
+ sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (deployment_id) REFERENCES Deployments(id) ON DELETE CASCADE,
+ FOREIGN KEY (user_id) REFERENCES Users(slack_id)
+ )
+ """)
await db.commit()
async def add_user(slack_id: str, oauth_token: str):
@@ -30,3 +53,79 @@ async def add_user(slack_id: str, oauth_token: str):
)
except Exception as e:
print(f"Failed to send welcome message: {e}")
+
+async def create_deployment(user_id: str, message: str) -> int:
+ async with aiosqlite.connect(DB_PATH) as db:
+ cursor = await db.execute(
+ "INSERT INTO Deployments (message, submitted_by) VALUES (?, ?)",
+ (message, user_id)
+ )
+ await db.commit()
+ return cursor.lastrowid
+
+async def get_daily_user_deployment_count(user_id: str) -> int:
+ from datetime import datetime
+ today = datetime.now().date().isoformat()
+ async with aiosqlite.connect(DB_PATH) as db:
+ cursor = await db.execute(
+ """SELECT COUNT(*) FROM Deployments
+ WHERE submitted_by = ? AND date(created_at) = ?""",
+ (user_id, today)
+ )
+ row = await cursor.fetchone()
+ return row[0] if row else 0
+
+async def get_deployment(deployment_id: int) -> dict | None:
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ cursor = await db.execute(
+ "SELECT * FROM Deployments WHERE id = ?",
+ (deployment_id,)
+ )
+ row = await cursor.fetchone()
+ return dict(row) if row else None
+
+async def update_deployment_status(deployment_id: int, status: str):
+ async with aiosqlite.connect(DB_PATH) as db:
+ await db.execute(
+ "UPDATE Deployments SET status = ? WHERE id = ?",
+ (status, deployment_id)
+ )
+ await db.commit()
+
+async def add_deployment_message(deployment_id: int, user_id: str, channel_id: str, message_id: str):
+ async with aiosqlite.connect(DB_PATH) as db:
+ await db.execute(
+ """INSERT INTO DeploymentMessages
+ (deployment_id, user_id, channel_id, message_id)
+ VALUES (?, ?, ?, ?)""",
+ (deployment_id, user_id, channel_id, message_id)
+ )
+ await db.commit()
+
+async def get_deployment_messages(deployment_id: int) -> list[dict]:
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ cursor = await db.execute(
+ "SELECT * FROM DeploymentMessages WHERE deployment_id = ?",
+ (deployment_id,)
+ )
+ rows = await cursor.fetchall()
+ return [dict(row) for row in rows]
+
+async def get_user(slack_id: str) -> dict | None:
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ cursor = await db.execute(
+ "SELECT * FROM Users WHERE slack_id = ?",
+ (slack_id,)
+ )
+ row = await cursor.fetchone()
+ return dict(row) if row else None
+
+async def get_active_users() -> list[dict]:
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ cursor = await db.execute("SELECT * FROM Users")
+ rows = await cursor.fetchall()
+ return [dict(row) for row in rows]
diff --git a/src/main.py b/src/main.py
index 8b0e692..02375cc 100644
--- a/src/main.py
+++ b/src/main.py
@@ -4,6 +4,7 @@ from slack_bolt.adapter.socket_mode.async_handler import AsyncSocketModeHandler
from dotenv import load_dotenv
from .db import init_db
from .auth import app as fastapi_app
+from .botnet import deploy_message
import threading
import asyncio
import uvicorn
@@ -18,39 +19,50 @@ app = AsyncApp(token=selfbot_token)
@app.message("")
async def handle_messages(message, say):
- if message.get("channel_type") != "im":
- return
-
- if message.get("user") == selfbot_user_id:
- return
+ try:
+ if message.get("channel_type") != "im":
+ return
+
+ if message.get("user") == selfbot_user_id:
+ return
- if message.get("user") != "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", "").lower().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 <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)
- elif command == "deploy":
- await say("Not implemented yet")
- elif command == "approve":
- await say("Not implemented yet")
- else:
- await say(f"Unknown command: {command}")
+ if message.get("user") not in ["U0AKCBZHHMH", "U0BG82V0CP6"]:
+ 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 <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)
+ 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.event("message")
async def shut_up_slack_bolt():