Compare commits

...

10 Commits

Author SHA1 Message Date
25f9159155 sd update 2024-03-01 09:21:29 -07:00
803acf514b switched to using pytubefix 2024-03-01 09:20:44 -07:00
542bb5d5cd Various plugin updates. New plugin for stable diffusion 2024-02-29 20:18:58 -07:00
5962eb53ad AI plugins updated, now html formatted 2024-02-17 18:37:09 -07:00
eb81f7aa67 Help plugin reformat HTML 2024-02-17 17:48:46 -07:00
c996168543 Funguy class refactor 2024-02-17 16:19:23 -07:00
784409cef6 date module update 2024-02-17 07:04:35 -07:00
e9a853c31a Rehash function should work now 2024-02-16 20:13:13 -07:00
669fd361d4 update ignores 2024-02-14 12:32:30 -07:00
58fa16663e Updated README.md 2024-02-14 05:16:45 -07:00
15 changed files with 261 additions and 142 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,4 @@
.env
.gitignore
karma.db
proxies.db
session.txt
@@ -10,3 +9,4 @@ simplematrixbotlib*/
chromedriver
store
funguybot.service
stats.db

View File

@@ -87,7 +87,7 @@ To use the bot, invite it to a Matrix room and interact with it by sending comma
- `!karma <user>`: View or modify karma points for a user.
- `!funguy <prompt>` Talk to the Tech AI LLM
- `!music <prompt>` Talk to the music knowledge LLM
- `!yt <search terms>` Search Youtube
For a complete list of available commands and their descriptions, use the `!commands` command.
# 🍄 Funguy Bot Commands 🍄

128
funguy.py
View File

