Compare commits

..

37 Commits

Author SHA1 Message Date
5d746027e2 unload plugin loadplugin.py. youtube-preview now gets lyrics. 2024-03-10 05:58:21 -06:00
d2acef611b Update plugins, refactor proxy plugin, yt preview now gets lyrics 2024-03-04 00:53:58 -07:00
e8186c9fec Plugin clean up 2024-03-03 16:52:04 -07:00
543e139ca0 removed standalone ai plugins 2024-03-03 16:35:07 -07:00
551c5ddc02 Updated plugins. AI plugin updated 2024-03-03 16:34:39 -07:00
387606426c Funguy Class updated 2024-03-03 15:17:40 -07:00
41c59618cc Funguy Plugin Enable/Disable feature per room, save to conf file 2024-03-03 15:01:44 -07:00
5d6ad98303 SD plugin update SD, disble YT preview for music room 2024-03-03 13:51:41 -07:00
4ef6f3beb7 SD plugin update image info 2024-03-03 13:17:01 -07:00
c4b6c750fb SD plugin working 2024-03-03 12:56:19 -07:00
ae2bdfecae Latest working copy 2024-03-03 06:59:39 -07:00
25f9159155 sd update 2024-03-01 09:21:29 -07:00
803acf514b switched to using pytubefix 2024-03-01 09:20:44 -07:00
542bb5d5cd Various plugin updates. New plugin for stable diffusion 2024-02-29 20:18:58 -07:00
5962eb53ad AI plugins updated, now html formatted 2024-02-17 18:37:09 -07:00
eb81f7aa67 Help plugin reformat HTML 2024-02-17 17:48:46 -07:00
c996168543 Funguy class refactor 2024-02-17 16:19:23 -07:00
784409cef6 date module update 2024-02-17 07:04:35 -07:00
e9a853c31a Rehash function should work now 2024-02-16 20:13:13 -07:00
669fd361d4 update ignores 2024-02-14 12:32:30 -07:00
58fa16663e Updated README.md 2024-02-14 05:16:45 -07:00
911f8dd47a Update patch file 2024-02-14 12:00:26 +00:00
Hash Borgir
b10178452b ai plugins cleaned and collapsed output 2024-02-14 03:38:19 -07:00
Hash Borgir
5f5c9d2a0c yt added. Misc output clean up, collapsible text 2024-02-14 02:56:11 -07:00
Hash Borgir
bb331092d0 Config refactor. Plugins now using config 2024-02-14 00:12:40 -07:00
Hash Borgir
2277dd1b90 Testing configuration changes/updates 2024-02-13 22:39:17 -07:00
Hash Borgir
aa7e76e653 README updated 2024-02-13 17:01:58 -07:00
Hash Borgir
976f806397 installer and systemd service added 2024-02-13 16:39:09 -07:00
Hash Borgir
8c23deb13b simplematrixbotlib updated to 2.10 from codeberg 2024-02-13 10:08:26 -07:00
Hash Borgir
df87d39e82 Music plugin updated 2024-02-12 21:31:13 -07:00
Hash Borgir
f50fff0ba6 Plugin for music bot added 2024-02-12 21:23:46 -07:00
Hash Borgir
a459c1299d Plugin for music bot added 2024-02-12 21:23:24 -07:00
Hash Borgir
33ad735292 llm trigger to funguy 2024-02-12 21:12:03 -07:00
Hash Borgir
700dc8ccb6 Updated documenation 2024-02-12 21:01:45 -07:00
Hash Borgir
c9fd5b3d9b Updated documenation 2024-02-12 21:00:59 -07:00
Hash Borgir
ef7704c0bb LLM plugin updated 2024-02-12 20:44:49 -07:00
Hash Borgir
2b7e7db27b LLM plugin added 2024-02-12 20:19:03 -07:00
26 changed files with 2785 additions and 308 deletions

7
.gitignore vendored
View File

@@ -1,9 +1,12 @@
.env
.gitignore
karma.db
proxies.db
session.txt
socks5.txt
venv/
plugins/__pycache__/
simplematrixbotlib/
simplematrixbotlib*/
chromedriver
store
funguybot.service
stats.db

View File

