diff --git a/bot.py b/bot.py index 75fd9f7..b59a0fb 100755 --- a/bot.py +++ b/bot.py @@ -1,9 +1,12 @@ #!/usr/bin/env python +# bot.py import os import logging -from dotenv import load_dotenv +import importlib import simplematrixbotlib as botlib +from dotenv import load_dotenv +import time # Load environment variables from .env file load_dotenv() @@ -25,35 +28,37 @@ logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=lo # Load plugins PLUGINS_DIR = "plugins" +PLUGINS = {} def load_plugins(): - plugins = [] for plugin_file in os.listdir(PLUGINS_DIR): if plugin_file.endswith(".py"): plugin_name = os.path.splitext(plugin_file)[0] try: - plugin_module = __import__(f"{PLUGINS_DIR}.{plugin_name}", fromlist=[""]) - plugins.append(plugin_module) + module = importlib.import_module(f"{PLUGINS_DIR}.{plugin_name}") + PLUGINS[plugin_name] = module logging.info(f"Loaded plugin: {plugin_name}") except Exception as e: logging.error(f"Error loading plugin {plugin_name}: {e}") - return plugins -plugins = load_plugins() +def reload_plugins(): + global PLUGINS + PLUGINS = {} + load_plugins() + +load_plugins() @bot.listener.on_message_event async def handle_commands(room, message): - """ - Function to handle incoming command messages. + match = botlib.MessageMatch(room, message, bot, PREFIX) + if match.is_not_from_this_bot() and match.prefix() and match.command("reload"): + if str(message.sender) == ADMIN_USER: + reload_plugins() + await bot.api.send_text_message(room.room_id, "Plugins reloaded successfully") + else: + await bot.api.send_text_message(room.room_id, "You are not authorized to reload plugins.") - Args: - room (Room): The Matrix room where the command was invoked. - message (RoomMessage): The message object containing the command. - - Returns: - None - """ - for plugin in plugins: - await plugin.handle_command(room, message, bot, PREFIX) + for plugin_name, plugin_module in PLUGINS.items(): + await plugin_module.handle_command(room, message, bot, PREFIX) bot.run() diff --git a/plugins/__pycache__/commands.cpython-310.pyc b/plugins/__pycache__/commands.cpython-310.pyc deleted file mode 100644 index 23a484e..0000000 Binary files a/plugins/__pycache__/commands.cpython-310.pyc and /dev/null differ diff --git a/plugins/__pycache__/date.cpython-310.pyc b/plugins/__pycache__/date.cpython-310.pyc deleted file mode 100644 index 1d039ce..0000000 Binary files a/plugins/__pycache__/date.cpython-310.pyc and /dev/null differ diff --git a/plugins/__pycache__/fortune.cpython-310.pyc b/plugins/__pycache__/fortune.cpython-310.pyc deleted file mode 100644 index e2bd1b7..0000000 Binary files a/plugins/__pycache__/fortune.cpython-310.pyc and /dev/null differ diff --git a/plugins/__pycache__/isup.cpython-310.pyc b/plugins/__pycache__/isup.cpython-310.pyc deleted file mode 100644 index d7ed436..0000000 Binary files a/plugins/__pycache__/isup.cpython-310.pyc and /dev/null differ diff --git a/plugins/__pycache__/karma.cpython-310.pyc b/plugins/__pycache__/karma.cpython-310.pyc deleted file mode 100644 index 332333b..0000000 Binary files a/plugins/__pycache__/karma.cpython-310.pyc and /dev/null differ diff --git a/plugins/__pycache__/proxy.cpython-310.pyc b/plugins/__pycache__/proxy.cpython-310.pyc deleted file mode 100644 index 7aa98e1..0000000 Binary files a/plugins/__pycache__/proxy.cpython-310.pyc and /dev/null differ diff --git a/plugins/__pycache__/youtube.cpython-310.pyc b/plugins/__pycache__/youtube.cpython-310.pyc deleted file mode 100644 index d288a69..0000000 Binary files a/plugins/__pycache__/youtube.cpython-310.pyc and /dev/null differ diff --git a/plugins/commands.py b/plugins/commands.py index 44239fc..068a53b 100644 --- a/plugins/commands.py +++ b/plugins/commands.py @@ -18,28 +18,53 @@ async def handle_command(room, message, bot, PREFIX): if match.is_not_from_this_bot() and match.prefix() and match.command("commands"): logging.info("Fetching commands documentation") commands_message = """ - **Available Commands:** +# 🍄 Funguy Bot Commands 🍄 - **!fortune** - Returns a random fortune message. +🃏 **!fortune** +Returns a random fortune message. +Executes the `/usr/games/fortune` utility and sends the output as a message to the chat room. - **!date** - Displays the current date and time. +⏰ **!date** +Displays the current date and time. +Fetches the current date and time using Python's `datetime` module and sends it in a formatted message to the chat room. - **!proxy** - Retrieves and tests random SOCKS5 and HTTP proxies. +💻 **!proxy** +Retrieves a tested/working random SOCKS5 proxy. +Fetches a list of SOCKS5 proxies, tests their availability, and sends the first working proxy to the chat room. - **!isup ** - Checks if the specified domain or IP address is reachable. +📶 **!isup ** +Checks if the specified domain or IP address is reachable. +Checks if the specified domain or IP address is reachable by attempting to ping it. If DNS resolution is successful, it checks HTTP and HTTPS service availability by sending requests to the domain. - **!karma ** - Retrieves the karma points for the specified user. +☯ **!karma ** +Retrieves the karma points for the specified user. +Retrieves the karma points for the specified user from a database and sends them as a message to the chat room. - **!karma up** - Increases the karma points for the specified user by 1. +⇧ **!karma up** +Increases the karma points for the specified user by 1. +Increases the karma points for the specified user by 1 in the database and sends the updated points as a message to the chat room. + +⇩ **!karma down** +Decreases the karma points for the specified user by 1. +Decreases the karma points for the specified user by 1 in the database and sends the updated points as a message to the chat room. + +# 🌟 Funguy Bot Credits 🌟 + +🧙‍♂️ Creator & Developer: Hash Borgir (HB) + +🎓 Academic Achievements: + - Bachelor of Computer Science (2004) + - Bachelor of Science, Cybersecurity & Information Assurance (2023) + - Master of Science, Cybersecurity & Information Assurance (ongoing) + +🔐 Certifications: + - A+, Net+, Sec+, CySA+, Pentest+, Project+, ECES, ITILv4, SSCP, CCSP, MCP, MCSE + +💼 Professional Profile: + - Website: [Hash Security](https://www.hashsecurity.net) + +Hash Borgir, with a rich background in computer science and cybersecurity, is the author of 🍄Funguy Bot🍄. - **!karma down** - Decreases the karma points for the specified user by 1. """ await bot.api.send_markdown_message(room.room_id, commands_message) logging.info("Sent commands documentation to the room") diff --git a/plugins/date.py b/plugins/date.py index ea582c0..36d3a15 100644 --- a/plugins/date.py +++ b/plugins/date.py @@ -23,6 +23,6 @@ async def handle_command(room, message, bot, PREFIX): month = current_datetime.strftime("%B") year = current_datetime.strftime("%Y") time = current_datetime.strftime("%I:%M:%S %p") - date_message = f"Today is **{day_of_week}** of **{month}** in **{year}**. The time is **{time}**" + date_message = f"📅 Today is **{day_of_week}** of **{month}** in **{year}**. The time is **⏰ {time}**" await bot.api.send_markdown_message(room.room_id, date_message) logging.info("Sent current date and time to the room") diff --git a/plugins/fortune.py b/plugins/fortune.py index 0a1de27..add2a2c 100644 --- a/plugins/fortune.py +++ b/plugins/fortune.py @@ -18,6 +18,6 @@ async def handle_command(room, message, bot, PREFIX): match = botlib.MessageMatch(room, message, bot, PREFIX) if match.is_not_from_this_bot() and match.prefix() and match.command("fortune"): logging.info("Received !fortune command") - fortune_output = subprocess.run(['/usr/games/fortune'], capture_output=True).stdout.decode('UTF-8') - await bot.api.send_text_message(room.room_id, fortune_output) + fortune_output = "🃏 " + subprocess.run(['/usr/games/fortune'], capture_output=True).stdout.decode('UTF-8') + await bot.api.send_markdown_message(room.room_id, fortune_output) logging.info("Sent fortune to the room") diff --git a/plugins/isup.py b/plugins/isup.py index f12f7cc..f68ad17 100644 --- a/plugins/isup.py +++ b/plugins/isup.py @@ -1,9 +1,44 @@ # plugins/isup.py -import os import logging +import aiohttp +import socket import simplematrixbotlib as botlib +async def check_http(domain): + """ + Check if HTTP service is up for the given domain. + + Args: + domain (str): The target domain. + + Returns: + bool: True if HTTP service is up, False otherwise. + """ + try: + async with aiohttp.ClientSession() as session: + async with session.get(f"http://{domain}") as response: + return response.status == 200 + except aiohttp.ClientError: + return False + +async def check_https(domain): + """ + Check if HTTPS service is up for the given domain. + + Args: + domain (str): The target domain. + + Returns: + bool: True if HTTPS service is up, False otherwise. + """ + try: + async with aiohttp.ClientSession() as session: + async with session.get(f"https://{domain}") as response: + return response.status == 200 + except aiohttp.ClientError: + return False + async def handle_command(room, message, bot, PREFIX): """ Function to handle the !isup command. @@ -20,18 +55,29 @@ async def handle_command(room, message, bot, PREFIX): logging.info("Received !isup command") args = match.args() if len(args) != 1: - await bot.api.send_text_message(room.room_id, "Usage: !isup ") + await bot.api.send_markdown_message(room.room_id, "Usage: !isup ") logging.info("Sent usage message to the room") return target = args[0] + + # DNS resolution try: - response = os.system(f"ping -c 1 {target}") - if response == 0: - await bot.api.send_text_message(room.room_id, f"{target} is up") - else: - await bot.api.send_text_message(room.room_id, f"{target} is down") - logging.info(f"Sent status of {target} to the room") - except Exception as e: - await bot.api.send_text_message(room.room_id, f"Error: {e}") - logging.error(f"Error occurred while checking {target}: {e}") + ip_address = socket.gethostbyname(target) + logging.info(f"DNS resolution successful for {target}: {ip_address}") + await bot.api.send_markdown_message(room.room_id, f"✅ DNS resolution successful for **{target}**: **{ip_address}** (A record)") + except socket.gaierror: + logging.info(f"DNS resolution failed for {target}") + await bot.api.send_markdown_message(room.room_id, f"❌ DNS resolution failed for **{target}**") + return + + # Check HTTP/HTTPS services + if await check_http(target): + await bot.api.send_markdown_message(room.room_id, f"🖧 **{target}** HTTP service is up") + logging.info(f"{target} HTTP service is up") + elif await check_https(target): + await bot.api.send_markdown_message(room.room_id, f"🖧 **{target}** HTTPS service is up") + logging.info(f"{target} HTTPS service is up") + else: + await bot.api.send_markdown_message(room.room_id, f"😕 **{target}** HTTP/HTTPS services are down") + logging.info(f"{target} HTTP/HTTPS services are down") diff --git a/plugins/karma.py b/plugins/karma.py index 38b0853..aa37cae 100644 --- a/plugins/karma.py +++ b/plugins/karma.py @@ -29,15 +29,20 @@ async def handle_command(room, message, bot, PREFIX): (username TEXT PRIMARY KEY, points INTEGER)''') c.execute('''INSERT OR IGNORE INTO karma (username, points) VALUES (?, ?)''', (sender, 0)) c.execute('''SELECT points FROM karma WHERE username = ?''', (sender,)) - points = c.fetchone()[0] + row = c.fetchone() + if row is not None: + points = row[0] + await bot.api.send_markdown_message(room.room_id, f"💗 {sender}'s karma points: {points}") + logging.info(f"Sent {sender}'s karma points ({points}) to the room") + else: + await bot.api.send_markdown_message(room.room_id, f"💗 {sender} does not have any karma points yet.") + logging.info(f"Sent message that {sender} does not have any karma points yet.") conn.close() - await bot.api.send_text_message(room.room_id, f"{sender}'s karma points: {points}") - logging.info(f"Sent {sender}'s karma points ({points}) to the room") elif len(args) == 1: username = args[0] if username == sender: - await bot.api.send_text_message(room.room_id, "You cannot modify your own karma.") + await bot.api.send_markdown_message(room.room_id, "❌ You cannot modify your own karma.") logging.info("Sent self-modification warning message to the room") return @@ -47,19 +52,24 @@ async def handle_command(room, message, bot, PREFIX): (username TEXT PRIMARY KEY, points INTEGER)''') c.execute('''INSERT OR IGNORE INTO karma (username, points) VALUES (?, ?)''', (username, 0)) c.execute('''SELECT points FROM karma WHERE username = ?''', (username,)) - points = c.fetchone()[0] + row = c.fetchone() + if row is not None: + points = row[0] + await bot.api.send_markdown_message(room.room_id, f"💗 {username}'s karma points: {points}") + logging.info(f"Sent {username}'s karma points ({points}) to the room") + else: + await bot.api.send_markdown_message(room.room_id, f"💗 {username} does not have any karma points yet.") + logging.info(f"Sent message that {username} does not have any karma points yet.") conn.close() - await bot.api.send_text_message(room.room_id, f"{username}'s karma points: {points}") - logging.info(f"Sent {username}'s karma points ({points}) to the room") elif len(args) == 2: username, action = args if action not in ['up', 'down']: - await bot.api.send_text_message(room.room_id, "Invalid action. Use 'up' or 'down'.") + await bot.api.send_markdown_message(room.room_id, "❌ Invalid action. Use 'up' or 'down'.") logging.info("Sent invalid action message to the room") return if username == sender: - await bot.api.send_text_message(room.room_id, "You cannot modify your own karma.") + await bot.api.send_markdown_message(room.room_id, "❌ You cannot modify your own karma.") logging.info("Sent self-modification warning message to the room") return @@ -69,17 +79,23 @@ async def handle_command(room, message, bot, PREFIX): (username TEXT PRIMARY KEY, points INTEGER)''') if action == 'up': + c.execute('''INSERT OR IGNORE INTO karma (username, points) VALUES (?, ?)''', (username, 0)) c.execute('''UPDATE karma SET points = points + 1 WHERE username = ?''', (username,)) else: + c.execute('''INSERT OR IGNORE INTO karma (username, points) VALUES (?, ?)''', (username, 0)) c.execute('''UPDATE karma SET points = points - 1 WHERE username = ?''', (username,)) conn.commit() c.execute('''SELECT points FROM karma WHERE username = ?''', (username,)) - points = c.fetchone()[0] + row = c.fetchone() + if row is not None: + points = row[0] + await bot.api.send_markdown_message(room.room_id, f"💗 {username}'s karma points: {points}") + logging.info(f"Sent {username}'s karma points ({points}) to the room") + else: + await bot.api.send_markdown_message(room.room_id, f"💗 {username} does not have any karma points yet.") + logging.info(f"Sent message that {username} does not have any karma points yet.") conn.close() - - await bot.api.send_text_message(room.room_id, f"{username}'s karma points: {points}") - logging.info(f"Sent {username}'s karma points ({points}) to the room") else: - await bot.api.send_text_message(room.room_id, "Usage: !karma [username] [up/down]") + await bot.api.send_markdown_message(room.room_id, "☯ Usage: !karma [username] [up/down]") logging.info("Sent usage message to the room") diff --git a/plugins/proxy.py b/plugins/proxy.py index 3a65547..c498d90 100644 --- a/plugins/proxy.py +++ b/plugins/proxy.py @@ -5,53 +5,69 @@ import logging import random import requests import socket -import asyncio +import ssl import time +from datetime import datetime, timedelta import simplematrixbotlib as botlib -# Function to test SOCKS4 proxies -async def test_socks4_proxy(proxy): - try: - start_time = time.time() - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(5) - s.connect((proxy.split(':')[0], int(proxy.split(':')[1]))) - latency = round((time.time() - start_time) * 1000, 2) - logging.info(f"Tested SOCKS4 proxy {proxy}. Latency: {latency} ms") - return True, latency - except Exception as e: - logging.error(f"Error testing SOCKS4 proxy {proxy}: {e}") - return False, None +# Constants +SOCKS5_LIST_URL = 'https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks5.txt' +MAX_TRIES = 5 +PROXY_LIST_FILENAME = 'socks5.txt' +PROXY_LIST_EXPIRATION = timedelta(hours=8) -# Function to test SOCKS5 proxies async def test_socks5_proxy(proxy): + """ + Test a SOCKS5 proxy by attempting a connection and sending a request through it. + + Args: + proxy (str): The SOCKS5 proxy address in the format 'ip:port'. + + Returns: + bool: True if the proxy is working, False otherwise. + float: The latency in milliseconds if the proxy is working, None otherwise. + """ try: + ip, port = proxy.split(':') start_time = time.time() - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(5) - s.connect((proxy.split(':')[0], int(proxy.split(':')[1]))) - latency = round((time.time() - start_time) * 1000, 2) - logging.info(f"Tested SOCKS5 proxy {proxy}. Latency: {latency} ms") - return True, latency + with socket.create_connection((ip, int(port)), timeout=5) as client: + client.sendall(b'\x05\x01\x00') + response = client.recv(2) + if response == b'\x05\x00': + latency = int(round((time.time() - start_time) * 1000, 0)) + logging.info(f"Successful connection to SOCKS5 proxy {proxy}. Latency: {latency} ms") + return True, latency + else: + logging.info(f"Failed to connect to SOCKS5 proxy {proxy}: Connection refused") + return False, None except Exception as e: logging.error(f"Error testing SOCKS5 proxy {proxy}: {e}") return False, None -# Function to test HTTP proxies -async def test_http_proxy(proxy): - local_ip = requests.get("https://api.ipify.org").text +async def download_proxy_list(): + """ + Download the SOCKS5 proxy list file if it doesn't already exist or if it's expired. + + Returns: + bool: True if the proxy list is downloaded or up-to-date, False otherwise. + """ try: - response = requests.get("https://api.ipify.org", proxies={"http": proxy}, timeout=5).text - if response.strip() != local_ip.strip(): - logging.info(f"Tested anonymous HTTP proxy {proxy}") + if not os.path.exists(PROXY_LIST_FILENAME) or \ + datetime.now() - datetime.fromtimestamp(os.path.getctime(PROXY_LIST_FILENAME)) > PROXY_LIST_EXPIRATION: + logging.info("Downloading SOCKS5 proxy list") + response = requests.get(SOCKS5_LIST_URL, timeout=5) + with open(PROXY_LIST_FILENAME, 'w') as f: + f.write(response.text) + logging.info("Proxy list downloaded successfully") return True else: - logging.info(f"HTTP proxy {proxy} is not anonymous") - return False + logging.info("Proxy list already exists and is up-to-date") + return True except Exception as e: - logging.error(f"Error testing HTTP proxy {proxy}: {e}") + logging.error(f"Error downloading proxy list: {e}") return False + async def handle_command(room, message, bot, PREFIX): """ Function to handle the !proxy command. @@ -66,38 +82,35 @@ async def handle_command(room, message, bot, PREFIX): match = botlib.MessageMatch(room, message, bot, PREFIX) if match.is_not_from_this_bot() and match.prefix() and match.command("proxy"): logging.info("Received !proxy command") + + # Download proxy list if needed + if not await download_proxy_list(): + await bot.api.send_markdown_message(room.room_id, "Error downloading proxy list") + logging.error("Error downloading proxy list") + return + try: - # Fetch SOCKS4 proxy - socks4_proxies = requests.get('https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks4.txt', timeout=5).text.splitlines() - random.shuffle(socks4_proxies) - for proxy in socks4_proxies: - is_working, latency = await test_socks4_proxy(proxy) - if is_working: - await bot.api.send_text_message(room.room_id, f"SOCKS4 Proxy: {proxy} - Latency: {latency} ms") - break - - # Fetch SOCKS5 proxy - socks5_proxies = requests.get('https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks5.txt', timeout=5).text.splitlines() + # Read proxies from file + with open(PROXY_LIST_FILENAME, 'r') as f: + socks5_proxies = f.read().splitlines() random.shuffle(socks5_proxies) - for proxy in socks5_proxies: - is_working, latency = await test_socks5_proxy(proxy) - if is_working: - await bot.api.send_text_message(room.room_id, f"SOCKS5 Proxy: {proxy} - Latency: {latency} ms") + + # Test proxies + socks5_proxy = None + for proxy in socks5_proxies[:MAX_TRIES]: + success, latency = await test_socks5_proxy(proxy) + if success: + socks5_proxy = proxy break - # Fetch HTTP proxy - http_proxies = requests.get('https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/http.txt', timeout=5).text.splitlines() - random.shuffle(http_proxies) - for proxy in http_proxies: - is_working = await test_http_proxy(proxy) - if is_working: - await bot.api.send_text_message(room.room_id, f"HTTP Proxy: {proxy}") - break + # Send the first working anonymous proxy of each type to the Matrix room + if socks5_proxy: + await bot.api.send_markdown_message(room.room_id, f"✅ Anonymous SOCKS5 Proxy: **{socks5_proxy}** - Latency: **{latency} ms**") + logging.info(f"Sent SOCKS5 proxy {socks5_proxy} to the room") + else: + await bot.api.send_markdown_message(room.room_id, "❌ No working anonymous SOCKS5 proxy found") + logging.info("No working anonymous SOCKS5 proxy found") - logging.info("Sent proxies to the room") - except asyncio.TimeoutError: - await bot.api.send_text_message(room.room_id, "Failed to fetch or test proxies. The operation timed out.") - logging.error("Proxy fetch and test operation timed out") except Exception as e: - await bot.api.send_text_message(room.room_id, f"An error occurred: {e}") - logging.error(f"Error fetching or testing proxies: {e}") + logging.error(f"Error handling !proxy command: {e}") + await bot.api.send_markdown_message(room.room_id, "❌ Error handling !proxy command") diff --git a/plugins/youtube.py b/plugins/youtube.py index 4260b08..952f872 100644 --- a/plugins/youtube.py +++ b/plugins/youtube.py @@ -31,9 +31,9 @@ async def handle_command(room, message, bot, PREFIX): length = video.length views = video.views author = video.author - info_message = f"""Title: {title} | Length: {length} seconds | Views: {views} | Description: {description}""" + info_message = f"""**🎬🎝 Title:** {title} | **Length**: {length} sec | **Views:** {views} | **Description:** {description}""" await bot.api.send_markdown_message(room.room_id, info_message) logging.info("Sent YouTube video information to the room") except Exception as e: logging.error(f"Error fetching YouTube video information: {str(e)}") - await bot.api.send_text_message(room.room_id, "Error fetching YouTube video information.") + # await bot.api.send__message(room.room_id, "Error fetching YouTube video information.")