@@ -10,78 +10,86 @@ import time
import sys
from plugins.config import FunguyConfig
# Setup logging
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
class FunguyBot:
# Load plugins (defined before plugin load/reload functions)
PLUGINS_DIR = "plugins"
PLUGINS = {}
bot = None
config = None
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 __init__(self):
self.PLUGINS_DIR = "plugins"
self.PLUGINS = {}
self.config = None
self.bot = None
self.load_dotenv()
self.setup_logging()
self.load_plugins()
self.load_config()
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()
def load_dotenv(self):
load_dotenv()
def rehash_config(config):
del config
config = FunguyConfig()
def setup_logging(self):
logging.basicConfig(format='%(asctime)s - %(levelname)s - %(message)s', level=logging.INFO)
logging.getLogger().setLevel(logging.INFO)
def load_plugins(self):
for plugin_file in os.listdir(self.PLUGINS_DIR):
if plugin_file.endswith(".py"):
plugin_name = os.path.splitext(plugin_file)[0]
try:
module = importlib.import_module(f"{self.PLUGINS_DIR}.{plugin_name}")
self.PLUGINS[plugin_name] = module
logging.info(f"Loaded plugin: {plugin_name}")
except Exception as e:
logging.error(f"Error loading plugin {plugin_name}: {e}")
# Load environment variables from .env file
load_dotenv()
def reload_plugins(self):
self.PLUGINS = {}
# Unload modules from sys.modules
for plugin_name in list(sys.modules.keys()):
if plugin_name.startswith(self.PLUGINS_DIR + "."):
del sys.modules[plugin_name]
self.load_plugins()
# Load plugins
load_plugins()
def load_config(self):
self.config = FunguyConfig()
# Bot configuration settings
MATRIX_URL = os.getenv("MATRIX_URL")
MATRIX_USER = os.getenv("MATRIX_USER")
MATRIX_PASS = os.getenv("MATRIX_PASS")
async def handle_commands(self, room, message):
match = botlib.MessageMatch(room, message, self.bot, self.config.prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("reload"):
if str(message.sender) == self.config.admin_user:
self.reload_plugins()
await self.bot.api.send_text_message(room.room_id, "Plugins reloaded successfully")
else:
await self.bot.api.send_text_message(room.room_id, "You are not authorized to reload plugins.")
# Get credentials from env
creds = botlib.Creds(MATRIX_URL, MATRIX_USER, MATRIX_PASS)
for plugin_name, plugin_module in self.PLUGINS.items():
await plugin_module.handle_command(room, message, self.bot, self.config.prefix, self.config)
# Bot configuration
config = FunguyConfig()
bot = botlib.Bot(creds, config)
if match.is_not_from_this_bot() and match.prefix() and match.command("rehash"):
if str(message.sender) == self.config.admin_user:
self.rehash_config()
await self.bot.api.send_text_message(room.room_id, "Config rehashed")
else:
await self.bot.api.send_text_message(room.room_id, "You are not authorized to reload plugins.")
@bot.listener.on_message_event
async def handle_commands(room, message):
match = botlib.MessageMatch(room, message, bot, config.prefix)
if match.is_not_from_this_bot() and match.prefix() and match.command("reload"):
if str(message.sender) == config.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.")
def rehash_config(self):
del self.config
self.config = FunguyConfig()
for plugin_name, plugin_module in PLUGINS.items():
await plugin_module.handle_command(room, message, bot, config.prefix, config)
def run(self):
MATRIX_URL = os.getenv("MATRIX_URL")
MATRIX_USER = os.getenv("MATRIX_USER")
MATRIX_PASS = os.getenv("MATRIX_PASS")
creds = botlib.Creds(MATRIX_URL, MATRIX_USER, MATRIX_PASS)
self.bot = botlib.Bot(creds, self.config)
@self.bot.listener.on_message_event
async def wrapper_handle_commands(room, message):
await self.handle_commands(room, message)
if match.is_not_from_this_bot() and match.prefix() and match.command("rehash"):
if str(message.sender) == config.admin_user:
rehash_config(config)
await bot.api.send_text_message(room.room_id, "Config rehashed")
else:
await bot.api.send_text_message(room.room_id, "You are not authorized to reload plugins.")
self.bot.run()
bot.run()
if __name__ == "__main__":
bot = FunguyBot()
bot.run()

4
g Executable file
View File

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

View File

@@ -8,6 +8,7 @@ import requests
import json
import simplematrixbotlib as botlib
import re
import markdown2
async def handle_command(room, message, bot, prefix, config):
"""
@@ -56,9 +57,12 @@ async def handle_command(room, message, bot, prefix, config):
response.raise_for_status() # Raise HTTPError for bad responses
payload = response.json()
new_text = payload['choices'][0]['text']
if new_text.count('\n') > 1: # Check if new_text has more than one paragraph
new_text = new_text.replace("\n", '<br>')
new_text = re.sub(r"\*\*(.*?)\*\*", r"<strong>\1</strong>", new_text)
new_text = markdown_to_html(new_text)
print(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 = new_text.replace("\n", '<br>')
#new_text = re.sub(r"\*\*(.*?)\*\*", r"<strong>\1</strong>", new_text)
new_text = "<details><summary><strong>🎵Funguy Music GPT🎵<br>⤵Click Here To See Funguy's Response⤵</strong></summary>" + new_text + "</details>"
await bot.api.send_markdown_message(room.room_id, new_text)
else:
@@ -67,3 +71,8 @@ async def handle_command(room, message, bot, prefix, config):
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):
html_content = markdown2.markdown(markdown_text)
return html_content

View File

@@ -9,6 +9,7 @@ import requests
import json
import simplematrixbotlib as botlib
import re
import markdown2
async def handle_command(room, message, bot, prefix, config):
"""
@@ -57,9 +58,10 @@ async def handle_command(room, message, bot, prefix, config):
response.raise_for_status() # Raise HTTPError for bad responses
payload = response.json()
new_text = payload['choices'][0]['text']
if new_text.count('\n') > 2: # Check if new_text has more than one paragraph
new_text = new_text.replace("\n", '<br>')
new_text = re.sub(r"\*\*(.*?)\*\*", r"<strong>\1</strong>", new_text)
new_text = markdown_to_html(new_text)
if new_text.count('<p>') > 2: # Check if new_text has more than one paragraph
#new_text = new_text.replace("\n", '<br>')
#new_text = re.sub(r"\*\*(.*?)\*\*", r"<strong>\1</strong>", new_text)
new_text = "<details><summary><strong>🍄Funguy Tech GPT🍄<br>⤵Click Here To See Funguy's Response⤵</strong></summary>" + new_text + "</details>"
await bot.api.send_markdown_message(room.room_id, new_text)
else:
@@ -68,3 +70,7 @@ async def handle_command(room, message, bot, prefix, config):
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):
html_content = markdown2.markdown(markdown_text)
return html_content