@@ -12,24 +12,31 @@ Matrix Bot is a Python-based chat bot designed to work with Matrix, an open netw
- Extensible: Users can add new commands by creating additional plugin modules.
## Automatic Installation
1. `./install.sh` or `bash install.sh`
Run the installation script
1. `./install-funguy.sh`
2. Set up environment variables:
Put your bot homeserver/user/pass in `.env` file
3. Launch the bot:
`python funguy.py`, or `chmod +x funguy.py`, then `./funguy.py`
2. Launch the bot:
`sudo systemctl start funguybot`
## Manual Installation
1. Clone the repository:
1. Create python venv
`python3 -m venv venv`
`source venv/bin/activate`
2. Clone the repository:
`git clone https://git.stoned.io/hash/FunguyBot`
2. Install dependencies:
`pip install -r requirements.txt`
3. Apply the patch
`cp api.py.patch simplematrixbotlib`
`git apply api.py.patch`
4. Install dependencies:
`cd simplematrixbotlib && pip install .`
`cd ../ && pip install -r requirements.txt`
3. Set up environment variables:
Create a `.env` file in the root directory of the bot and add the following variables:
Create/Edit `.env` file in the root directory of the bot and add the following variables:
```
MATRIX_URL="https://matrix.org" (or another homeserver)
@@ -37,9 +44,37 @@ MATRIX_USER=""
MATRIX_PASS=""
```
4. Run the bot:
`python funguy.py`, or `chmod +x funguy.py`, then `./funguy.py`
4. Create systemd.service
Create `/etc/systemd/system/funguybot.service`
Replace `$working_directory` with your bot install path
```
[Unit]
Description=Funguy Bot Service
After=network.target
[Service]
Type=simple
User=$user
Group=$group
WorkingDirectory=$working_directory
ExecStart=$working_directory/start-funguy.sh
Restart=on-failure
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=funguybot
[Install]
WantedBy=multi-user.target
EOF
```
5. Launch Fungy
```
systemctl daemon-reload
systemctl enable funguybot
systemctl start funguybot
```
## Usage
@@ -50,7 +85,9 @@ To use the bot, invite it to a Matrix room and interact with it by sending comma
- `!proxy`: Retrieve and test random SOCKS5 and HTTP proxies.
- `!isup <domain/ip>`: Check if the specified domain or IP address is reachable.
- `!karma <user>`: View or modify karma points for a user.
- `!funguy <prompt>` Talk to the Tech AI LLM
- `!music <prompt>` Talk to the music knowledge LLM
- `!yt <search terms>` Search Youtube
For a complete list of available commands and their descriptions, use the `!commands` command.
# 🍄 Funguy Bot Commands 🍄
@@ -83,6 +120,13 @@ Increases the karma points for the specified user by 1 in the database and sends
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 [prompt]**
An AI large language model designed to serve as a chatbot within a vibrant and diverse community chat room hosted on the Matrix platform.
(Currently loaded model: **TheBloke_Mistral-7B-Instruct-v0.2-GPTQ**
🎝 **!music [prompt]**
Your music expert! Try it out.
# 🌟 Funguy Bot Credits 🌟
🧙‍♂️ Creator & Developer:

View File

@@ -1,17 +1,17 @@
From 4aa5b913f75e32f6ed06fc6e691daa856824b26a Mon Sep 17 00:00:00 2001
From: Hash Borgir <atirjavid@gmail.com>
Date: Mon, 12 Feb 2024 19:12:05 -0700
Subject: [PATCH] Fixed stability issue in api.py
From 7b3421cf893ef8ea36978ae1343f7c8d5d353412 Mon Sep 17 00:00:00 2001
From: Hash Borgir <hash@stoned.io>
Date: Tue, 13 Feb 2024 15:48:35 -0700
Subject: [PATCH] api.py patch
---
simplematrixbotlib/api.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/simplematrixbotlib/api.py b/simplematrixbotlib/api.py
index 1d87b3d..13a78cf 100644
index 6d51b38..3af7e7e 100644
--- a/simplematrixbotlib/api.py
+++ b/simplematrixbotlib/api.py
@@ -295,6 +295,7 @@ class Api:
@@ -347,6 +347,7 @@ class Api:
pass # Successful upload
else:
print(f"Failed Upload Response: {resp}")
@@ -19,7 +19,7 @@ index 1d87b3d..13a78cf 100644
content = {
"body": os.path.basename(image_filepath),
@@ -342,6 +343,7 @@ class Api:
@@ -394,6 +395,7 @@ class Api:
pass # Successful upload
else:
print(f"Failed Upload Response: {resp}")

16
funguy.conf Normal file
View File

@@ -0,0 +1,16 @@
[simplematrixbotlib.config]
timeout = 65536
join_on_invite = true
encryption_enabled = false
emoji_verify = false
ignore_unverified_devices = true
store_path = "./store/"
allowlist = []
blocklist = []
admin_user = "@hashborgir:mozilla.org"
prefix = "!"
config_file = "funguy.conf"
[plugins.disabled]
"!uFhErnfpYhhlauJsNK:matrix.org" = [ "youtube-preview", "ai", "proxy",]
"!vYcfWXpPvxeQvhlFdV:matrix.org" = []

277
funguy.py
View File

@@ -1,64 +1,235 @@
#!/usr/bin/env python
# bot.py
import os
import logging
import importlib
import simplematrixbotlib as botlib
from dotenv import load_dotenv
import time
"""
Funguy Bot Class
"""
# Load environment variables from .env file
# Importing necessary libraries and modules
import os # Operating System functions
import logging # Logging library for logging messages
import importlib # Library for dynamically importing modules
import simplematrixbotlib as botlib # Library for interacting with Matrix chat
from dotenv import load_dotenv # Library for loading environment variables from a .env file
import time # Time-related functions
import sys # System-specific parameters and functions
import toml # Library for parsing TOML configuration files
# Importing FunguyConfig class from plugins.config module
from plugins.config import FunguyConfig
class FunguyBot:
"""
A bot class for managing plugins and handling commands in a Matrix chat environment.
Methods:
- __init__: Constructor method for initializing the bot.
- load_dotenv: Method to load environment variables from a .env file.
- setup_logging: Method to configure logging settings.
- load_plugins: Method to load plugins from the specified directory.
- reload_plugins: Method to reload all plugins.
- load_config: Method to load configuration settings.
- load_disabled_plugins: Method to load disabled plugins from configuration file.
- save_disabled_plugins: Method to save disabled plugins to configuration file.
- handle_commands: Method to handle incoming commands and dispatch them to appropriate plugins.
- rehash_config: Method to rehash the configuration settings.
- disable_plugin: Method to disable a plugin for a specific room.
- enable_plugin: Method to enable a plugin for a specific room.
- run: Method to initialize and run the bot.
Properties:
- PLUGINS_DIR: Directory where plugins are stored
- PLUGINS: Dictionary to store loaded plugins
- config: Configuration object
- bot: Bot object
- disabled_plugins: Dictionary to store disabled plugins for each room
"""
def __init__(self):
"""
Constructor method for FunguyBot class.
"""
# Setting up instance variables
self.PLUGINS_DIR = "plugins" # Directory where plugins are stored
self.PLUGINS = {} # Dictionary to store loaded plugins
self.config = None # Configuration object
self.bot = None # Bot object
self.disabled_plugins = {} # Dictionary to store disabled plugins for each room
self.load_dotenv() # Loading environment variables from .env file
self.setup_logging() # Setting up logging configurations
self.load_plugins() # Loading plugins
self.load_config() # Loading bot configuration
self.load_disabled_plugins() # Loading disabled plugins from configuration file
def load_dotenv(self):
"""
Method to load environment variables from a .env file.
"""
load_dotenv()
# Bot configuration settings
def setup_logging(self):
"""
Method to configure logging settings.
"""
# Basic configuration for logging messages to console
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
logging.getLogger().setLevel(logging.INFO)
def load_plugins(self):
"""
Method to load plugins from the specified directory.
"""
# Iterating through files in the plugins directory
for plugin_file in os.listdir(self.PLUGINS_DIR):
if plugin_file.endswith(".py"): # Checking if file is a Python file
plugin_name = os.path.splitext(plugin_file)[0] # Extracting plugin name
try:
# Importing plugin module dynamically
module = importlib.import_module(f"{self.PLUGINS_DIR}.{plugin_name}")
self.PLUGINS[plugin_name] = module # Storing loaded plugin module
logging.info(f"Loaded plugin: {plugin_name}") # Logging successful plugin loading
except Exception as e:
logging.error(f"Error loading plugin {plugin_name}: {e}") # Logging error if plugin loading fails
def reload_plugins(self):
"""
Method to reload all plugins.
"""
self.PLUGINS = {} # Clearing loaded plugins dictionary
# Unloading modules from sys.modules
for plugin_name in list(sys.modules.keys()):
if plugin_name.startswith(self.PLUGINS_DIR + "."):
del sys.modules[plugin_name] # Deleting plugin module from system modules
self.load_plugins() # Reloading plugins
def load_config(self):
"""
Method to load configuration settings.
"""
self.config = FunguyConfig() # Creating instance of FunguyConfig to load configuration
def load_disabled_plugins(self):
"""
Method to load disabled plugins from configuration file.
"""
# Checking if configuration file exists
if os.path.exists('funguy.conf'):
# Loading configuration data from TOML file
with open('funguy.conf', 'r') as f:
config_data = toml.load(f)
# Extracting disabled plugins from configuration data
self.disabled_plugins = config_data.get('plugins', {}).get('disabled', {})
def save_disabled_plugins(self):
"""
Method to save disabled plugins to configuration file.
"""
existing_config = {}
# Checking if configuration file exists
if os.path.exists('funguy.conf'):
# Loading existing configuration data
with open('funguy.conf', 'r') as f:
existing_config = toml.load(f)
# Updating configuration data with disabled plugins
existing_config['plugins'] = {'disabled': self.disabled_plugins}
# Writing updated configuration data back to file
with open('funguy.conf', 'w') as f:
toml.dump(existing_config, f)
async def handle_commands(self, room, message):
"""
Method to handle incoming commands and dispatch them to appropriate plugins.
"""
match = botlib.MessageMatch(room, message, self.bot, self.config.prefix) # Matching message against bot's prefix
# Reloading plugins command
if match.is_not_from_this_bot() and match.prefix() and match.command("reload"):
if str(message.sender) == self.config.admin_user: # Checking if sender is admin user
self.reload_plugins() # Reloading plugins
await self.bot.api.send_text_message(room.room_id, "Plugins reloaded successfully") # Sending success message
else:
await self.bot.api.send_text_message(room.room_id, "You are not authorized to reload plugins.") # Sending unauthorized message
# Disable plugin command
if match.is_not_from_this_bot() and match.prefix() and match.command("disable"):
if str(message.sender) == self.config.admin_user: # Checking if sender is admin user
args = match.args() # Getting command arguments
if len(args) != 2: # Checking if correct number of arguments provided
await self.bot.api.send_text_message(room.room_id, "Usage: !disable <plugin> <room_id>") # Sending usage message
else:
plugin_name, room_id = args # Extracting plugin name and room ID
await self.disable_plugin(room_id, plugin_name) # Disabling plugin
await self.bot.api.send_text_message(room.room_id, f"Plugin '{plugin_name}' disabled for room '{room_id}'") # Sending success message
else:
await self.bot.api.send_text_message(room.room_id, "You are not authorized to disable plugins.") # Sending unauthorized message
# Enable plugin command
if match.is_not_from_this_bot() and match.prefix() and match.command("enable"):
if str(message.sender) == self.config.admin_user: # Checking if sender is admin user
args = match.args() # Getting command arguments
if len(args) != 2: # Checking if correct number of arguments provided
await self.bot.api.send_text_message(room.room_id, "Usage: !enable <plugin> <room_id>") # Sending usage message
else:
plugin_name, room_id = args # Extracting plugin name and room ID
await self.enable_plugin(room_id, plugin_name) # Enabling plugin
await self.bot.api.send_text_message(room.room_id, f"Plugin '{plugin_name}' enabled for room '{room_id}'") # Sending success message
else:
await self.bot.api.send_text_message(room.room_id, "You are not authorized to enable plugins.") # Sending unauthorized message
# Dispatching commands to plugins
for plugin_name, plugin_module in self.PLUGINS.items():
if plugin_name not in self.disabled_plugins.get(room.room_id, []):
await plugin_module.handle_command(room, message, self.bot, self.config.prefix, self.config)
# Rehash config command
if match.is_not_from_this_bot() and match.prefix() and match.command("rehash"):
if str(message.sender) == self.config.admin_user: # Checking if sender is admin user
self.rehash_config() # Rehashing configuration
await self.bot.api.send_text_message(room.room_id, "Config rehashed") # Sending success message
else:
await self.bot.api.send_text_message(room.room_id, "You are not authorized to reload plugins.") # Sending unauthorized message
def rehash_config(self):
"""
Method to rehash the configuration settings.
"""
del self.config # Deleting current configuration object
self.config = FunguyConfig() # Creating new instance of FunguyConfig to load updated configuration
async def disable_plugin(self, room_id, plugin_name):
"""
Method to disable a plugin for a specific room.
"""
if room_id not in self.disabled_plugins:
self.disabled_plugins[room_id] = [] # Creating entry for room ID if not exist
if plugin_name not in self.disabled_plugins[room_id]:
self.disabled_plugins[room_id].append(plugin_name) # Adding plugin to list of disabled plugins for the room
self.save_disabled_plugins() # Saving disabled plugins to configuration file
async def enable_plugin(self, room_id, plugin_name):
"""
Method to enable a plugin for a specific room.
"""
if room_id in self.disabled_plugins and plugin_name in self.disabled_plugins[room_id]:
self.disabled_plugins[room_id].remove(plugin_name) # Removing plugin from list of disabled plugins for the room
self.save_disabled_plugins() # Saving disabled plugins to configuration file
def run(self):
"""
Method to initialize and run the bot.
"""
# Retrieving Matrix credentials from environment variables
MATRIX_URL = os.getenv("MATRIX_URL")
MATRIX_USER = os.getenv("MATRIX_USER")
MATRIX_PASS = os.getenv("MATRIX_PASS")
creds = botlib.Creds(MATRIX_URL, MATRIX_USER, MATRIX_PASS) # Creating credentials object
self.bot = botlib.Bot(creds, self.config) # Creating bot instance
ADMIN_USER = "@hashborgir:mozilla.org"
# Defining listener for message events
@self.bot.listener.on_message_event
async def wrapper_handle_commands(room, message):
await self.handle_commands(room, message) # Calling handle_commands method for incoming messages
PREFIX = '!'
self.bot.run() # Running the bot
creds = botlib.Creds(MATRIX_URL, MATRIX_USER, MATRIX_PASS)
bot = botlib.Bot(creds)
# Setup logging
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
# Load plugins
PLUGINS_DIR = "plugins"
PLUGINS = {}
def load_plugins():
for plugin_file in os.listdir(PLUGINS_DIR):
if plugin_file.endswith(".py"):
plugin_name = os.path.splitext(plugin_file)[0]
try:
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}")
def reload_plugins():
global PLUGINS
PLUGINS = {}
load_plugins()
load_plugins()
@bot.listener.on_message_event
async def handle_commands(room, message):
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.")
for plugin_name, plugin_module in PLUGINS.items():
await plugin_module.handle_command(room, message, bot, PREFIX)
bot.run()
if __name__ == "__main__":
bot = FunguyBot() # Creating instance of FunguyBot
bot.run() # Running the bot

4
g Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
git add .;
git commit -a -m "$1";
git push -u origin "$2"

145
install-funguy.sh Executable file
View File

@@ -0,0 +1,145 @@
#!/bin/bash
# Function to handle errors
handle_error() {
echo "Error: $1"
exit 1
}
# Function to handle sudo errors
handle_sudo_error() {
echo -e "\e[31mError: Failed to execute sudo command: $1\e[0m"
exit 1
}
# Function to execute sudo commands with error handling
sudo_execute() {
echo -e "\e[31m$ sudo $@\e[0m"
sudo "$@" || handle_sudo_error "$*"
}
# Function to create .env file with provided content
create_env() {
echo -e "MATRIX_URL = \"$1\"\nMATRIX_USER = \"$2\"\nMATRIX_PASS = \"$3\"" > .env || handle_error "Failed to create .env file"
}
# Function to prompt user for input
prompt_user() {
read -p "$1: " input
echo "$input"
}
# Store current directory
current_dir=$(pwd)
# Setup python virtual environment
python3 -m venv venv
source venv/bin/activate
# Check if simplematrixbotlib directory already exists
if [ -d "simplematrixbotlib" ] && [ "$(ls -A simplematrixbotlib)" ]; then
echo "simplematrixbotlib directory already exists."
else
# Clone the simplematrixbotlib repository
git clone https://codeberg.org/imbev/simplematrixbotlib.git || handle_error "Failed to clone simplematrixbotlib repository"
fi
# Copy the patch file to the simplematrixbotlib directory
cp api.py.patch "$current_dir/simplematrixbotlib/" || handle_error "Failed to copy patch file"
# Change directory to simplematrixbotlib
cd "$current_dir/simplematrixbotlib/" || handle_error "Failed to change directory to simplematrixbotlib"
# Apply the patch
git apply api.py.patch || handle_error "Failed to apply patch"
# Install simplematrixbotlib
pip install . || handle_error "Failed to install simplematrixbotlib"
# Change back to the original directory
cd "$current_dir"
# Install requirements
pip install -r requirements.txt || handle_error "Failed to install requirements"
# Prompt for Matrix homeserver
read -p "Do you want to use the default homeserver https://matrix.org? (Y/n): " use_default_hs
if [[ $use_default_hs =~ ^[Nn]$ ]]; then
homeserver=$(prompt_user "Enter Matrix homeserver URL (e.g., example.com): ")
homeserver="https://$homeserver"
else
homeserver="https://matrix.org"
fi
# Prompt for username
username=$(prompt_user "Enter Matrix username: ")
# Prompt for password
password=$(prompt_user "Enter Matrix password: ")
# Create .env file
create_env "$homeserver" "$username" "$password"
# Print ASCII art
cat << "EOF"
_____ ____ _
| ___| _ _ __ __ _ _ _ _ _ | __ ) ___ | |_
| |_ | | | | '_ \ / _\` | | | | | | | | _ \ / _ \| __|
| _|| |_| | | | | (_| | |_| | |_| | | |_) | (_) | |_
|_| \__,_|_| |_|\__, |\__,_|\__, | |____/ \___/ \__|
|___/ |___/
By HB (@hashborgir@mozilla.org)
--------------------------------------------------------
EOF
# Echo setup completion message
echo "Setting up funguy bot..."
echo "Setting up systemd service..."
working_directory=$current_dir
# Prompt for user and group
user=$(prompt_user "Enter user")
same_group=$(prompt_user "Is group same as user? (Y/n): ")
if [[ $same_group =~ ^[Yy]$ ]]; then
group=$user
else
group=$(prompt_user "Enter group")
fi
# Create systemd service file
cat <<EOF > funguybot.service
[Unit]
Description=Funguy Bot Service
After=network.target
[Service]
Type=simple
User=$user
Group=$group
WorkingDirectory=$working_directory
ExecStart=$working_directory/start-funguy.sh
Restart=on-failure
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=funguybot
[Install]
WantedBy=multi-user.target
EOF
# Copy systemd service file
sudo_execute cp funguybot.service /etc/systemd/system/ || handle_sudo_error "Failed to copy systemd service file"
# Enable and start the service
sudo_execute systemctl daemon-reload || handle_sudo_error "Failed to reload systemd daemon"
sudo_execute systemctl enable funguybot || handle_sudo_error "Failed to enable funguybot service"
sudo_execute systemctl start funguybot || handle_sudo_error "Failed to start funguybot service"
echo "Funguy bot has been successfully installed and the service has been enabled."
echo "Before starting the service, make sure to edit the .env file and add your bot account's homeserver URL, username, and password."
echo ""
echo "Once done, start the service by running the following command:"
echo ""
echo ""
echo "sudo systemctl start funguybot"

