diff --git a/.gitignore b/.gitignore index ac0adb7..68604e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ .env .gitattributes .gitignore -.venv +.venv/* # config file key.txt @@ -13,4 +13,7 @@ data/mouse-ss.png data/combined_image.png data/webcam-capture.jpg test.py -commands-to-be-added/* \ No newline at end of file +commands-to-be-added/* + +commands/__pycache__/* +/venv diff --git a/README.md b/README.md index 5c723bf..51606a7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Discord Windows Controller Bot -The Discord Windows Controller Bot is a Python bot designed to control Windows operations through Discord commands. It can perform tasks like locking your workstation and monitoring mouse movement in a specified channel. Follow these steps to set up and run the bot. +The Discord Windows Controller Bot is a Python bot designed to control Windows operations through Discord commands. It can perform tasks like locking your workstation and monitoring mouse movement in a specified channel as well as saving files you send to the local workstation. Follow these steps to set up and run the bot. ## Step 1: Clone the Repository @@ -45,6 +45,38 @@ Install the necessary libraries for your bot using the following command: pip install -r requirements.txt ``` +### Troubleshooting Installation Errors + +If you encounter an error message similar to the following: + +``` +error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-tools/ +``` + +This error occurs because some packages require compilation, and your system is missing the necessary tools. To resolve this: + +1. Install Microsoft Visual C++ Build Tools: + - Go to https://visualstudio.microsoft.com/visual-cpp-build-tools/ + - Download and run the installer + - In the installer, select "C++ build tools" and install it + +2. After installation, restart your computer. + +3. Try running the pip install command again: + + ```bash + pip install -r requirements.txt + ``` + +If you still face issues, try the following: + +- Update pip and setuptools: + ```bash + pip install --upgrade pip setuptools wheel + ``` + +- If problems persist, please open an issue on the GitHub repository for further assistance. + ## Step 5: Create a Discord Bot 1. Go to the Discord Developer Portal: [Discord Developer Portal](https://discord.com/developers/applications) @@ -65,6 +97,7 @@ pip install -r requirements.txt 1. In the project folder, create a text file named "key.txt." 2. Paste the copied bot token into the "key.txt" file and save it. 3. Get your Channel ID and your own ID from Discord, and paste them into the "config.json" file. +4. Create a folder where you would want the files to be saved to locally, and copy the path to the `file_save_path` variable Example: @@ -73,7 +106,8 @@ Example: "command_channel_id": 1137276176451079374, "author_id": 637917562920429309, "log_channel_id": 1137693326543122128, - "mouse_log_channel_id": 1137693326543123128 + "mouse_log_channel_id": 1137693326543123128, + "file_save_path": "insert/file/path" } ``` @@ -100,4 +134,4 @@ Example: ## Step 9: Test the Functionality 1. In the server where your bot is added, send a message with the content "lock" in the designated channel. -2. The bot should respond by locking the workstation and sending a confirmation message. +2. The bot should respond by locking the workstation and sending a confirmation message. \ No newline at end of file diff --git a/bot.py b/bot.py index 942f75e..5087c28 100644 --- a/bot.py +++ b/bot.py @@ -5,13 +5,13 @@ import asyncio import pyautogui from commands.mouse_movement import monitor_mouse_movement +from aiohttp.client_exceptions import ClientConnectorError -KEY_PATH = "D:\Coding\Discord bots\python-windows-bot\key.txt" -CONFIG_PATH = "D:\Coding\Discord bots\python-windows-bot\config.json" +KEY_PATH = "key.txt" +CONFIG_PATH = "config.json" def is_connected(): try: - # Try to resolve a common domain to check if the network is available socket.create_connection(("www.google.com", 80)) return True except OSError: @@ -29,14 +29,14 @@ async def network_monitor(): if not is_connected(): print("Network connection lost. Waiting for network...") while not is_connected(): - await asyncio.sleep(5) # Wait for 5 seconds before checking again + await asyncio.sleep(5) print("Network has been restored.") - await asyncio.sleep(60) # Check network status every 60 seconds + await asyncio.sleep(60) async def main(): if os.path.exists(KEY_PATH): with open(KEY_PATH, "r") as f: - TOKEN = f.read() + TOKEN = f.read().strip() else: TOKEN = "" @@ -46,16 +46,13 @@ async def main(): client = discord.Client(intents=intents) - # Start the network monitoring task in the background - asyncio.create_task(network_monitor()) - @client.event async def on_ready(): print(f"We have logged in as {client.user}") starting_mouse_position = pyautogui.position() asyncio.create_task( monitor_mouse_movement(client, config, starting_mouse_position) - ) # Call the function + ) @client.event async def on_message(message): @@ -73,23 +70,61 @@ async def on_message(message): if str(message.attachments) != "[]": from commands.file_save import execute_file_save - await execute_file_save(client, message, config) if msg == "lock": from commands.lock_command import execute_lock_command - await execute_lock_command(client, message, config) if msg == "ping": from commands.ping_command import execute_ping_command - await execute_ping_command(client, message, config) if msg == "ss" or msg == "screenshot": from commands.screenshot_command import execute_screenshot_command await execute_screenshot_command(message, args) + + if msg == "help" or msg == "?": + from commands.help_command import execute_help_command + await execute_help_command(client, message, config) + + async def start_bot(): + while True: + try: + # Start the network monitoring task + network_task = asyncio.create_task(network_monitor()) + + # Start the client + await client.start(TOKEN) + except ClientConnectorError: + print("Failed to connect to Discord. Retrying in 30 seconds...") + await asyncio.sleep(30) + except KeyboardInterrupt: + print("Interrupt received. Shutting down gracefully...") + break + except Exception as e: + print(f"An unexpected error occurred: {e}") + print("Restarting the bot in 30 seconds...") + await asyncio.sleep(30) - await client.start(TOKEN) + try: + await start_bot() + finally: + # Cancel all running tasks + tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()] + for task in tasks: + task.cancel() + + # Wait for all tasks to complete + await asyncio.gather(*tasks, return_exceptions=True) + + # Close the client connection + await client.close() + + print("Bot has been shut down.") -asyncio.run(main()) +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("Bot stopped.") \ No newline at end of file diff --git a/commands/file_save.py b/commands/file_save.py index 337e547..c284c12 100644 --- a/commands/file_save.py +++ b/commands/file_save.py @@ -1,15 +1,35 @@ +import json +import os + async def execute_file_save(client, message, config): LOG_CHANNEL_ID = config["log_channel_id"] + + # Load the configuration file + with open('config.json', 'r') as config_file: + full_config = json.load(config_file) + + # Get the file save path from the config + file_save_path = full_config.get("file_save_path", "") + + if not file_save_path: + await message.guild.get_channel(LOG_CHANNEL_ID).send("Error: file_save_path not configured in config.json") + print("[file_save_LOG] - Error: file_save_path not configured in config.json") + return split_v1 = str(message.attachments).split("filename='")[1] filename = str(split_v1).split("' ")[0] await message.guild.get_channel(LOG_CHANNEL_ID).send(f"File Detected: {filename}") + try: - await message.attachments[0].save( - fp="D:/Coding\Discord bots/python-windows-bot/files/{}".format(filename) - ) # saves the file - await message.guild.get_channel(LOG_CHANNEL_ID).send("File Sucessfully saved") - print(f"[file_save_LOG] - File Sucessfully saved.") + # Ensure the directory exists + os.makedirs(file_save_path, exist_ok=True) + + # Construct the full file path + full_file_path = os.path.join(file_save_path, filename) + + await message.attachments[0].save(fp=full_file_path) # saves the file + await message.guild.get_channel(LOG_CHANNEL_ID).send("File Successfully saved") + print(f"[file_save_LOG] - File Successfully saved to {full_file_path}") except Exception as e: await message.guild.get_channel(LOG_CHANNEL_ID).send(f"Error while saving: {e}") - print(f"[file_save_LOG] - Error while saving: {e}") + print(f"[file_save_LOG] - Error while saving: {e}") \ No newline at end of file diff --git a/commands/help_command.py b/commands/help_command.py new file mode 100644 index 0000000..d16015a --- /dev/null +++ b/commands/help_command.py @@ -0,0 +1,33 @@ +import os +import discord +from discord.ext import commands + +async def execute_help_command(client, message, config): + """ + Displays a list of all available commands and their descriptions. + + Usage: !help or !? + """ + LOG_CHANNEL_ID = config["log_channel_id"] + + commands_dir = r"D:\Coding\python-discord-windows-controller\commands" + command_files = [f for f in os.listdir(commands_dir) if f.endswith('.py') and f != '__init__.py'] + + embed = discord.Embed(title="Bot Commands", description="Here are all available commands:", color=discord.Color.blue()) + + for file in command_files: + command_name = file[:-3] # Remove .py extension + module = __import__(f"commands.{command_name}", fromlist=['']) + + # Try to get the docstring of the execute function + try: + func = getattr(module, f"execute_{command_name}_command") + description = func.__doc__ or "No description available." + except AttributeError: + description = "No description available." + + embed.add_field(name=f"!{command_name}", value=description, inline=False) + + await message.channel.send(embed=embed) + print(f"[help_command_LOG] - Help command executed.") + await message.delete() \ No newline at end of file diff --git a/commands/lock_command.py b/commands/lock_command.py index dd88ddf..d01649e 100644 --- a/commands/lock_command.py +++ b/commands/lock_command.py @@ -2,6 +2,11 @@ async def execute_lock_command(client, message, config): + """ + Locks the computer screen. + + Usage: !lock + """ LOG_CHANNEL_ID = config["log_channel_id"] command = "rundll32.exe user32.dll,LockWorkStation" diff --git a/commands/mouse_movement.py b/commands/mouse_movement.py index 9667737..17df4d1 100644 --- a/commands/mouse_movement.py +++ b/commands/mouse_movement.py @@ -1,5 +1,3 @@ -# commands/mouse_movement.py - import discord import asyncio import pyautogui @@ -10,10 +8,10 @@ import cv2 import os -DATA_JSON_PATH = "D:\Coding\Discord bots\python-windows-bot\data\data.json" -SCREENSHOT_PATH = "D:\Coding\Discord bots\python-windows-bot\data\mouse-ss.png" -WEBCAM_CAPTURE_PATH = "D:\Coding\Discord bots\python-windows-bot\data\webcam-capture.jpg" -COMBINED_IMAGE_PATH = "D:\Coding\Discord bots\python-windows-bot\data\combined_image.png" +DATA_JSON_PATH = "data/data.json" +SCREENSHOT_PATH = "data/mouse-ss.png" +WEBCAM_CAPTURE_PATH = "data/webcam-capture.jpg" +COMBINED_IMAGE_PATH = "data/combined_image.png" async def monitor_mouse_movement(client, config, starting_mouse_position): log_channel_id = config["mouse_log_channel_id"] @@ -21,7 +19,7 @@ async def monitor_mouse_movement(client, config, starting_mouse_position): last_message = None image_message_id = None last_movement_time = None - image_message = None # Define image_message here to ensure it's initialized + image_message = None previous_mouse_position = starting_mouse_position @@ -36,7 +34,7 @@ async def monitor_mouse_movement(client, config, starting_mouse_position): try: image_message = await log_channel.fetch_message(image_message_id) except discord.NotFound: - image_message = None # Set image_message to None if the message is not found + image_message = None last_movement_time = data.get("last_movement_time") if last_movement_time: last_movement_time = datetime.datetime.strptime( @@ -60,42 +58,47 @@ async def monitor_mouse_movement(client, config, starting_mouse_position): except OSError as e: print(f"[mouse_movement_LOG] - Error capturing screenshot: {e}") await asyncio.sleep(5) - continue # Skip the rest of the loop iteration if there's an error + continue # Capture webcam image - webcam = cv2.VideoCapture(0) # Initialize webcam capture + webcam = cv2.VideoCapture(0) ret, frame = webcam.read() + webcam.release() + if ret: cv2.imwrite(WEBCAM_CAPTURE_PATH, frame) - webcam.release() # Release the webcam capture - - # Load the screenshot and webcam capture images - screenshot = cv2.imread(SCREENSHOT_PATH) - webcam_capture = cv2.imread(WEBCAM_CAPTURE_PATH) - - # Calculate the scaling ratio to fit the webcam image within a maximum width and height - max_width = 200 # Maximum width for the webcam capture image - max_height = 150 # Maximum height for the webcam capture image - scale_ratio = min(max_width / webcam_capture.shape[1], max_height / webcam_capture.shape[0]) - - # Resize the webcam capture image while maintaining aspect ratio - webcam_capture_resized = cv2.resize(webcam_capture, (0, 0), fx=scale_ratio, fy=scale_ratio) - - # Define the coordinates where the webcam capture will be placed in the screenshot - x_offset = 10 # Adjust as needed - y_offset = 10 # Adjust as needed - - # Overlay the webcam capture on the screenshot - screenshot[y_offset:y_offset+webcam_capture_resized.shape[0], x_offset:x_offset+webcam_capture_resized.shape[1]] = webcam_capture_resized - - # Save the resulting image - cv2.imwrite(COMBINED_IMAGE_PATH, screenshot) - - image_message = await log_channel.send( - message, file=discord.File(COMBINED_IMAGE_PATH) - ) - - print(f"[mouse_movement_LOG] - {last_movement_time} : Mouse SS sent.") + webcam_capture = cv2.imread(WEBCAM_CAPTURE_PATH) + + if webcam_capture is not None: + screenshot = cv2.imread(SCREENSHOT_PATH) + + if screenshot is not None: + max_width = 200 + max_height = 150 + scale_ratio = min(max_width / webcam_capture.shape[1], max_height / webcam_capture.shape[0]) + + webcam_capture_resized = cv2.resize(webcam_capture, (0, 0), fx=scale_ratio, fy=scale_ratio) + + x_offset = 10 + y_offset = 10 + + screenshot[y_offset:y_offset+webcam_capture_resized.shape[0], x_offset:x_offset+webcam_capture_resized.shape[1]] = webcam_capture_resized + + cv2.imwrite(COMBINED_IMAGE_PATH, screenshot) + + image_message = await log_channel.send( + message, file=discord.File(COMBINED_IMAGE_PATH) + ) + print(f"[mouse_movement_LOG] - {last_movement_time} : Mouse SS sent.") + else: + print("[mouse_movement_LOG] - Error reading screenshot.") + image_message = await log_channel.send(message) + else: + print("[mouse_movement_LOG] - Error reading webcam capture.") + image_message = await log_channel.send(message) + else: + print("[mouse_movement_LOG] - Error capturing webcam image.") + image_message = await log_channel.send(message) if last_message: await last_message.edit(content=message) diff --git a/commands/ping_command.py b/commands/ping_command.py index cbb902d..e009524 100644 --- a/commands/ping_command.py +++ b/commands/ping_command.py @@ -2,6 +2,11 @@ async def execute_ping_command(client, message, config): + """ + Responds with 'Pong!' to check if the bot is responsive. + + Usage: !ping + """ LOG_CHANNEL_ID = config["log_channel_id"] ping_message = await message.channel.send("Pong !") diff --git a/commands/screenshot_command.py b/commands/screenshot_command.py index 19959b4..a72ae53 100644 --- a/commands/screenshot_command.py +++ b/commands/screenshot_command.py @@ -2,7 +2,7 @@ import datetime from PIL import ImageGrab -SCREENSHOT_PATH = "D:\Coding\Discord bots\python-windows-bot\data\screenshot.png" +SCREENSHOT_PATH = "data\screenshot.png" def get_ss(): screenshot = ImageGrab.grab() @@ -10,6 +10,11 @@ def get_ss(): return SCREENSHOT_PATH async def execute_screenshot_command(message,args): + """ + Takes a screenshot of the current screen and sends it. + + Usage: !ss or !screenshot + """ time = datetime.datetime.now().strftime("%d-%m-%Y %H:%M:%S") MESSAGE = f"Screenshot at {time}" PATH = get_ss() diff --git a/config.json.example b/config.json.example index 069e9dd..6d18e02 100644 --- a/config.json.example +++ b/config.json.example @@ -2,5 +2,6 @@ "command_channel_id": 1137276176451079374, "author_id": 637917562920429309, "log_channel_id": 1137693326543122128, - "mouse_log_channel_id": 1137693326543123128 + "mouse_log_channel_id": 1137693326543123128, + "file_save_path": "insert-file-path" } \ No newline at end of file