From 683cbe10a0f408b0f9e3556d35de31f97276ba5a Mon Sep 17 00:00:00 2001 From: zeyadhost Date: Thu, 26 Mar 2026 00:06:29 +0200 Subject: [PATCH] Added typing indicator block via injection --- bot/bot.py | 5 ++ bot/commands/mod.py | 13 +++- bot/commands/util.py | 29 +++++++++ utils/typing_blocker.py | 127 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 utils/typing_blocker.py diff --git a/bot/bot.py b/bot/bot.py index 5d27271..ece76f6 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -11,6 +11,7 @@ os.environ["SSL_CERT_FILE"] = certifi.where() from discord.ext import commands, tasks + from utils import files from utils.config import Config import utils.console as console @@ -42,6 +43,9 @@ def __init__(self, controller): self.files = files self.allowed_users = [] self.allowed_cogs = ["fun", "text", "general", "img", "info"] + + + async def _setup_scripts(self): scripts = self.cfg.get_scripts() @@ -133,6 +137,7 @@ async def on_message(self, message): async def on_ready(self): try: self.start_time = time.time() + self.cfg.add_token(self.cfg.get("token"), self.user.name, self.user.id) await self.load_cogs() diff --git a/bot/commands/mod.py b/bot/commands/mod.py index a54fd99..cea064f 100644 --- a/bot/commands/mod.py +++ b/bot/commands/mod.py @@ -54,7 +54,18 @@ def is_me(m): @commands.command(name="dmpurge", description="Purge a number of messages in a DM.", usage="[number] [user id]") async def dmpurge(self, ctx, number: int, user_id: int): - user = self.bot.get_user(user_id) + if self.cfg.get("message_settings")["edit_og"]: + await cmdhelper.send_message(ctx, { + "title": "DM Purge", + "description": f"Purging {number} messages..." + }) + else: + try: + await ctx.message.delete() + except: + pass + + user = discord.utils.get(self.bot.users, id=user_id) if isinstance(user, discord.User): latest_msg = [msg async for msg in user.dm_channel.history(limit=1)][0] diff --git a/bot/commands/util.py b/bot/commands/util.py index 9d3f6c5..0e46251 100644 --- a/bot/commands/util.py +++ b/bot/commands/util.py @@ -304,6 +304,35 @@ async def sessionspoofer(self, ctx, device = None): cfg.save() await self.restart(ctx, no_response=True) + @commands.command(name="st", description="Toggle silent typing.", usage="", aliases=["silenttype", "silenttyping"]) + async def st(self, ctx): + from utils import typing_blocker + + cfg = self.cfg + current = cfg.get("message_settings").get("silent_typing", False) + new_val = not current + + cfg.set("message_settings.silent_typing", new_val) + cfg.save() + + typing_blocker.set_enabled(new_val) + + status = "enabled" if new_val else "disabled" + desc = f"Silent typing {status}" + + if not typing_blocker.is_injected(): + success, msg = typing_blocker.inject() + if success: + desc += "\nRestart Discord for injection to take effect." + else: + desc += f"\nInjection failed: {msg}" + + await cmdhelper.send_message(ctx, { + "title": "Silent Typing", + "description": desc, + "colour": "#00ff00" if new_val else "#ff0000" + }) + @commands.command(name="uptime", description="View the bot's uptime", usage="") async def uptime(self, ctx): uptime = time.time() - self.bot.start_time diff --git a/utils/typing_blocker.py b/utils/typing_blocker.py new file mode 100644 index 0000000..7e0ba1e --- /dev/null +++ b/utils/typing_blocker.py @@ -0,0 +1,127 @@ +import os +import glob +import re + +_FLAG_FILE = os.path.join(os.path.expanduser("~"), ".ghost_silent_typing") + +_INJECTED_JS = r"""const _ghostFs = require('fs'); +const _ghostPath = require('path'); +const _ghostOs = require('os'); +const _ghostFlagPath = _ghostPath.join(_ghostOs.homedir(), '.ghost_silent_typing'); +const _ghostElectron = require('electron'); + +function _ghostInstallTypingBlock(ses) { + ses.webRequest.onBeforeRequest({ urls: ['https://*.discord.com/*'] }, (details, callback) => { + if (details.url.endsWith('/typing') && details.method === 'POST') { + try { + if (_ghostFs.existsSync(_ghostFlagPath)) { + callback({ cancel: true }); + return; + } + } catch (e) {} + } + callback({}); + }); +} + +_ghostElectron.app.on('ready', () => { + _ghostInstallTypingBlock(_ghostElectron.session.defaultSession); +}); + +_ghostElectron.app.on('browser-window-created', (e, w) => { + if (w.webContents.session !== _ghostElectron.session.defaultSession) { + _ghostInstallTypingBlock(w.webContents.session); + } +});""" + + +import sys + +def find_discord_index(): + app_dirs = [] + + if os.name == "nt": + base = os.environ.get("LOCALAPPDATA", "") + if base: + for c in ["Discord", "DiscordPTB", "DiscordCanary"]: + app_dirs.extend(glob.glob(os.path.join(base, c, "app-*"))) + elif sys.platform == "darwin": + base = os.path.join(os.path.expanduser("~"), "Library", "Application Support") + for c in ["discord", "discordptb", "discordcanary"]: + app_dirs.extend(glob.glob(os.path.join(base, c, "[0-9]*.[0-9]*.[0-9]*"))) + + app_dirs.sort(reverse=True) + + for app_dir in app_dirs: + if os.name == "nt": + pattern = os.path.join(app_dir, "modules", "discord_desktop_core-*", "discord_desktop_core", "index.js") + else: + pattern = os.path.join(app_dir, "modules", "discord_desktop_core", "index.js") + + matches = glob.glob(pattern) + if matches: + return matches[0] + + return None + + +def is_injected(index_path=None): + if index_path is None: + index_path = find_discord_index() + if index_path is None or not os.path.isfile(index_path): + return False + + with open(index_path, "r", encoding="utf-8") as f: + return _INJECTED_JS in f.read() + + +def inject(index_path=None): + if index_path is None: + index_path = find_discord_index() + if index_path is None or not os.path.isfile(index_path): + return False, "Discord installation not found" + + if is_injected(index_path): + return True, "" + + with open(index_path, "r", encoding="utf-8") as f: + original_content = f.read() + + new_content = _INJECTED_JS + "\n" + original_content + + with open(index_path, "w", encoding="utf-8") as f: + f.write(new_content) + + return True, "" + + +def uninject(index_path=None): + if index_path is None: + index_path = find_discord_index() + if index_path is None or not os.path.isfile(index_path): + return False, "Discord installation not found" + + if not is_injected(index_path): + return True, "" + + with open(index_path, "r", encoding="utf-8") as f: + content = f.read() + + cleaned = content.replace(_INJECTED_JS + "\n", "") + + with open(index_path, "w", encoding="utf-8") as f: + f.write(cleaned) + + return True, "" + + +def set_enabled(enabled): + if enabled: + with open(_FLAG_FILE, "w") as f: + f.write("1") + elif os.path.isfile(_FLAG_FILE): + os.remove(_FLAG_FILE) + + +def is_enabled(): + return os.path.isfile(_FLAG_FILE)