View File

@@ -1,3 +1,6 @@
"""
Custom configuration class for the Funguy bot.
"""
# plugins/config.py
import os

View File

@@ -23,10 +23,39 @@ async def handle_command(room, message, bot, prefix, config):
if match.is_not_from_this_bot() and match.prefix() and match.command("date"):
logging.info("Fetching current date and time")
current_datetime = datetime.datetime.now()
# Extract individual date components
day_of_week = current_datetime.strftime("%A")
date_of_month = current_datetime.strftime("%d") # Day with leading zero
month = current_datetime.strftime("%B")
year = current_datetime.strftime("%Y")
time = current_datetime.strftime("%I:%M:%S %p")
date_message = f"📅 Today is **{day_of_week}** of **{month}** in **{year}**. The time is **⏰ {time}**"
# Format date with ordinal suffix
date_with_ordinal = f"{date_of_month}{get_ordinal_suffix(date_of_month)}"
# Construct the message
date_message = f"Date: **{day_of_week}** the {date_with_ordinal}, **{month} {year}**. \nTime: **⏰ {time}**"
await bot.api.send_markdown_message(room.room_id, date_message)
logging.info("Sent current date and time to the room")
def get_ordinal_suffix(day_of_month):
"""
Helper function to get the ordinal suffix for a day number.
Args:
day_of_month (str): The day number as a string (e.g., "1", "13")
Returns:
str: The ordinal suffix (e.g., "st", "th", "nd")
"""
if day_of_month.endswith("11") or day_of_month.endswith("12") or day_of_month.endswith("13"):
return "th"
elif day_of_month.endswith("1") or day_of_month.endswith("3"):
return "st"
elif day_of_month.endswith("2"):
return "nd"
else:
return "th"

View File

