├── requirements.txt ├── config.json ├── .gitattributes ├── discord-appicon-stolen.png ├── run.sh ├── run.bat ├── README.md └── watson.py /requirements.txt: -------------------------------------------------------------------------------- 1 | discord -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "discord_bot_token": "REPLACE WITH YOUR BOT TOKEN" 3 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /discord-appicon-stolen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RocketGod-git/watson/HEAD/discord-appicon-stolen.png -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if the virtual environment already exists 4 | if [ ! -d "sherlock-venv" ]; then 5 | echo "Creating a virtual environment..." 6 | python3 -m venv sherlock-venv 7 | 8 | # Activate the virtual environment 9 | source sherlock-venv/bin/activate 10 | 11 | echo "Installing the required packages..." 12 | pip install -r requirements.txt 13 | else 14 | # Activate the virtual environment 15 | source sherlock-venv/bin/activate 16 | fi 17 | 18 | # Run the bot 19 | python3 watson.py -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM Check if the virtual environment already exists 4 | if not exist "sherlock-venv\" ( 5 | echo Creating a virtual environment... 6 | python -m venv sherlock-venv 7 | 8 | REM Activate the virtual environment 9 | call sherlock-venv\Scripts\activate 10 | 11 | echo Installing the required packages... 12 | pip install -r requirements.txt 13 | ) else ( 14 | REM Activate the virtual environment 15 | call sherlock-venv\Scripts\activate 16 | ) 17 | 18 | REM Run the bot 19 | python watson.py 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Watson - A Discord Bot for Sherlock 2 | 3 | Watson is a Discord bot designed to interface with the [Sherlock project](https://github.com/sherlock-project/sherlock). It allows users to search for usernames on various social networks directly from Discord using the power of Sherlock. 4 | 5 | ## Setup 6 | 7 | ### Prerequisites 8 | 9 | This bot requires the Sherlock project to function. If you haven't already cloned the Sherlock repository, you can do so with the following command: 10 | 11 | ```bash 12 | git clone https://github.com/sherlock-project/sherlock 13 | ``` 14 | 15 | ### Installation 16 | 17 | 1. Navigate to the `sherlock` directory: 18 | 19 | ```bash 20 | cd sherlock 21 | ``` 22 | 23 | 2. Create a new directory named `discordbot`: 24 | 25 | ```bash 26 | mkdir discordbot 27 | ``` 28 | 29 | 3. Navigate to the `discordbot` directory: 30 | 31 | ```bash 32 | cd discordbot 33 | ``` 34 | 35 | 4. Clone the Watson repository into the `discordbot` directory: 36 | 37 | ```bash 38 | git clone https://github.com/RocketGod-git/watson . 39 | ``` 40 | 41 | 5. Update the `config.json` file with your bot's token. Your `config.json` should look like this: 42 | 43 | ```json 44 | { 45 | "discord_bot_token": "REPLACE WITH YOUR BOT TOKEN" 46 | } 47 | ``` 48 | 49 | ### Running the Bot 50 | 51 | For **Windows** users: 52 | 53 | - Run the `run.bat` file. This will automatically set up a virtual environment, install the required packages on the first run, and then run the bot. 54 | 55 | - Execute the `run.sh` script: 56 | 57 | ```bash 58 | ./run.bat 59 | ``` 60 | 61 | For **Linux** users: 62 | 63 | - Execute the `run.sh` script: 64 | 65 | ```bash 66 | ./run.sh 67 | ``` 68 | 69 | This script will perform the same setup actions as the Windows batch file. 70 | 71 | ## Usage 72 | 73 | Once the bot is running, you can utilize the following slash commands on your Discord server: 74 | 75 | ### `/sherlock` 76 | 77 | Search for a username across various social networks using the Sherlock tool. By default, the search will include NSFW links. 78 | 79 | **Usage**: 80 | 81 | - `/sherlock [username]`: Search for a specific username. 82 | - **Options**: 83 | - `similar`: Check for similar usernames by replacing them with variations (e.g., '_', '-', '.'). 84 | 85 | ### `/help` 86 | 87 | Displays a list of available commands and their descriptions. 88 | 89 | --- 90 | 91 | Thank you for using Watson! If you find any issues or have any feedback, feel free to contribute to the [Watson repository](https://github.com/RocketGod-git/watson). 92 | 93 | ![RocketGod](https://github.com/RocketGod-git/shell-access-discord-bot/assets/57732082/c68635fa-b89d-4f74-a1cb-5b5351c22c98) 94 | -------------------------------------------------------------------------------- /watson.py: -------------------------------------------------------------------------------- 1 | # __________ __ __ ________ .___ 2 | # \______ \ ____ ____ | | __ ____ _/ |_ / _____/ ____ __| _/ 3 | # | _/ / _ \ _/ ___\ | |/ /_/ __ \\ __\/ \ ___ / _ \ / __ | 4 | # | | \( <_> )\ \___ | < \ ___/ | | \ \_\ \( <_> )/ /_/ | 5 | # |____|_ / \____/ \___ >|__|_ \ \___ >|__| \______ / \____/ \____ | 6 | # \/ \/ \/ \/ \/ \/ 7 | # 8 | # Discord bot for Sherlock by RocketGod 9 | # https://github.com/RocketGod-git/watson 10 | 11 | import json 12 | import logging 13 | import platform 14 | import time 15 | import discord 16 | from discord import Embed 17 | import os 18 | import asyncio 19 | import sys 20 | from io import StringIO 21 | import subprocess 22 | 23 | class NoShardResumeFilter(logging.Filter): 24 | def filter(self, record): 25 | if 'discord.gateway' in record.name and 'has successfully RESUMED session' in record.msg: 26 | return False 27 | return True 28 | 29 | discord_gateway_logger = logging.getLogger('discord.gateway') 30 | discord_gateway_logger.addFilter(NoShardResumeFilter()) 31 | 32 | logging.basicConfig(level=logging.INFO) 33 | 34 | def load_config(): 35 | try: 36 | with open('config.json', 'r') as file: 37 | return json.load(file) 38 | except Exception as e: 39 | logging.error(f"Error loading configuration: {e}") 40 | return None 41 | 42 | async def send_message_with_retry(channel, content, max_retries=3): 43 | for attempt in range(max_retries): 44 | try: 45 | await channel.send(content) 46 | return 47 | except discord.errors.Forbidden: 48 | logging.warning(f"Bot doesn't have permission to send messages in channel {channel.id}") 49 | return 50 | except discord.errors.HTTPException as e: 51 | if attempt == max_retries - 1: 52 | logging.error(f"Failed to send message after {max_retries} attempts: {e}") 53 | else: 54 | await asyncio.sleep(1) 55 | 56 | async def run_sherlock_process(args, channel, timeout=300): 57 | sherlock_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 58 | process = await asyncio.create_subprocess_exec( 59 | sys.executable, "-m", "sherlock.sherlock", *args, 60 | stdout=asyncio.subprocess.PIPE, 61 | stderr=asyncio.subprocess.PIPE, 62 | cwd=sherlock_dir 63 | ) 64 | 65 | try: 66 | async def read_stream(stream): 67 | output = [] 68 | while True: 69 | line = await stream.readline() 70 | if not line: 71 | break 72 | line = line.decode().strip() 73 | if line: 74 | await channel.send(line) 75 | output.append(line) 76 | return '\n'.join(output) 77 | 78 | stdout_task = asyncio.create_task(read_stream(process.stdout)) 79 | stderr_task = asyncio.create_task(read_stream(process.stderr)) 80 | wait_task = asyncio.create_task(process.wait()) 81 | 82 | done, pending = await asyncio.wait( 83 | [stdout_task, stderr_task, wait_task], 84 | timeout=timeout, 85 | return_when=asyncio.ALL_COMPLETED 86 | ) 87 | 88 | for task in pending: 89 | task.cancel() 90 | 91 | if wait_task not in done: 92 | process.terminate() 93 | return "", "Process timed out", -1 94 | 95 | stdout = await stdout_task if stdout_task in done else "" 96 | stderr = await stderr_task if stderr_task in done else "" 97 | returncode = await wait_task 98 | 99 | return stdout, stderr, returncode 100 | except asyncio.TimeoutError: 101 | process.terminate() 102 | return "", "Process timed out", -1 103 | except Exception as e: 104 | return "", f"An error occurred: {str(e)}", -1 105 | 106 | async def send_results_as_messages(interaction, filename): 107 | try: 108 | with open(filename, 'r') as f: 109 | content = f.read() 110 | 111 | chunks = [content[i:i+1900] for i in range(0, len(content), 1900)] 112 | 113 | for chunk in chunks: 114 | await interaction.followup.send(f"```\n{chunk}\n```") 115 | except Exception as e: 116 | await interaction.followup.send(f"Error sending results as messages: {str(e)}") 117 | 118 | async def execute_sherlock(interaction, *args): 119 | if not args: 120 | await handle_errors(interaction, "No username provided") 121 | return 122 | 123 | username = args[0] 124 | 125 | sherlock_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 126 | filename = os.path.join(sherlock_dir, f"{username}.txt") 127 | 128 | await interaction.followup.send(f"Searching `{username}` for {interaction.user.mention}") 129 | 130 | sherlock_args = [username, '--nsfw', '--output', filename, '--local'] 131 | 132 | try: 133 | stdout, stderr, returncode = await run_sherlock_process(sherlock_args, interaction.channel) 134 | 135 | if returncode != 0: 136 | error_message = f"Sherlock exited with code {returncode}\n" 137 | if stderr: 138 | error_message += f"Error output:\n```\n{stderr}\n```" 139 | await handle_errors(interaction, error_message) 140 | return 141 | 142 | if stderr: 143 | await interaction.channel.send(f"Warnings occurred:\n```\n{stderr}\n```") 144 | 145 | except Exception as e: 146 | await handle_errors(interaction, f"An error occurred while running Sherlock: {str(e)}") 147 | return 148 | 149 | try: 150 | if os.path.exists(filename): 151 | file_size = os.path.getsize(filename) 152 | if file_size > 8 * 1024 * 1024: 153 | await interaction.channel.send(f"Results file for `{username}` is too large to upload (Size: {file_size / 1024 / 1024:.2f} MB). Sending results as text messages.") 154 | await send_results_as_messages(interaction, filename) 155 | else: 156 | try: 157 | with open(filename, "rb") as f: 158 | await interaction.channel.send(file=discord.File(f, filename=os.path.basename(filename))) 159 | except discord.HTTPException as e: 160 | await interaction.channel.send(f"Error uploading results file: {str(e)}. Sending results as text messages.") 161 | await send_results_as_messages(interaction, filename) 162 | else: 163 | await interaction.channel.send(f"No results file found for `{username}`") 164 | except Exception as e: 165 | await handle_errors(interaction, f"An error occurred while processing results: {str(e)}") 166 | 167 | await interaction.channel.send(f"Finished report on `{username}` for {interaction.user.mention}") 168 | 169 | class aclient(discord.Client): 170 | def __init__(self) -> None: 171 | super().__init__(intents=discord.Intents.default()) 172 | self.tree = discord.app_commands.CommandTree(self) 173 | self.activity = discord.Activity(type=discord.ActivityType.watching, name="/sherlock") 174 | self.discord_message_limit = 2000 175 | 176 | async def handle_errors(interaction, error, error_type="Error"): 177 | error_message = f"{error_type}: {error}" 178 | logging.error(f"Error for user {interaction.user}: {error_message}") 179 | try: 180 | if interaction.response.is_done(): 181 | await interaction.followup.send(error_message) 182 | else: 183 | await interaction.response.send_message(error_message, ephemeral=True) 184 | except discord.HTTPException as http_err: 185 | logging.warning(f"HTTP error while responding to {interaction.user}: {http_err}") 186 | await interaction.followup.send(error_message) 187 | except Exception as unexpected_err: 188 | logging.error(f"Unexpected error while responding to {interaction.user}: {unexpected_err}") 189 | await interaction.followup.send("An unexpected error occurred. Please try again later.") 190 | 191 | def run_discord_bot(token): 192 | client = aclient() 193 | active_searches = {} 194 | 195 | @client.event 196 | async def on_ready(): 197 | await client.tree.sync() 198 | 199 | logging.info(f"Bot {client.user} is ready and running in {len(client.guilds)} servers.") 200 | for guild in client.guilds: 201 | try: 202 | owner = await guild.fetch_member(guild.owner_id) 203 | owner_name = f"{owner.name}#{owner.discriminator}" 204 | except Exception as e: 205 | logging.error(f"Could not fetch owner for guild: {guild.name}, error: {e}") 206 | owner_name = "Could not fetch owner" 207 | 208 | logging.info(f" - {guild.name} (Owner: {owner_name})") 209 | 210 | server_count = len(client.guilds) 211 | activity_text = f"/sherlock on {server_count} servers" 212 | await client.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name=activity_text)) 213 | 214 | logging.info(f'{client.user} is online.') 215 | 216 | @client.tree.command(name="sherlock", description="Search for a username on social networks using Sherlock") 217 | async def sherlock(interaction: discord.Interaction, username: str): 218 | await interaction.response.defer(ephemeral=False) 219 | 220 | logging.info(f"User {interaction.user} from {interaction.guild if interaction.guild else 'DM'} executed '/sherlock' with username '{username}'.") 221 | 222 | formatted_username = username.replace("{", "{%}") 223 | 224 | try: 225 | task = asyncio.create_task(execute_sherlock(interaction, formatted_username)) 226 | active_searches[username] = task 227 | await task 228 | except Exception as e: 229 | await handle_errors(interaction, str(e)) 230 | finally: 231 | if username in active_searches: 232 | del active_searches[username] 233 | 234 | 235 | client.run(token) 236 | 237 | if __name__ == "__main__": 238 | config = load_config() 239 | run_discord_bot(config.get("discord_bot_token")) 240 | --------------------------------------------------------------------------------