Compare commits
32 Commits
33ad735292
...
main
Author | SHA1 | Date | |
---|---|---|---|
5d746027e2 | |||
d2acef611b | |||
e8186c9fec | |||
543e139ca0 | |||
551c5ddc02 | |||
387606426c | |||
41c59618cc | |||
5d6ad98303 | |||
4ef6f3beb7 | |||
c4b6c750fb | |||
ae2bdfecae | |||
25f9159155 | |||
803acf514b | |||
542bb5d5cd | |||
5962eb53ad | |||
eb81f7aa67 | |||
c996168543 | |||
784409cef6 | |||
e9a853c31a | |||
669fd361d4 | |||
58fa16663e | |||
911f8dd47a | |||
|
b10178452b | ||
|
5f5c9d2a0c | ||
|
bb331092d0 | ||
|
2277dd1b90 | ||
|
aa7e76e653 | ||
|
976f806397 | ||
|
8c23deb13b | ||
|
df87d39e82 | ||
|
f50fff0ba6 | ||
|
a459c1299d |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,9 +1,12 @@
|
|||||||
.env
|
.env
|
||||||
.gitignore
|
|
||||||
karma.db
|
karma.db
|
||||||
proxies.db
|
proxies.db
|
||||||
session.txt
|
session.txt
|
||||||
socks5.txt
|
socks5.txt
|
||||||
venv/
|
venv/
|
||||||
plugins/__pycache__/
|
plugins/__pycache__/
|
||||||
simplematrixbotlib/
|
simplematrixbotlib*/
|
||||||
|
chromedriver
|
||||||
|
store
|
||||||
|
funguybot.service
|
||||||
|
stats.db
|
66
README.md
66
README.md
@@ -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.
|
- Extensible: Users can add new commands by creating additional plugin modules.
|
||||||
|
|
||||||
## Automatic Installation
|
## Automatic Installation
|
||||||
1. `./install.sh` or `bash install.sh`
|
Run the installation script
|
||||||
|
1. `./install-funguy.sh`
|
||||||
|
|
||||||
2. Set up environment variables:
|
2. Launch the bot:
|
||||||
Put your bot homeserver/user/pass in `.env` file
|
`sudo systemctl start funguybot`
|
||||||
|
|
||||||
3. Launch the bot:
|
|
||||||
`python funguy.py`, or `chmod +x funguy.py`, then `./funguy.py`
|
|
||||||
|
|
||||||
## Manual Installation
|
## 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`
|
`git clone https://git.stoned.io/hash/FunguyBot`
|
||||||
|
|
||||||
2. Install dependencies:
|
3. Apply the patch
|
||||||
`pip install -r requirements.txt`
|
`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:
|
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)
|
MATRIX_URL="https://matrix.org" (or another homeserver)
|
||||||
@@ -37,9 +44,37 @@ MATRIX_USER=""
|
|||||||
MATRIX_PASS=""
|
MATRIX_PASS=""
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Run the bot:
|
4. Create systemd.service
|
||||||
`python funguy.py`, or `chmod +x funguy.py`, then `./funguy.py`
|
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
|
## 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.
|
- `!proxy`: Retrieve and test random SOCKS5 and HTTP proxies.
|
||||||
- `!isup <domain/ip>`: Check if the specified domain or IP address is reachable.
|
- `!isup <domain/ip>`: Check if the specified domain or IP address is reachable.
|
||||||
- `!karma <user>`: View or modify karma points for a user.
|
- `!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.
|
For a complete list of available commands and their descriptions, use the `!commands` command.
|
||||||
|
|
||||||
# 🍄 Funguy Bot Commands 🍄
|
# 🍄 Funguy Bot Commands 🍄
|
||||||
@@ -87,6 +124,9 @@ Decreases the karma points for the specified user by 1 in the database and sends
|
|||||||
An AI large language model designed to serve as a chatbot within a vibrant and diverse community chat room hosted on the Matrix platform.
|
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**
|
(Currently loaded model: **TheBloke_Mistral-7B-Instruct-v0.2-GPTQ**
|
||||||
|
|
||||||
|
🎝 **!music [prompt]**
|
||||||
|
Your music expert! Try it out.
|
||||||
|
|
||||||
# 🌟 Funguy Bot Credits 🌟
|
# 🌟 Funguy Bot Credits 🌟
|
||||||
|
|
||||||
🧙♂️ Creator & Developer:
|
🧙♂️ Creator & Developer:
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
From 4aa5b913f75e32f6ed06fc6e691daa856824b26a Mon Sep 17 00:00:00 2001
|
From 7b3421cf893ef8ea36978ae1343f7c8d5d353412 Mon Sep 17 00:00:00 2001
|
||||||
From: Hash Borgir <atirjavid@gmail.com>
|
From: Hash Borgir <hash@stoned.io>
|
||||||
Date: Mon, 12 Feb 2024 19:12:05 -0700
|
Date: Tue, 13 Feb 2024 15:48:35 -0700
|
||||||
Subject: [PATCH] Fixed stability issue in api.py
|
Subject: [PATCH] api.py patch
|
||||||
|
|
||||||
---
|
---
|
||||||
simplematrixbotlib/api.py | 2 ++
|
simplematrixbotlib/api.py | 2 ++
|
||||||
1 file changed, 2 insertions(+)
|
1 file changed, 2 insertions(+)
|
||||||
|
|
||||||
diff --git a/simplematrixbotlib/api.py b/simplematrixbotlib/api.py
|
diff --git a/simplematrixbotlib/api.py b/simplematrixbotlib/api.py
|
||||||
index 1d87b3d..13a78cf 100644
|
index 6d51b38..3af7e7e 100644
|
||||||
--- a/simplematrixbotlib/api.py
|
--- a/simplematrixbotlib/api.py
|
||||||
+++ b/simplematrixbotlib/api.py
|
+++ b/simplematrixbotlib/api.py
|
||||||
@@ -295,6 +295,7 @@ class Api:
|
@@ -347,6 +347,7 @@ class Api:
|
||||||
pass # Successful upload
|
pass # Successful upload
|
||||||
else:
|
else:
|
||||||
print(f"Failed Upload Response: {resp}")
|
print(f"Failed Upload Response: {resp}")
|
||||||
@@ -19,7 +19,7 @@ index 1d87b3d..13a78cf 100644
|
|||||||
|
|
||||||
content = {
|
content = {
|
||||||
"body": os.path.basename(image_filepath),
|
"body": os.path.basename(image_filepath),
|
||||||
@@ -342,6 +343,7 @@ class Api:
|
@@ -394,6 +395,7 @@ class Api:
|
||||||
pass # Successful upload
|
pass # Successful upload
|
||||||
else:
|
else:
|
||||||
print(f"Failed Upload Response: {resp}")
|
print(f"Failed Upload Response: {resp}")
|
16
funguy.conf
Normal file
16
funguy.conf
Normal 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" = []
|
282
funguy.py
282
funguy.py
@@ -1,69 +1,235 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# bot.py
|
|
||||||
|
|
||||||
import os
|
"""
|
||||||
import logging
|
Funguy Bot Class
|
||||||
import importlib
|
"""
|
||||||
import simplematrixbotlib as botlib
|
|
||||||
from dotenv import load_dotenv
|
|
||||||
import time
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# 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()
|
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_URL = os.getenv("MATRIX_URL")
|
||||||
MATRIX_USER = os.getenv("MATRIX_USER")
|
MATRIX_USER = os.getenv("MATRIX_USER")
|
||||||
MATRIX_PASS = os.getenv("MATRIX_PASS")
|
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)
|
if __name__ == "__main__":
|
||||||
bot = botlib.Bot(creds)
|
bot = FunguyBot() # Creating instance of FunguyBot
|
||||||
|
bot.run() # Running the bot
|
||||||
# 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 = {}
|
|
||||||
# Unload modules from sys.modules
|
|
||||||
for plugin_name in list(sys.modules.keys()):
|
|
||||||
if plugin_name.startswith(PLUGINS_DIR + "."):
|
|
||||||
del sys.modules[plugin_name]
|
|
||||||
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()
|
|
||||||
|
4
g
Executable file
4
g
Executable 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
145
install-funguy.sh
Executable 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"
|
51
install.sh
51
install.sh
@@ -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
1194
plugins/ai.json
Normal file
File diff suppressed because it is too large
Load Diff
115
plugins/ai.py
Normal file
115
plugins/ai.py
Normal 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()
|
@@ -1,67 +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 [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**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# 🌟 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
154
plugins/config.py
Normal 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")
|
@@ -1,10 +1,14 @@
|
|||||||
|
"""
|
||||||
|
This plugin provides a command to get the current date
|
||||||
|
"""
|
||||||
|
|
||||||
# plugins/date.py
|
# plugins/date.py
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import simplematrixbotlib as botlib
|
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.
|
Function to handle the !date command.
|
||||||
|
|
||||||
@@ -15,14 +19,43 @@ async def handle_command(room, message, bot, PREFIX):
|
|||||||
Returns:
|
Returns:
|
||||||
None
|
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"):
|
if match.is_not_from_this_bot() and match.prefix() and match.command("date"):
|
||||||
logging.info("Fetching current date and time")
|
logging.info("Fetching current date and time")
|
||||||
current_datetime = datetime.datetime.now()
|
current_datetime = datetime.datetime.now()
|
||||||
|
|
||||||
|
# Extract individual date components
|
||||||
day_of_week = current_datetime.strftime("%A")
|
day_of_week = current_datetime.strftime("%A")
|
||||||
|
date_of_month = current_datetime.strftime("%d") # Day with leading zero
|
||||||
month = current_datetime.strftime("%B")
|
month = current_datetime.strftime("%B")
|
||||||
year = current_datetime.strftime("%Y")
|
year = current_datetime.strftime("%Y")
|
||||||
time = current_datetime.strftime("%I:%M:%S %p")
|
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)
|
await bot.api.send_markdown_message(room.room_id, date_message)
|
||||||
logging.info("Sent current date and time to the room")
|
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"
|
||||||
|
@@ -1,10 +1,13 @@
|
|||||||
|
"""
|
||||||
|
This plugin provides a command to get a random fortune message.
|
||||||
|
"""
|
||||||
# plugins/fortune.py
|
# plugins/fortune.py
|
||||||
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import logging
|
import logging
|
||||||
import simplematrixbotlib as botlib
|
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.
|
Function to handle the !fortune command.
|
||||||
|
|
||||||
@@ -15,7 +18,7 @@ async def handle_command(room, message, bot, PREFIX):
|
|||||||
Returns:
|
Returns:
|
||||||
None
|
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"):
|
if match.is_not_from_this_bot() and match.prefix() and match.command("fortune"):
|
||||||
logging.info("Received !fortune command")
|
logging.info("Received !fortune command")
|
||||||
fortune_output = "🃏 " + subprocess.run(['/usr/games/fortune'], capture_output=True).stdout.decode('UTF-8')
|
fortune_output = "🃏 " + subprocess.run(['/usr/games/fortune'], capture_output=True).stdout.decode('UTF-8')
|
||||||
|
83
plugins/help.py
Normal file
83
plugins/help.py
Normal 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")
|
@@ -1,3 +1,7 @@
|
|||||||
|
"""
|
||||||
|
This plugin provides a command to check if a website or server is up.
|
||||||
|
"""
|
||||||
|
|
||||||
# plugins/isup.py
|
# plugins/isup.py
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@@ -39,45 +43,60 @@ async def check_https(domain):
|
|||||||
except aiohttp.ClientError:
|
except aiohttp.ClientError:
|
||||||
return False
|
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.
|
Function to handle the !isup command.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
room (Room): The Matrix room where the command was invoked.
|
room (Room): The Matrix room where the command was invoked.
|
||||||
message (RoomMessage): The message object containing the command.
|
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:
|
Returns:
|
||||||
None
|
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"):
|
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")
|
logging.info("Received !isup command")
|
||||||
args = match.args()
|
args = match.args()
|
||||||
|
# Check if the command has exactly one argument
|
||||||
if len(args) != 1:
|
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>")
|
await bot.api.send_markdown_message(room.room_id, "Usage: !isup <ipv4/ipv6/domain>")
|
||||||
logging.info("Sent usage message to the room")
|
logging.info("Sent usage message to the room")
|
||||||
return
|
return
|
||||||
|
|
||||||
target = args[0]
|
target = args[0]
|
||||||
|
|
||||||
# DNS resolution
|
# Perform DNS resolution
|
||||||
try:
|
try:
|
||||||
ip_address = socket.gethostbyname(target)
|
ip_address = socket.gethostbyname(target)
|
||||||
|
# Log successful DNS resolution
|
||||||
logging.info(f"DNS resolution successful for {target}: {ip_address}")
|
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)")
|
await bot.api.send_markdown_message(room.room_id, f"✅ DNS resolution successful for **{target}**: **{ip_address}** (A record)")
|
||||||
except socket.gaierror:
|
except socket.gaierror:
|
||||||
|
# Log DNS resolution failure
|
||||||
logging.info(f"DNS resolution failed for {target}")
|
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}**")
|
await bot.api.send_markdown_message(room.room_id, f"❌ DNS resolution failed for **{target}**")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Check HTTP/HTTPS services
|
# Check HTTP/HTTPS services
|
||||||
if await check_http(target):
|
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")
|
await bot.api.send_markdown_message(room.room_id, f"🖧 **{target}** HTTP service is up")
|
||||||
logging.info(f"{target} HTTP service is up")
|
logging.info(f"{target} HTTP service is up")
|
||||||
elif await check_https(target):
|
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")
|
await bot.api.send_markdown_message(room.room_id, f"🖧 **{target}** HTTPS service is up")
|
||||||
logging.info(f"{target} HTTPS service is up")
|
logging.info(f"{target} HTTPS service is up")
|
||||||
else:
|
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")
|
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")
|
logging.info(f"{target} HTTP/HTTPS services are down")
|
||||||
|
|
||||||
|
@@ -1,10 +1,15 @@
|
|||||||
|
"""
|
||||||
|
This plugin provides a command to manage karma points for users.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
# plugins/karma.py
|
# plugins/karma.py
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import logging
|
import logging
|
||||||
import simplematrixbotlib as botlib
|
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.
|
Function to handle the !karma command.
|
||||||
|
|
||||||
@@ -15,7 +20,7 @@ async def handle_command(room, message, bot, PREFIX):
|
|||||||
Returns:
|
Returns:
|
||||||
None
|
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"):
|
if match.is_not_from_this_bot() and match.prefix() and match.command("karma"):
|
||||||
logging.info("Received !karma command")
|
logging.info("Received !karma command")
|
||||||
args = match.args()
|
args = match.args()
|
||||||
|
115
plugins/loadplugin.py
Normal file
115
plugins/loadplugin.py
Normal 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
58
plugins/plugins.py
Normal 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
|
147
plugins/proxy.py
147
plugins/proxy.py
@@ -1,3 +1,7 @@
|
|||||||
|
"""
|
||||||
|
This plugin provides a command to get random SOCKS5 proxies.
|
||||||
|
"""
|
||||||
|
|
||||||
# plugins/proxy.py
|
# plugins/proxy.py
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@@ -5,44 +9,50 @@ import logging
|
|||||||
import random
|
import random
|
||||||
import requests
|
import requests
|
||||||
import socket
|
import socket
|
||||||
import ssl
|
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
import concurrent.futures
|
||||||
import simplematrixbotlib as botlib
|
import simplematrixbotlib as botlib
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
# Constants
|
# Constants
|
||||||
SOCKS5_LIST_URL = 'https://raw.githubusercontent.com/TheSpeedX/SOCKS-List/master/socks5.txt'
|
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_FILENAME = 'socks5.txt'
|
||||||
PROXY_LIST_EXPIRATION = timedelta(hours=8)
|
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:
|
Args:
|
||||||
proxy (str): The SOCKS5 proxy address in the format 'ip:port'.
|
proxy (str): The SOCKS5 proxy address in the format 'ip:port'.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if the proxy is working, False otherwise.
|
tuple: (bool: success, str: proxy, int: latency)
|
||||||
float: The latency in milliseconds if the proxy is working, None otherwise.
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
ip, port = proxy.split(':')
|
ip, port = proxy.split(':')
|
||||||
|
logging.info(f"Testing SOCKS5 proxy: {ip}:{port}")
|
||||||
start_time = time.time()
|
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')
|
client.sendall(b'\x05\x01\x00')
|
||||||
response = client.recv(2)
|
response = client.recv(2)
|
||||||
if response == b'\x05\x00':
|
if response == b'\x05\x00':
|
||||||
latency = int(round((time.time() - start_time) * 1000, 0))
|
latency = int(round((time.time() - start_time) * 1000, 0))
|
||||||
logging.info(f"Successful connection to SOCKS5 proxy {proxy}. Latency: {latency} ms")
|
return True, proxy, latency
|
||||||
return True, latency
|
|
||||||
else:
|
else:
|
||||||
logging.info(f"Failed to connect to SOCKS5 proxy {proxy}: Connection refused")
|
return False, proxy, None
|
||||||
return False, None
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error testing SOCKS5 proxy {proxy}: {e}")
|
return False, proxy, None
|
||||||
return False, None
|
|
||||||
|
|
||||||
async def download_proxy_list():
|
async def download_proxy_list():
|
||||||
"""
|
"""
|
||||||
@@ -68,7 +78,70 @@ async def download_proxy_list():
|
|||||||
return False
|
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.
|
Function to handle the !proxy command.
|
||||||
|
|
||||||
@@ -79,11 +152,20 @@ async def handle_command(room, message, bot, PREFIX):
|
|||||||
Returns:
|
Returns:
|
||||||
None
|
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"):
|
if match.is_not_from_this_bot() and match.prefix() and match.command("proxy"):
|
||||||
logging.info("Received !proxy command")
|
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
|
# Download proxy list if needed
|
||||||
|
else:
|
||||||
if not await download_proxy_list():
|
if not await download_proxy_list():
|
||||||
await bot.api.send_markdown_message(room.room_id, "Error downloading proxy list")
|
await bot.api.send_markdown_message(room.room_id, "Error downloading proxy list")
|
||||||
logging.error("Error downloading proxy list")
|
logging.error("Error downloading proxy list")
|
||||||
@@ -92,25 +174,40 @@ async def handle_command(room, message, bot, PREFIX):
|
|||||||
try:
|
try:
|
||||||
# Read proxies from file
|
# Read proxies from file
|
||||||
with open(PROXY_LIST_FILENAME, 'r') as f:
|
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)
|
random.shuffle(socks5_proxies)
|
||||||
|
|
||||||
# Test proxies
|
# Test proxies concurrently
|
||||||
socks5_proxy = None
|
tested_proxies = 0
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
|
||||||
|
futures = []
|
||||||
for proxy in socks5_proxies[:MAX_TRIES]:
|
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:
|
if success:
|
||||||
socks5_proxy = proxy
|
await bot.api.send_markdown_message(room.room_id,
|
||||||
break
|
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:
|
else:
|
||||||
|
# If no working proxy found after testing
|
||||||
await bot.api.send_markdown_message(room.room_id, "❌ No working anonymous SOCKS5 proxy found")
|
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("No working anonymous SOCKS5 proxy found")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error handling !proxy command: {e}")
|
logging.error(f"Error handling !proxy command: {e}")
|
||||||
await bot.api.send_markdown_message(room.room_id, "❌ Error handling !proxy command")
|
await bot.api.send_markdown_message(room.room_id, "❌ Error handling !proxy command")
|
||||||
|
|
||||||
|
|
||||||
|
178
plugins/stable-diffusion.py
Normal file
178
plugins/stable-diffusion.py
Normal 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><psychedelicai-SDXL></li>
|
||||||
|
<li><ral-frctlgmtry-sdxl></li>
|
||||||
|
<li><SDXL-PsyAI-v4></li>
|
||||||
|
<li><al3xxl></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p>Load LORAs like this:</p>
|
||||||
|
<ul>
|
||||||
|
<li><lora:SDXL-PsyAI-v4:1> PsyAI</li>
|
||||||
|
<li><lora:psychedelicai-SDXL:1> Psychedelic</li>
|
||||||
|
<li><ral-frctlgmtry-sdxl> ral-frctlgmtry</li>
|
||||||
|
<li><lora:al3xxl:1> 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
124
plugins/youtube-preview.py
Normal 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
73
plugins/youtube-search.py
Normal 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)
|
@@ -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.")
|
|
@@ -1,35 +1,12 @@
|
|||||||
aiofiles==23.2.1
|
python-dotenv
|
||||||
aiohttp==3.9.3
|
requests
|
||||||
aiohttp-socks==0.7.1
|
pytubefix
|
||||||
aiosignal==1.3.1
|
duckduckgo_search
|
||||||
async-timeout==4.0.3
|
nio
|
||||||
attrs==23.2.0
|
markdown2
|
||||||
certifi==2024.2.2
|
watchdog
|
||||||
cffi==1.16.0
|
emoji
|
||||||
charset-normalizer==3.3.2
|
python-slugify
|
||||||
cryptography==42.0.2
|
youtube_title_parse
|
||||||
frozenlist==1.4.1
|
dnspython
|
||||||
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
|
|
||||||
|
17
start-funguy.sh
Executable file
17
start-funguy.sh
Executable 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; }
|
||||||
|
|
Reference in New Issue
Block a user