View File

@@ -1,51 +0,0 @@
#!/bin/bash
# Store current directory
current_dir=$(pwd)
# Clone the simplematrixbotlib repository
git clone https://github.com/imbev/simplematrixbotlib.git
# Copy the patch file to the simplematrixbotlib directory
cp 0001-Fixed-stability-issue-in-api.py.patch $current_dir/simplematrixbotlib/
# Change directory to simplematrixbotlib
cd $current_dir/simplematrixbotlib/
# Apply the patch
git apply 0001-Fixed-stability-issue-in-api.py.patch
# Remove the patch file
rm 0001-Fixed-stability-issue-in-api.py.patch
# Install simplematrixbotlib
pip install .
# Change back to the original directory
cd $current_dir
# Install requirements
pip install -r requirements.txt
# Create .env file with required content
echo -e "MATRIX_URL = \"https://matrix.org\"\nMATRIX_USER = \"\"\nMATRIX_PASS = \"\"" > .env
# Print ASCII art
echo " _____ ____ _ "
echo "| ___| _ _ __ __ _ _ _ _ _ | __ ) ___ | |_ "
echo "| |_ | | | | '_ \ / _\` | | | | | | | | _ \ / _ \| __| "
echo "| _|| |_| | | | | (_| | |_| | |_| | | |_) | (_) | |_ "
echo "|_| \__,_|_| |_|\__, |\__,_|\__, | |____/ \___/ \__| "
echo " |___/ |___/ "
# Echo setup completion message
echo "Bot setup completed."
# Prompt user to modify funguy.py to set ADMIN_USER
echo "Modify .env file and set your credentials and homeserver"
echo "Please open funguy.py and set the ADMIN_USER variable to your admin username."
# Launch the bot
echo "Launch the bot with 'python funguy.py'"