@@ -21,51 +21,52 @@ async def handle_command(room, message, bot, prefix, config):
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🍄<br>⤵Click Here To See Help Text⤵</strong></summary>
<br>
<br>🃏 **!fortune**
<br>Returns a random fortune message.
<br>Executes the `/usr/games/fortune` utility and sends the output as a message to the chat room.
<br>
<br>⏰ **!date**
<br>Displays the current date and time.
<br>Fetches the current date and time using Python's `datetime` module and sends it in a formatted message to the chat room.
<br>
<br>💻 **!proxy**
<br>Retrieves a tested/working random SOCKS5 proxy.
<br>Fetches a list of SOCKS5 proxies, tests their availability, and sends the first working proxy to the chat room.
<br>
<br>📶 **!isup [domain/ip]**
<br>Checks if the specified domain or IP address is reachable.
<br>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.
<br>
<br>☯ **!karma [user]**
<br>Retrieves the karma points for the specified user.
<br>Retrieves the karma points for the specified user from a database and sends them as a message to the chat room.
<br>
<br>⇧ **!karma [user] up**
<br>Increases the karma points for the specified user by 1.
<br>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.
<br>
<br>⇩ **!karma [user] down**
<br>Decreases the karma points for the specified user by 1.
<br>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.
<br>
<br>📄 **!funguy [prompt]
<br>An AI large language model designed to serve as a chatbot within a vibrant and diverse community chat room hosted on the Matrix platform.
<br>(Currently loaded model: **TheBloke_Mistral-7B-Instruct-v0.2-GPTQ**
<br>
<br>🎝 **!music [prompt]**
<br>Your music expert! Try it out.
<br> 🌟 Funguy Bot Credits 🌟
<br>
<br>🧙‍♂️ Creator & Developer:
<br> Hash Borgir is the author of 🍄Funguy Bot🍄. (@hashborgir:mozilla.org)
<br>
<br> Join our Matrix Room:
<br>
<br>[Self-hosting | Security | Sysadmin | Homelab | Programming](https://matrix.to/#/#selfhosting:mozilla.org)
<br>
<br></details>"""
commands_message = """
<details><summary><strong>🍄Funguy Bot Commands🍄<br>⤵Click Here To See Help Text⤵</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>!funguy [prompt]</strong></summary>
<p>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: <strong>TheBloke_Mistral-7B-Instruct-v0.2-GPTQ</strong>)</p>
</details>
<details><summary>🎝 <strong>!music [prompt]</strong></summary>
<p>Your music expert! Try it out.</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>
"""
await bot.api.send_markdown_message(room.room_id, commands_message)
logging.info("Sent help documentation to the room")

View File

@@ -1,3 +1,6 @@
"""
This plugin provides a command for the admin to load a plugin
"""
# plugins/load_plugin.py
import os

View File

@@ -26,9 +26,9 @@ async def handle_command(room, message, bot, prefix, config):
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><br>")
plugin_descriptions.insert(0, "<details><summary><strong>🔌Plugins List🔌<br>⤵Click Here to Expand⤵</strong></summary>")
plugins_message = "<br><br>".join(plugin_descriptions)
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")

View File

@@ -0,0 +1,42 @@
"""
This plugin provides a command to generate images using self hosted Stable Diffusion and send to the room
"""
# plugins/stable-diffusion.py
import requests
import base64
from asyncio import Queue
import simplematrixbotlib as botlib
# Queue to store pending commands
command_queue = Queue()
async def process_command(room, message, bot, prefix, config):
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):
match = botlib.MessageMatch(room, message, bot, prefix)
if match.prefix() and match.command("sd"):
prompt = message.body[len(prefix) + len("sd"):].strip() # Extract prompt from message body
payload = {
"prompt": prompt,
"steps": 16
}
url = "http://127.0.0.1:7860/sdapi/v1/txt2img"
try:
response = requests.post(url=url, json=payload)
r = response.json()
with open("/tmp/output.png", 'wb') as f:
f.write(base64.b64decode(r['images'][0]))
await bot.api.send_image_message(room_id=room.room_id, image_filepath="/tmp/output.png") # Corrected argument name
except Exception as e:
await bot.api.send_text_message(room.room_id, f"Error processing the command: {str(e)}")
finally:
if not command_queue.empty():
next_command = await command_queue.get()
await handle_command(*next_command)

View File

@@ -6,25 +6,31 @@ This plugin provides a command to fetch YouTube video information from links.
import re
import logging
from pytube import YouTube
from pytubefix import YouTube
import simplematrixbotlib as botlib
import asyncio
def seconds_to_minutes_seconds(seconds):
minutes = seconds // 60
seconds %= 60
return f"{minutes:02d}:{seconds:02d}"
async def fetch_youtube_info(youtube_url):
try:
video = YouTube(youtube_url)
title = video.title
description = video.description
length = seconds_to_minutes_seconds(video.length)
views = video.views
author = video.author
description_with_breaks = description.replace('\n', '<br>')
info_message = f"""<strong>🎬🎝 Title:</strong> {title} | <strong>Length</strong>: {length} minutes | <strong>Views</strong>: {views}\n<details><summary><strong>⤵Click Here For Description⤵</strong></summary>{description_with_breaks}</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):
"""
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, prefix)
if match.is_not_from_this_bot() and re.search(r'youtube\.com/watch\?v=', message.body):
logging.info("YouTube link detected")
@@ -33,16 +39,18 @@ async def handle_command(room, message, bot, prefix, config):
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 = seconds_to_minutes_seconds(video.length)
views = video.views
author = video.author
info_message = f"""**🎬🎝 Title:** {title} | **Length**: {length} minutes| **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.")
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")

View File

@@ -1,3 +1,6 @@
"""
This plugin provides a command to search for YouTube videos in the room
"""
# plugins/youtube_search.py
import logging

View File

@@ -1,5 +1,8 @@
python-dotenv
requests
pytube
pytubefix
duckduckgo_search
nio
markdown2
watchdog
emoji