1194
plugins/ai.json Normal file

File diff suppressed because it is too large Load Diff

115
plugins/ai.py Normal file
View File

@@ -0,0 +1,115 @@
"""
This plugin provides commands to interact with different AI models.
"""
import logging
import requests
import json
import simplematrixbotlib as botlib
import re
import markdown2
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle AI commands.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot object.
prefix (str): The command prefix.
config (dict): Configuration parameters.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix():
logging.info(f"Received command: {match.command()}")
command = match.command()
conf = load_config()
if command in conf:
await handle_ai_command(room, bot, command, match.args(), conf)
async def handle_ai_command(room, bot, command, args, config):
"""
Function to handle AI commands.
Args:
room (Room): The Matrix room where the command was invoked.
bot (Bot): The bot object.
command (str): The name of the AI model command.
args (list): List of arguments provided with the command.
config (dict): Configuration parameters.
Returns:
None
"""
if len(args) < 1:
await bot.api.send_text_message(room.room_id, f"Usage: !{command} [prompt]")
logging.info("Sent usage message to the room")
return
prompt = ' '.join(args)
# Prepare data for the API request
url = "http://127.0.0.1:5000/v1/completions"
headers = {
"Content-Type": "application/json"
}
data = {
"prompt": f"<s>[INST]{config[command]['prompt']}{prompt}[/INST]",
"max_tokens": 1024,
"temperature": config[command]["temperature"],
"top_p": config[command]["top_p"],
"top_k": config[command]["top_k"],
"repetition_penalty": config[command]["repetition_penalty"],
"seed": -1,
"stream": False
}
# Make HTTP request to the API endpoint
try:
response = requests.post(url, headers=headers, json=data, verify=False)
response.raise_for_status() # Raise HTTPError for bad responses
payload = response.json()
new_text = payload['choices'][0]['text']
new_text = markdown_to_html(new_text)
if new_text.count('<p>') > 1 or new_text.count('<li>') > 1: # Check if new_text has more than one paragraph
new_text = f"<details><summary><strong>{config[command]['summary']}</strong></summary>{new_text}</details>"
await bot.api.send_markdown_message(room.room_id, new_text)
else:
await bot.api.send_markdown_message(room.room_id, new_text)
logging.info("Sent generated text to the room")
except requests.exceptions.RequestException as e:
logging.error(f"HTTP request failed for '{prompt}': {e}")
await bot.api.send_text_message(room.room_id, f"Error generating text: {e}")
def markdown_to_html(markdown_text):
"""
Convert Markdown text to HTML.
Args:
markdown_text (str): Markdown formatted text.
Returns:
str: HTML formatted text.
"""
html_content = markdown2.markdown(markdown_text)
return html_content
def load_config():
"""
Load configuration from ai.json file.
Returns:
dict: Configuration parameters.
"""
with open("plugins/ai.json", "r") as f:
config = json.load(f)
return config
CONFIG = load_config()

View File

@@ -1,61 +0,0 @@
# plugins/commands.py
import logging
import simplematrixbotlib as botlib
async def handle_command(room, message, bot, PREFIX):
"""
Function to handle the !commands command.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
Returns:
None
"""
match = botlib.MessageMatch(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 = """
# 🍄 Funguy Bot Commands 🍄
🃏 **!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.
Fetches the current date and time using Python's `datetime` module and sends it in a formatted message to the chat room.
💻 **!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 <domain/ip>**
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 <user>**
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 <user> 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 <user> 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 is the author of 🍄Funguy Bot🍄. (@hashborgir:mozilla.org)
# Join our Matrix Room:
[Self-hosting | Security | Sysadmin | Homelab | Programming](https://chat.mozilla.org/#/room/#selfhosting:mozilla.org)
"""
await bot.api.send_markdown_message(room.room_id, commands_message)
logging.info("Sent commands documentation to the room")

154
plugins/config.py Normal file
View File

@@ -0,0 +1,154 @@
"""
Custom configuration class for the Funguy bot.
"""
# plugins/config.py
import os
import logging
import simplematrixbotlib as botlib
from dataclasses import dataclass
@dataclass
class FunguyConfig(botlib.Config):
"""
Custom configuration class for the Funguy bot.
Extends the base Config class to provide additional configuration options.
Args:
config_file (str): Path to the configuration file.
"""
def __init__(self, config_file="funguy.conf"):
super().__init__()
# Load configuration from file
self.load_toml(config_file)
logging.info(f"Loaded configuration from {config_file}")
_admin_user: str = ""
_prefix: str = ""
_config_file: str= ""
# Define getters and setters for custom configuration options
@property
def admin_user(self):
return self._admin_user
@admin_user.setter
def admin_user(self, value):
self._admin_user = value
@property
def prefix(self):
return self._prefix
@prefix.setter
def prefix(self, value):
self._prefix = value
@property
def config_file(self):
return self._config_file
@config_file.setter
def config_file(self, value):
self._config_file = value
# Method to load configuration from file
def load_config(self, config_file):
"""
Load configuration options from a TOML file.
Args:
config_file (str): Path to the configuration file.
Returns:
None
"""
self.load_toml(config_file)
logging.info(f"Loaded configuration from {config_file}")
# Method to save configuration to file
def save_config(self, config_file):
"""
Save configuration options to a TOML file.
Args:
config_file (str): Path to the configuration file.
Returns:
None
"""
self.save_toml(config_file)
logging.info(f"Saved configuration to {config_file}")
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle commands related to bot configuration.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot instance.
PREFIX (str): The bot command prefix.
config (FunguyConfig): The bot configuration instance.
Returns:
None
"""
# Check if the message matches the command pattern and is not from this bot
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("set"):
# If the command is 'set', check if it has exactly two arguments
args = match.args()
if len(args) != 2:
await bot.api.send_text_message(room.room_id, "Usage: !set <config_option> <value>")
return
option, value = args
# Set the specified configuration option based on the provided value
if option == "admin_user":
config.admin_user = value
await bot.api.send_text_message(room.room_id, f"Admin user set to {value}")
elif option == "prefix":
config.prefix = value
await bot.api.send_text_message(room.room_id, f"Prefix set to {value}")
else:
await bot.api.send_text_message(room.room_id, "Invalid configuration option")
# If the command is 'get', retrieve the value of the specified configuration option
elif match.is_not_from_this_bot() and match.prefix() and match.command("get"):
args = match.args()
if len(args) != 1:
await bot.api.send_text_message(room.room_id, "Usage: !get <config_option>")
return
option = args[0]
if option == "admin_user":
await bot.api.send_text_message(room.room_id, f"Admin user: {config.admin_user}")
elif option == "prefix":
await bot.api.send_text_message(room.room_id, f"Prefix: {config.prefix}")
else:
await bot.api.send_text_message(room.room_id, "Invalid configuration option")
# If the command is 'saveconf', save the current configuration
elif match.is_not_from_this_bot() and match.prefix() and match.command("saveconf"):
config.save_config(config.config_file)
await bot.api.send_text_message(room.room_id, "Configuration saved")
# If the command is 'loadconf', load the saved configuration
elif match.is_not_from_this_bot() and match.prefix() and match.command("loadconf"):
config.load_config(config.config_file)
await bot.api.send_text_message(room.room_id, "Configuration loaded")
# If the command is 'show', display the current configuration
elif match.is_not_from_this_bot() and match.prefix() and match.command("show"):
admin_user = config.admin_user
prefix = config.prefix
await bot.api.send_text_message(room.room_id, f"Admin user: {admin_user}, Prefix: {prefix}")
# If the command is 'reset', reset the configuration to default values
elif match.is_not_from_this_bot() and match.prefix() and match.command("reset"):
config.admin_user = ""
config.prefix = "!"
await bot.api.send_text_message(room.room_id, "Configuration reset")

View File

@@ -1,10 +1,14 @@
"""
This plugin provides a command to get the current date
"""
# plugins/date.py
import datetime
import logging
import simplematrixbotlib as botlib
async def handle_command(room, message, bot, PREFIX):
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle the !date command.
@@ -15,14 +19,43 @@ async def handle_command(room, message, bot, PREFIX):
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, PREFIX)
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("date"):
logging.info("Fetching current date and time")
current_datetime = datetime.datetime.now()
# Extract individual date components
day_of_week = current_datetime.strftime("%A")
date_of_month = current_datetime.strftime("%d") # Day with leading zero
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}**"
# Format date with ordinal suffix
date_with_ordinal = f"{date_of_month}{get_ordinal_suffix(date_of_month)}"
# Construct the message
date_message = f"Date: **{day_of_week}** the {date_with_ordinal}, **{month} {year}**. \nTime: **⏰ {time}**"
await bot.api.send_markdown_message(room.room_id, date_message)
logging.info("Sent current date and time to the room")
def get_ordinal_suffix(day_of_month):
"""
Helper function to get the ordinal suffix for a day number.
Args:
day_of_month (str): The day number as a string (e.g., "1", "13")
Returns:
str: The ordinal suffix (e.g., "st", "th", "nd")
"""
if day_of_month.endswith("11") or day_of_month.endswith("12") or day_of_month.endswith("13"):
return "th"
elif day_of_month.endswith("1") or day_of_month.endswith("3"):
return "st"
elif day_of_month.endswith("2"):
return "nd"
else:
return "th"

View File

@@ -1,10 +1,13 @@
"""
This plugin provides a command to get a random fortune message.
"""
# plugins/fortune.py
import subprocess
import logging
import simplematrixbotlib as botlib
async def handle_command(room, message, bot, PREFIX):
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle the !fortune command.
@@ -15,7 +18,7 @@ async def handle_command(room, message, bot, PREFIX):
Returns:
None
"""
match = botlib.MessageMatch(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')

83
plugins/help.py Normal file
View File

@@ -0,0 +1,83 @@
"""
Plugin for providing a command to display the list of available commands and their descriptions.
"""
import logging
import simplematrixbotlib as botlib
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle the !help command.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (MatrixBot): The Matrix bot instance.
prefix (str): The command prefix.
config (dict): The bot's configuration.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("help"):
logging.info("Fetching command help documentation")
commands_message = """
<details><summary><strong>🍄 Funguy Bot Commands 🍄</strong></summary>
<p>
<details><summary>🃏 <strong>!fortune</strong></summary>
<p>Returns a random fortune message. Executes the `/usr/games/fortune` utility and sends the output as a message to the chat room.</p>
</details>
<details><summary>⏰ <strong>!date</strong></summary>
<p>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.</p>
</details>
<details><summary>💻 <strong>!proxy</strong></summary>
<p>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.</p>
</details>
<details><summary>📶 <strong>!isup [domain/ip]</strong></summary>
<p>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.</p>
</details>
<details><summary>☯ <strong>!karma [user]</strong></summary>
<p>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.</p>
</details>
<details><summary>⇧ <strong>!karma [user] up</strong></summary>
<p>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.</p>
</details>
<details><summary>⇩ <strong>!karma [user] down</strong></summary>
<p>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.</p>
</details>
<details><summary>📸 <strong>!sd [prompt]</strong></summary>
<p>Generates images using self-hosted Stable Diffusion. See available options using just '!sd'.</p>
</details>
<details><summary>💡 <strong>!enable</strong></summary>
<p>Enables a disabled command. Use '!enable plugin room' to enable a specific command.</p>
</details>
<details><summary>🚫 <strong>!disable</strong></summary>
<p>Disables a command. Use '!disable plugin room' to disable a specific command.</p>
</details>
<details><summary>🌟 <strong>Funguy Bot Credits</strong> 🌟</summary>
<p>🧙‍♂️ Creator & Developer: Hash Borgir is the author of 🍄Funguy Bot🍄. (@hashborgir:mozilla.org)</p>
<p>Join our Matrix Room: [Self-hosting | Security | Sysadmin | Homelab | Programming](https://matrix.to/#/#selfhosting:mozilla.org)</p>
</details>
</p>
</details>
<hr>
<details><summary>📸 <strong>Funguy Bot AI Commands</strong> 🌟</summary>
<p>
!tech, !music, !eth, !seo, !eng, !intv, !pron, !spk, !trv, !plag, !char, !adv, !story, !foot, !comic, !motiv, !debate, !write, !script, !author, !crit, !rel, !poem, !rap, !speak, !phil, !math, !tutor, !design, !sec, !recruit, !coach, !etymo, !com, !magic, !counsel, !behavior, !fit, !mh, !realest, !log, !dental, !web, !health, !acc, !chef, !auto, !art, !fin, !invest, !tea, !interior, !florist, !selfhelp, !gnome, !aph, !adv, !advgame, !esc, !title, !stats, !prompt, !teach, !diet, !psych, !domain, !tech, !review, !devrel, !acad, !arch, !insane, !manip, !logic, !review, !diy, !influencer, !philos, !socrat, !edu, !meditate, !writer, !smm, !eloc, !viz, !nav, !hypno, !hist, !astro, !critic, !comp, !journo, !curation, !pscoach, !makeup, !childcare, !writing, !art, !py, !syn, !shop, !dining, !telemed, !cook, !law, !fashion, !ml, !trans, !design, !it, !chess, !prompt, !dev, !math, !regex, !time, !dream, !coach, !r, !comm, !trans, !php, !emergency, !worksheet, !test, !game, !security, !create, !browse, !dev, !search, !startup, !guide, !langdet, !sales, !msg, !ceo, !diag, !coach, !therapy, !legal, !gen, !mgmt, !drunk, !hist, !rec, !write, !techtrans, !ai, !game, !proof, !spirit, !spirit, !chem, !friend, !py, !chat, !wiki, !kanji, !note, !litcrit, !enhance, !travel, !data, !gaming
</p>
</details>
"""
await bot.api.send_markdown_message(room.room_id, commands_message)
logging.info("Sent help documentation to the room")

View File

@@ -1,3 +1,7 @@
"""
This plugin provides a command to check if a website or server is up.
"""
# plugins/isup.py
import logging
@@ -39,45 +43,60 @@ async def check_https(domain):
except aiohttp.ClientError:
return False
async def handle_command(room, message, bot, PREFIX):
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle the !isup command.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (Bot): The bot instance.
prefix (str): The bot command prefix.
config (FunguyConfig): The bot configuration instance.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, PREFIX)
# Check if the message matches the command pattern and is not from this bot
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("isup"):
# Log that the !isup command has been received
logging.info("Received !isup command")
args = match.args()
# Check if the command has exactly one argument
if len(args) != 1:
# If the command does not have exactly one argument, send usage message
await bot.api.send_markdown_message(room.room_id, "Usage: !isup <ipv4/ipv6/domain>")
logging.info("Sent usage message to the room")
return
target = args[0]
# DNS resolution
# Perform DNS resolution
try:
ip_address = socket.gethostbyname(target)
# Log successful DNS resolution
logging.info(f"DNS resolution successful for {target}: {ip_address}")
# Send DNS resolution success message
await bot.api.send_markdown_message(room.room_id, f"✅ DNS resolution successful for **{target}**: **{ip_address}** (A record)")
except socket.gaierror:
# Log DNS resolution failure
logging.info(f"DNS resolution failed for {target}")
# Send DNS resolution failure message
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):
# If HTTP service is up, send HTTP service up message
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):
# If HTTPS service is up, send HTTPS service up message
await bot.api.send_markdown_message(room.room_id, f"🖧 **{target}** HTTPS service is up")
logging.info(f"{target} HTTPS service is up")
else:
# If both HTTP and HTTPS services are down, send service down message
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")

View File

@@ -1,10 +1,15 @@
"""
This plugin provides a command to manage karma points for users.
"""
# plugins/karma.py
import sqlite3
import logging
import simplematrixbotlib as botlib
async def handle_command(room, message, bot, PREFIX):
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle the !karma command.
@@ -15,7 +20,7 @@ async def handle_command(room, message, bot, PREFIX):
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, PREFIX)
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("karma"):
logging.info("Received !karma command")
args = match.args()

115
plugins/loadplugin.py Normal file
View File

@@ -0,0 +1,115 @@
"""
Plugin for providing a command for the admin to load a plugin.
"""
import os
import logging
import importlib
import simplematrixbotlib as botlib
import sys # Import sys module for unloading plugins
# Dictionary to store loaded plugins
PLUGINS = {}
async def load_plugin(plugin_name):
"""
Asynchronously loads a plugin.
Args:
plugin_name (str): The name of the plugin to load.
Returns:
bool: True if the plugin is loaded successfully, False otherwise.
"""
try:
# Import the plugin module
module = importlib.import_module(f"plugins.{plugin_name}")
# Add the plugin module to the PLUGINS dictionary
PLUGINS[plugin_name] = module
logging.info(f"Loaded plugin: {plugin_name}")
return True
except Exception as e:
# Log an error if the plugin fails to load
logging.error(f"Error loading plugin {plugin_name}: {e}")
return False
async def unload_plugin(plugin_name):
"""
Asynchronously unloads a plugin.
Args:
plugin_name (str): The name of the plugin to unload.
Returns:
bool: True if the plugin is unloaded successfully, False otherwise.
"""
try:
if plugin_name in PLUGINS:
del PLUGINS[plugin_name] # Remove the plugin from the PLUGINS dictionary
del sys.modules[f"plugins.{plugin_name}"] # Unload the plugin module from sys.modules
logging.info(f"Unloaded plugin: {plugin_name}")
return True
else:
logging.warning(f"Plugin '{plugin_name}' is not loaded")
return False
except Exception as e:
# Log an error if the plugin fails to unload
logging.error(f"Error unloading plugin {plugin_name}: {e}")
return False
async def handle_command(room, message, bot, prefix, config):
"""
Asynchronously handles the command to load or unload a plugin.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (MatrixBot): The Matrix bot instance.
prefix (str): The command prefix.
config (dict): The bot's configuration.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix():
command = match.command()
if command == "load":
if str(message.sender) == config.admin_user:
args = match.args()
if len(args) != 1:
# Send usage message if the command format is incorrect
await bot.api.send_text_message(room.room_id, "Usage: !load <plugin>")
else:
plugin_name = args[0]
# Check if the plugin is not already loaded
if plugin_name not in PLUGINS:
# Load the plugin
success = await load_plugin(plugin_name)
if success:
await bot.api.send_text_message(room.room_id, f"Plugin '{plugin_name}' loaded successfully")
else:
await bot.api.send_text_message(room.room_id, f"Error loading plugin '{plugin_name}'")
else:
await bot.api.send_text_message(room.room_id, f"Plugin '{plugin_name}' is already loaded")
else:
# Send unauthorized message if the sender is not the admin
await bot.api.send_text_message(room.room_id, "You are not authorized to load plugins.")
elif command == "unload":
if str(message.sender) == config.admin_user:
args = match.args()
if len(args) != 1:
# Send usage message if the command format is incorrect
await bot.api.send_text_message(room.room_id, "Usage: !unload <plugin>")
else:
plugin_name = args[0]
# Unload the plugin
success = await unload_plugin(plugin_name)
if success:
await bot.api.send_text_message(room.room_id, f"Plugin '{plugin_name}' unloaded successfully")
else:
await bot.api.send_text_message(room.room_id, f"Error unloading plugin '{plugin_name}'")
else:
# Send unauthorized message if the sender is not the admin
await bot.api.send_text_message(room.room_id, "You are not authorized to unload plugins.")

58
plugins/plugins.py Normal file
View File

@@ -0,0 +1,58 @@
"""
This plugin provides a command to list all loaded plugins along with their descriptions.
"""
# plugins/plugins.py
import os
import sys
import logging
import simplematrixbotlib as botlib
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle the !plugins command.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("plugins"):
logging.info("Received !plugins command")
plugin_descriptions = get_plugin_descriptions()
# Prepend custom string before the output
plugin_descriptions.insert(0, "<details><summary><strong>🔌Plugins List🔌<br>⤵Click Here to Expand⤵</strong></summary>")
plugins_message = "<br>".join(plugin_descriptions)
plugins_message += "</details>"
await bot.api.send_markdown_message(room.room_id, plugins_message)
logging.info("Sent plugin list to the room")
def get_plugin_descriptions():
"""
Function to get descriptions of all loaded plugin modules.
Returns:
list: A list of plugin descriptions sorted alphabetically.
"""
plugin_descriptions = []
for module_name, module in sys.modules.items():
if module_name.startswith("plugins.") and hasattr(module, "__file__"):
plugin_path = module.__file__
plugin_name = os.path.basename(plugin_path).split(".")[0]
try:
description = module.__doc__.strip().split("\n")[0]
except AttributeError:
description = "No description available"
plugin_descriptions.append(f"<strong>[{plugin_name}.py]:</strong> {description}")
# Sort the plugin descriptions alphabetically by plugin name
plugin_descriptions.sort()
return plugin_descriptions

View File

@@ -1,3 +1,7 @@
"""
This plugin provides a command to get random SOCKS5 proxies.
"""
# plugins/proxy.py
import os
@@ -5,44 +9,50 @@ import logging
import random
import requests
import socket
import ssl
import time
from datetime import datetime, timedelta
import concurrent.futures
import simplematrixbotlib as botlib
import sqlite3
# Constants
SOCKS5_LIST_URL = 'https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks5.txt'
MAX_TRIES = 5
# SOCKS5_LIST_URL = 'https://raw.githubusercontent.com/proxifly/free-proxy-list/main/proxies/protocols/socks5/data.txt'
MAX_TRIES = 64
PROXY_LIST_FILENAME = 'socks5.txt'
PROXY_LIST_EXPIRATION = timedelta(hours=8)
MAX_THREADS = 128
PROXIES_DB_FILE = 'proxies.db'
MAX_PROXIES_IN_DB = 500
async def test_socks5_proxy(proxy):
# Setup verbose logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
def test_proxy(proxy):
"""
Test a SOCKS5 proxy by attempting a connection and sending a request through it.
Test a SOCKS5 proxy and return the outcome.
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.
tuple: (bool: success, str: proxy, int: latency)
"""
try:
ip, port = proxy.split(':')
logging.info(f"Testing SOCKS5 proxy: {ip}:{port}")
start_time = time.time()
with socket.create_connection((ip, int(port)), timeout=5) as client:
with socket.create_connection((ip, int(port)), timeout=12) 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
return True, proxy, latency
else:
logging.info(f"Failed to connect to SOCKS5 proxy {proxy}: Connection refused")
return False, None
return False, proxy, None
except Exception as e:
logging.error(f"Error testing SOCKS5 proxy {proxy}: {e}")
return False, None
return False, proxy, None
async def download_proxy_list():
"""
@@ -68,7 +78,70 @@ async def download_proxy_list():
return False
async def handle_command(room, message, bot, PREFIX):
def check_db_for_proxy():
"""
Check the proxies database for a working proxy.
If found, test the proxy and remove it from the database if it doesn't work.
Returns:
str or None: The working proxy if found, None otherwise.
"""
try:
with sqlite3.connect(PROXIES_DB_FILE) as conn:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS proxies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
proxy TEXT,
latency INTEGER,
status TEXT
)
""")
cursor.execute("SELECT proxy, latency FROM proxies WHERE status = 'working' AND latency < 3000 ORDER BY RANDOM() LIMIT 1")
result = cursor.fetchone()
if result:
proxy, latency = result
success, _, _ = test_proxy(proxy)
if success:
return proxy, latency
else:
cursor.execute("DELETE FROM proxies WHERE proxy = ?", (proxy,))
conn.commit()
logging.info(f"Removed non-working proxy from the database: {proxy}")
return None, None
else:
return None, None
except Exception as e:
logging.error(f"Error checking proxies database: {e}")
return None, None
def save_proxy_to_db(proxy, latency):
"""
Save a working proxy to the proxies database.
Args:
proxy (str): The working proxy to be saved.
latency (int): Latency of the proxy.
"""
try:
with sqlite3.connect(PROXIES_DB_FILE) as conn:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS proxies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
proxy TEXT,
latency INTEGER,
status TEXT
)
""")
cursor.execute("INSERT INTO proxies (proxy, latency, status) VALUES (?, ?, 'working')", (proxy, latency))
conn.commit()
except Exception as e:
logging.error(f"Error saving proxy to database: {e}")
async def handle_command(room, message, bot, prefix, config):
"""
Function to handle the !proxy command.
@@ -79,11 +152,20 @@ async def handle_command(room, message, bot, PREFIX):
Returns:
None
"""
match = botlib.MessageMatch(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")
# Check database for a working proxy
working_proxy, latency = check_db_for_proxy()
if working_proxy:
await bot.api.send_markdown_message(room.room_id,
f"✅ Using cached working SOCKS5 Proxy: **{working_proxy}** - Latency: **{latency} ms**")
logging.info(f"Using cached working SOCKS5 proxy {working_proxy}")
return
# Download proxy list if needed
else:
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")
@@ -92,25 +174,40 @@ async def handle_command(room, message, bot, PREFIX):
try:
# Read proxies from file
with open(PROXY_LIST_FILENAME, 'r') as f:
socks5_proxies = f.read().splitlines()
socks5_proxies = [line.replace("socks5://", "") for line in f.read().splitlines()]
random.shuffle(socks5_proxies)
# Test proxies
socks5_proxy = None
# Test proxies concurrently
tested_proxies = 0
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
futures = []
for proxy in socks5_proxies[:MAX_TRIES]:
success, latency = await test_socks5_proxy(proxy)
futures.append(executor.submit(test_proxy, proxy))
for future in concurrent.futures.as_completed(futures):
success, proxy, latency = future.result()
if success:
socks5_proxy = proxy
break
await bot.api.send_markdown_message(room.room_id,
f"✅ Anonymous SOCKS5 Proxy: **{proxy}** - Latency: **{latency} ms**")
logging.info(f"Sent SOCKS5 proxy {proxy} to the room")
save_proxy_to_db(proxy, latency) # Save working proxy to the database
tested_proxies += 1
if tested_proxies >= MAX_PROXIES_IN_DB:
break # Stop testing proxies once MAX_PROXIES_IN_DB are saved to the database
# Check database for a working proxy after testing
working_proxy, latency = check_db_for_proxy()
if working_proxy:
await bot.api.send_markdown_message(room.room_id,
f"✅ Using cached working SOCKS5 Proxy: **{working_proxy}** - Latency: **{latency} ms**")
logging.info(f"Using cached working SOCKS5 proxy {working_proxy}")
# 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:
# If no working proxy found after testing
await bot.api.send_markdown_message(room.room_id, "❌ No working anonymous SOCKS5 proxy found")
logging.info("No working anonymous SOCKS5 proxy found")
except Exception as e:
logging.error(f"Error handling !proxy command: {e}")
await bot.api.send_markdown_message(room.room_id, "❌ Error handling !proxy command")

178
plugins/stable-diffusion.py Normal file
View File

@@ -0,0 +1,178 @@
"""
Plugin for generating images using self-hosted Stable Diffusion and sending them to a Matrix chat room.
"""
import requests
import base64
from asyncio import Queue
import argparse
import simplematrixbotlib as botlib
import markdown2
from slugify import slugify
def slugify_prompt(prompt):
"""
Generates a URL-friendly slug from the given prompt.
Args:
prompt (str): The prompt to slugify.
Returns:
str: A URL-friendly slug version of the prompt.
"""
return slugify(prompt)
# Queue to store pending commands
command_queue = Queue()
def markdown_to_html(markdown_text):
"""
Converts Markdown text to HTML.
Args:
markdown_text (str): The Markdown text to convert.
Returns:
str: The HTML version of the input Markdown text.
"""
html_content = markdown2.markdown(markdown_text)
return html_content
async def process_command(room, message, bot, prefix, config):
"""
Asynchronously processes the commands received in the Matrix room.
Args:
room (Room): The Matrix room where the command was received.
message (Message): The message object containing the command.
bot (MatrixBot): The Matrix bot instance.
prefix (str): The command prefix.
config (dict): The bot's configuration.
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.prefix() and match.command("sd"):
if command_queue.empty():
await handle_command(room, message, bot, prefix, config)
else:
await command_queue.put((room, message, bot, prefix, config))
async def handle_command(room, message, bot, prefix, config):
"""
Asynchronously handles the 'sd' command, which generates images using Stable Diffusion.
Args:
room (Room): The Matrix room where the command was received.
message (Message): The message object containing the command.
bot (MatrixBot): The Matrix bot instance.
prefix (str): The command prefix.
config (dict): The bot's configuration.
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.prefix() and match.command("sd"):
try:
# Parse command-line arguments
parser = argparse.ArgumentParser(description='Generate images using self-hosted Stable Diffusion')
parser.add_argument('--steps', type=int, default=4, help='Number of steps, default=16')
parser.add_argument('--cfg', type=int, default=1.25, help='CFG scale, default=7')
parser.add_argument('--h', type=int, default=512, help='Height of the image, default=512')
parser.add_argument('--w', type=int, default=512, help='Width of the image, default=512')
parser.add_argument('--neg', type=str, default='((((ugly)))), (((duplicate))), ((morbid)), ((mutilated)), out of frame, extra fingers, mutated hands, ((poorly drawn hands)), ((poorly drawn face)), (((mutation))), (((deformed))), ((ugly)), blurry, ((bad anatomy)), (((bad proportions))), ((extra limbs)), cloned face, (((disfigured))), out of frame, ugly, extra limbs, (bad anatomy), gross proportions, (malformed limbs), ((missing arms)), ((missing legs)), (((extra arms))), (((extra legs))), mutated hands, (fused fingers), (too many fingers), (((long neck)))', nargs='+', help='Negative prompt, default=none')
parser.add_argument('--sampler', type=str, nargs='*', default=['DPM++', 'SDE', 'Karras'], help='Sampler name, default=Euler a')
parser.add_argument('prompt', type=str, nargs='*', help='Prompt for the image')
args = parser.parse_args(message.body.split()[1:]) # Skip the command itself
if not args.prompt:
raise argparse.ArgumentError(None, "Prompt is required.")
prompt = ' '.join(args.prompt)
sampler_name = ' '.join(args.sampler)
neg = ' '.join(args.neg)
payload = {
"prompt": prompt,
"steps": args.steps,
"negative_prompt": neg,
"sampler_name": sampler_name,
"cfg_scale": args.cfg,
"width": args.w,
"height": args.h,
}
url = "http://127.0.0.1:7860/sdapi/v1/txt2img"
# Send request to the Stable Diffusion API
response = requests.post(url=url, json=payload)
r = response.json()
# Slugify the prompt
prompt_slug = slugify(prompt[:120])
# Construct filename
filename = f"{prompt_slug}_{args.steps}_{args.h}x{args.w}_{sampler_name}_{args.cfg}.jpg"
with open(f"/tmp/{filename}", 'wb') as f:
f.write(base64.b64decode(r['images'][0]))
# Send the generated image to the Matrix room
await bot.api.send_image_message(room_id=room.room_id, image_filepath=f"/tmp/{filename}")
# Format and send information about the generated image
neg = neg.replace(" ", "")
info_msg = f"""<details><summary>🍄⤵Image Info:⤵︎</summary><strong>Prompt:</strong> {prompt_slug}<br><strong>Steps:</strong> {args.steps}<br><strong>Dimensions:</strong> {args.h}x{args.w}<br><strong>Sampler:</strong> {sampler_name}<br><strong>CFG Scale:</strong> {args.cfg}<br><strong>Negative Prompt: {neg}</strong></details>"""
await bot.api.send_markdown_message(room.room_id, info_msg)
except argparse.ArgumentError as e:
# Handle argument errors
await bot.api.send_text_message(room.room_id, f"Error: {e}")
await bot.api.send_markdown_message(room.room_id, "<details><summary>Stable Diffusion Help</summary>" + print_help() + "</details>")
except Exception as e:
# Handle general errors
await bot.api.send_text_message(room.room_id, f"Error processing the command: {str(e)}")
finally:
# Process next command from the queue, if any
if not command_queue.empty():
next_command = await command_queue.get()
await handle_command(*next_command)
def print_help():
"""
Generates help text for the 'sd' command.
Returns:
str: Help text for the 'sd' command.
"""
return """
<p>Generate images using self-hosted Stable Diffusion</p>
<p>Positional arguments:</p>
<ul>
<li>prompt - Prompt for the image</li>
</ul>
<p>Default Negative Prompts: ((((ugly)))), (((duplicate))), ((morbid)), ((mutilated)), out of frame, extra fingers, mutated hands, ((poorly drawn hands)), ((poorly drawn face)), (((mutation))), (((deformed))), ((ugly)), blurry, ((bad anatomy)), (((bad proportions))), ((extra limbs)), cloned face, (((disfigured))), out of frame, ugly, extra limbs, (bad anatomy), gross proportions, (malformed limbs), ((missing arms)), ((missing legs)), (((extra arms))), (((extra legs))), mutated hands, (fused fingers), (too many fingers), (((long neck)))</p>
<p>Optional arguments:</p>
<ul>
<li>--steps STEPS - Number of steps, default=16</li>
<li>--cfg CFG - CFG scale, default=7</li>
<li>--h H - Height of the image, default=512</li>
<li>--w W - Width of the image, default=512</li>
<li>--neg NEG - Negative prompt, default=none</li>
<li>--sampler SAMPLER - Sampler name, default=Euler a</li>
</ul>
<p>LORA List:</p>
<ul>
<li>&lt;psychedelicai-SDXL&gt;</li>
<li>&lt;ral-frctlgmtry-sdxl&gt;</li>
<li>&lt;SDXL-PsyAI-v4&gt;</li>
<li>&lt;al3xxl&gt;</li>
</ul>
<p>Load LORAs like this:</p>
<ul>
<li>&lt;lora:SDXL-PsyAI-v4:1&gt; PsyAI</li>
<li>&lt;lora:psychedelicai-SDXL:1&gt; Psychedelic</li>
<li>&lt;ral-frctlgmtry-sdxl&gt; ral-frctlgmtry</li>
<li>&lt;lora:al3xxl:1&gt; alexpainting, alexhuman, alexentity, alexthirdeye, alexforeheads, alexgalactic, spiraling, alexmirror, alexangel, alexkissing, alexthirdeye2, alexthirdeye3, alexhofmann, wearing, glasses, alexgalactic3, alexthirdeye4, alexhuman3, alexgalactic2, alexhuman2, alexbeing2, alexfractal2, alexfractal3</li>
</ul>
"""

124
plugins/youtube-preview.py Normal file
View File

@@ -0,0 +1,124 @@
"""
Plugin for providing a command to fetch YouTube video information from links.
"""
# Importing necessary libraries
import re
import logging
import asyncio
import aiohttp
from pytubefix import YouTube
import simplematrixbotlib as botlib
from youtube_title_parse import get_artist_title
LYRICIST_API_URL = "https://lyrist.vercel.app/api/{}"
def seconds_to_minutes_seconds(seconds):
"""
Converts seconds to a string representation of minutes and seconds.
Args:
seconds (int): The number of seconds.
Returns:
str: A string representation of minutes and seconds in the format MM:SS.
"""
minutes = seconds // 60
seconds %= 60
return f"{minutes:02d}:{seconds:02d}"
async def fetch_lyrics(song, artist):
"""
Asynchronously fetches lyrics for a song from the Lyricist API.
Args:
song (str): The name of the song.
artist (str): The name of the artist.
Returns:
str: Lyrics of the song.
None if an error occurs during fetching.
"""
try:
async with aiohttp.ClientSession() as session:
async with session.get(LYRICIST_API_URL.format(song, artist)) as response:
data = await response.json()
return data.get("lyrics")
except Exception as e:
logging.error(f"Error fetching lyrics: {str(e)}")
return None
async def fetch_youtube_info(youtube_url):
"""
Asynchronously fetches information about a YouTube video.
Args:
youtube_url (str): The URL of the YouTube video.
Returns:
str: A message containing information about the YouTube video.
None if an error occurs during fetching.
"""
try:
video = YouTube(youtube_url)
title = video.title
artist, song = get_artist_title(title)
description = video.description
length = seconds_to_minutes_seconds(video.length)
views = video.views
author = video.author
description_with_breaks = description.replace('\n', '<br>')
# Fetching lyrics
lyrics = await fetch_lyrics(song, artist)
lyrics = lyrics.replace('\n', "<br>")
info_message = f"""<strong>🎬🎝 Title:</strong> {title} | <strong>Length</strong>: {length} minutes | <strong>Views</strong>: {views}\n<details><summary><strong>⤵Description⤵</strong></summary>{description_with_breaks}</details>"""
if lyrics:
info_message += f"<br><details><summary><strong>🎵 Lyrics:</strong></summary><br>{lyrics}</details>"
return info_message
except Exception as e:
logging.error(f"Error fetching YouTube video information: {str(e)}")
return None
async def handle_command(room, message, bot, prefix, config):
"""
Asynchronously handles the command to fetch YouTube video information.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (MatrixBot): The Matrix bot instance.
prefix (str): The command prefix.
config (dict): The bot's configuration.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, prefix)
if match.is_not_from_this_bot() and re.search(r'youtube\.com/watch\?v=', message.body):
logging.info("YouTube link detected")
video_id_match = re.search(r'youtube\.com/watch\?v=([^\s]+)', message.body)
if video_id_match:
video_id = video_id_match.group(1)
youtube_url = f"https://www.youtube.com/watch?v={video_id}"
logging.info(f"Fetching information for YouTube video: {youtube_url}")
retry_count = 3
while retry_count > 0:
info_message = await fetch_youtube_info(youtube_url)
if info_message:
await bot.api.send_markdown_message(room.room_id, info_message)
logging.info("Sent YouTube video information to the room")
break
else:
logging.info("Retrying...")
retry_count -= 1
await asyncio.sleep(1) # wait for 1 second before retrying
else:
logging.error("Failed to fetch YouTube video information after retries")

73
plugins/youtube-search.py Normal file
View File

@@ -0,0 +1,73 @@
"""
Plugin for providing a command to search for YouTube videos in the room.
"""
import logging
import simplematrixbotlib as botlib
from youtube_search import YoutubeSearch
async def handle_command(room, message, bot, PREFIX, config):
"""
Asynchronously handles the command to search for YouTube videos in the room.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
bot (MatrixBot): The Matrix bot instance.
PREFIX (str): The command prefix.
config (dict): The bot's configuration.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot, PREFIX)
if match.is_not_from_this_bot() and match.prefix() and match.command("yt"):
args = match.args()
if len(args) < 1:
await bot.api.send_text_message(room.room_id, "Usage: !yt <search terms>")
else:
search_terms = " ".join(args)
logging.info(f"Performing YouTube search for: {search_terms}")
results = YoutubeSearch(search_terms, max_results=10).to_dict()
if results:
output = generate_output(results)
await send_collapsible_message(room, bot, output)
else:
await bot.api.send_text_message(room.room_id, "No results found.")
def generate_output(results):
"""
Generates HTML output for displaying YouTube search results.
Args:
results (list): A list of dictionaries containing information about YouTube videos.
Returns:
str: HTML formatted output containing YouTube search results.
"""
output = ""
for video in results:
output += f'<a href="https://www.youtube.com/watch?v={video["id"]}">'
output += f'<img src="{video["thumbnails"][0]}"></img><br>'
output += f'<strong>{video["title"]}</strong><br>'
output += f'Length: {video["duration"]} | Views: {video["views"]}<br>'
if video["long_desc"]:
output += f'Description: {video["long_desc"]}<br>'
output += "</a><br>"
return output
async def send_collapsible_message(room, bot, content):
"""
Sends a collapsible message containing YouTube search results to the room.
Args:
room (Room): The Matrix room where the message will be sent.
bot (MatrixBot): The Matrix bot instance.
content (str): HTML content to be included in the collapsible message.
Returns:
None
"""
message = f'<details><summary><strong>🍄Funguy ▶YouTube Search🍄<br>⤵Click Here To See Results⤵</strong></summary>{content}</details>'
await bot.api.send_markdown_message(room.room_id, message)

View File

@@ -1,39 +0,0 @@
# plugins/youtube.py
import re
import logging
from pytube import YouTube
import simplematrixbotlib as botlib
async def handle_command(room, message, bot, PREFIX):
"""
Function to handle YouTube video information from links.
Args:
room (Room): The Matrix room where the command was invoked.
message (RoomMessage): The message object containing the command.
Returns:
None
"""
match = botlib.MessageMatch(room, message, bot)
if match.is_not_from_this_bot() and re.search(r'youtube\.com/watch\?v=', message.body):
logging.info("YouTube link detected")
video_id_match = re.search(r'youtube\.com/watch\?v=([^\s]+)', message.body)
if video_id_match:
video_id = video_id_match.group(1)
youtube_url = f"https://www.youtube.com/watch?v={video_id}"
logging.info(f"Fetching information for YouTube video: {youtube_url}")
try:
video = YouTube(youtube_url)
title = video.title
description = video.description
length = video.length
views = video.views
author = video.author
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__message(room.room_id, "Error fetching YouTube video information.")

View File

@@ -1,35 +1,12 @@
aiofiles==23.2.1
aiohttp==3.9.3
aiohttp-socks==0.7.1
aiosignal==1.3.1
async-timeout==4.0.3
attrs==23.2.0
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
cryptography==42.0.2
frozenlist==1.4.1
h11==0.14.0
h2==4.1.0
hpack==4.0.0
hyperframe==6.0.1
idna==3.6
jsonschema==4.21.1
jsonschema-specifications==2023.12.1
Markdown==3.5.2
matrix-nio==0.23.0
multidict==6.0.5
pillow==10.2.0
pycparser==2.21
pycryptodome==3.20.0
python-cryptography-fernet-wrapper==1.0.4
python-dotenv==1.0.1
python-socks==2.4.4
pytube==15.0.0
referencing==0.33.0
requests==2.31.0
rpds-py==0.17.1
toml==0.10.2
unpaddedbase64==2.1.0
urllib3==2.2.0
yarl==1.9.4
python-dotenv
requests
pytubefix
duckduckgo_search
nio
markdown2
watchdog
emoji
python-slugify
youtube_title_parse
dnspython

17
start-funguy.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/bash
# Function to handle errors
handle_error() {
echo "Error: $1"
exit 1
}
# Load Python virtual environment
source venv/bin/activate || { echo "Error: Failed to load Python virtual environment."; exit 1; }
# Get python path
python=$(which python)
# Run funguy.py
$python funguy.py || { echo "Error: Failed to execute funguy.py."; exit 1; }