Stable diffusion help text fixed. Whois plugin added
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
Plugin for generating text using Ollama's Mistral 7B Instruct model and sending it to a Matrix chat room.
|
||||
"""
|
||||
|
||||
import requests
|
||||
from asyncio import Queue
|
||||
import simplematrixbotlib as botlib
|
||||
import argparse
|
||||
|
||||
# Queue to store pending commands
|
||||
command_queue = Queue()
|
||||
|
||||
API_URL = "http://localhost:11434/api/generate"
|
||||
MODEL_NAME = "mistral:7b-instruct"
|
||||
|
||||
async def process_command(room, message, bot, prefix, config):
|
||||
"""
|
||||
Queue and process !text commands sequentially.
|
||||
"""
|
||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||
if match.prefix() and match.command("text"):
|
||||
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):
|
||||
"""
|
||||
Send the prompt to Ollama API and return the generated text.
|
||||
"""
|
||||
match = botlib.MessageMatch(room, message, bot, prefix)
|
||||
if not (match.prefix() and match.command("text")):
|
||||
return
|
||||
|
||||
# Parse optional arguments
|
||||
parser = argparse.ArgumentParser(description='Generate text using Ollama API')
|
||||
parser.add_argument('--max_tokens', type=int, default=512, help='Maximum tokens to generate')
|
||||
parser.add_argument('--temperature', type=float, default=0.7, help='Temperature for generation')
|
||||
parser.add_argument('prompt', nargs='+', help='Prompt for the model')
|
||||
|
||||
try:
|
||||
args = parser.parse_args(message.body.split()[1:]) # Skip command itself
|
||||
prompt = ' '.join(args.prompt).strip()
|
||||
|
||||
if not prompt:
|
||||
await bot.api.send_text_message(room.room_id, "Usage: !text <your prompt here>")
|
||||
return
|
||||
|
||||
payload = {
|
||||
"model": MODEL_NAME,
|
||||
"prompt": prompt,
|
||||
"max_tokens": args.max_tokens,
|
||||
"temperature": args.temperature,
|
||||
"stream": False
|
||||
}
|
||||
|
||||
response = requests.post(API_URL, json=payload, timeout=60)
|
||||
response.raise_for_status()
|
||||
r = response.json()
|
||||
|
||||
generated_text = r.get("response", "").strip()
|
||||
if not generated_text:
|
||||
generated_text = "(No response from model)"
|
||||
|
||||
await bot.api.send_text_message(room.room_id, generated_text)
|
||||
|
||||
except argparse.ArgumentError as e:
|
||||
await bot.api.send_text_message(room.room_id, f"Argument error: {e}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
await bot.api.send_text_message(room.room_id, f"Error connecting to Ollama API: {e}")
|
||||
except Exception as e:
|
||||
await bot.api.send_text_message(room.room_id, f"Unexpected error: {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 !text command.
|
||||
"""
|
||||
return """
|
||||
<p>Generate text using Ollama's Mistral 7B Instruct model</p>
|
||||
|
||||
<p>Usage:</p>
|
||||
<ul>
|
||||
<li>!text <prompt> - Basic prompt for the model</li>
|
||||
<li>Optional arguments:</li>
|
||||
<ul>
|
||||
<li>--max_tokens MAX_TOKENS - Maximum tokens to generate (default 512)</li>
|
||||
<li>--temperature TEMPERATURE - Sampling temperature (default 0.7)</li>
|
||||
</ul>
|
||||
</ul>
|
||||
"""
|
||||
@@ -0,0 +1,187 @@
|
||||
"""
|
||||
Plugin for providing a command to fetch YouTube video information from links.
|
||||
"""
|
||||
|
||||
# Importing necessary libraries
|
||||
import re
|
||||
import logging
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import yt_dlp
|
||||
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:
|
||||
url = LYRICIST_API_URL.format(artist, song)
|
||||
logging.info(f"Fetching lyrics from: {url}")
|
||||
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as response:
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
return data.get("lyrics")
|
||||
else:
|
||||
logging.warning(f"Lyrics API returned status {response.status}")
|
||||
return None
|
||||
except asyncio.TimeoutError:
|
||||
logging.error("Timeout fetching lyrics")
|
||||
return None
|
||||
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 using yt-dlp.
|
||||
|
||||
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:
|
||||
logging.info(f"Fetching YouTube info for: {youtube_url}")
|
||||
|
||||
# Configure yt-dlp options
|
||||
ydl_opts = {
|
||||
'quiet': True,
|
||||
'no_warnings': True,
|
||||
'extract_flat': False,
|
||||
'skip_download': True,
|
||||
}
|
||||
|
||||
# Run yt-dlp in thread pool to avoid blocking
|
||||
loop = asyncio.get_event_loop()
|
||||
|
||||
def extract_info():
|
||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||
return ydl.extract_info(youtube_url, download=False)
|
||||
|
||||
info = await loop.run_in_executor(None, extract_info)
|
||||
|
||||
if not info:
|
||||
logging.error("No info returned from yt-dlp")
|
||||
return None
|
||||
|
||||
# Extract video information
|
||||
title = info.get('title', 'Unknown Title')
|
||||
description = info.get('description', 'No description available')
|
||||
duration = info.get('duration', 0)
|
||||
view_count = info.get('view_count', 0)
|
||||
uploader = info.get('uploader', 'Unknown')
|
||||
|
||||
logging.info(f"Video title: {title}")
|
||||
|
||||
length = seconds_to_minutes_seconds(duration)
|
||||
|
||||
# Parse artist and song from title
|
||||
artist, song = get_artist_title(title)
|
||||
logging.info(f"Parsed artist: {artist}, song: {song}")
|
||||
|
||||
# Limit description length to avoid huge messages
|
||||
if len(description) > 500:
|
||||
description = description[:500] + "..."
|
||||
|
||||
description_with_breaks = description.replace('\n', '<br>')
|
||||
|
||||
# Build basic info message
|
||||
info_message = f"""<strong>🎬🎝 Title:</strong> {title}<br><strong>Length:</strong> {length} | <strong>Views:</strong> {view_count:,} | <strong>Uploader:</strong> {uploader}<br><details><summary><strong>⤵︎Description⤵︎</strong></summary>{description_with_breaks}</details>"""
|
||||
|
||||
# Try to fetch lyrics if artist and song were parsed
|
||||
if artist and song:
|
||||
logging.info("Attempting to fetch lyrics...")
|
||||
lyrics = await fetch_lyrics(song, artist)
|
||||
if lyrics:
|
||||
lyrics = lyrics.replace('\n', "<br>")
|
||||
# Limit lyrics length
|
||||
if len(lyrics) > 3000:
|
||||
lyrics = lyrics[:3000] + "<br>...(truncated)"
|
||||
info_message += f"<br><details><summary><strong>🎵 Lyrics:</strong></summary><br>{lyrics}</details>"
|
||||
else:
|
||||
logging.info("No lyrics found")
|
||||
else:
|
||||
logging.info("Could not parse artist/song from title, skipping lyrics")
|
||||
|
||||
return info_message
|
||||
except Exception as e:
|
||||
logging.error(f"Error fetching YouTube video information: {str(e)}", exc_info=True)
|
||||
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)
|
||||
|
||||
# Check if message contains a YouTube link
|
||||
if match.is_not_from_this_bot() and re.search(r'(youtube\.com/watch\?v=|youtu\.be/)', message.body):
|
||||
logging.info(f"YouTube link detected in message: {message.body}")
|
||||
|
||||
# Match both youtube.com and youtu.be formats
|
||||
video_id_match = re.search(r'(?:youtube\.com/watch\?v=|youtu\.be/)([a-zA-Z0-9_-]{11})', 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 ID: {video_id}")
|
||||
|
||||
retry_count = 2 # Reduced retries since yt-dlp is more reliable
|
||||
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.warning(f"Failed to fetch info, retrying... ({retry_count-1} attempts left)")
|
||||
retry_count -= 1
|
||||
if retry_count > 0:
|
||||
await asyncio.sleep(2) # wait for 2 seconds before retrying
|
||||
else:
|
||||
logging.error("Failed to fetch YouTube video information after all retries")
|
||||
await bot.api.send_text_message(room.room_id, "Failed to fetch YouTube video information. The video may be unavailable or age-restricted.")
|
||||
else:
|
||||
logging.warning("Could not extract video ID from YouTube URL")
|
||||
@@ -159,16 +159,14 @@ def print_help():
|
||||
<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>--steps STEPS - Number of steps, default=4</li>
|
||||
<li>--cfg CFG - CFG scale, default=2</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>
|
||||
<li>--neg NEG - Negative prompt, 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)))</li>
|
||||
<li>--sampler SAMPLER - Sampler name, default=DPM++ SDE</li>
|
||||
</ul>
|
||||
|
||||
<p>LORA List:</p>
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
"""
|
||||
This plugin provides WHOIS lookup functionality for domains, IPs, and related network information.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import whois
|
||||
import ipaddress
|
||||
import re
|
||||
import simplematrixbotlib as botlib
|
||||
|
||||
|
||||
def is_valid_domain(domain):
|
||||
"""
|
||||
Validate if the provided string is a valid domain name.
|
||||
|
||||
Args:
|
||||
domain (str): The domain to validate.
|
||||
|
||||
Returns:
|
||||
bool: True if valid, False otherwise.
|
||||
"""
|
||||
pattern = r'^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$|^[a-zA-Z0-9-]{1,63}$'
|
||||
return re.match(pattern, domain) is not None
|
||||
|
||||
|
||||
def is_valid_ip(ip):
|
||||
"""
|
||||
Validate if the provided string is a valid IPv4 or IPv6 address.
|
||||
|
||||
Args:
|
||||
ip (str): The IP address to validate.
|
||||
|
||||
Returns:
|
||||
bool: True if valid, False otherwise.
|
||||
"""
|
||||
try:
|
||||
ipaddress.ip_address(ip)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def format_whois_data(domain, data):
|
||||
"""
|
||||
Format WHOIS data into a readable format.
|
||||
|
||||
Args:
|
||||
domain (str): The queried domain/IP.
|
||||
data (whois domain object): The WHOIS data object.
|
||||
|
||||
Returns:
|
||||
str: Formatted HTML message.
|
||||
"""
|
||||
sections = []
|
||||
|
||||
# Domain/Query Information
|
||||
if hasattr(data, 'domain_name') or hasattr(data, 'query'):
|
||||
domain_names = getattr(data, 'domain_name', domain)
|
||||
if isinstance(domain_names, list):
|
||||
domain_names = ', '.join(domain_names)
|
||||
sections.append(f"<strong>🔍 Query:</strong> {domain_names}")
|
||||
|
||||
# Registrar Information
|
||||
registrar_items = []
|
||||
if hasattr(data, 'registrar'):
|
||||
registrar_items.append(f"<strong>Registrar:</strong> {data.registrar}")
|
||||
if hasattr(data, 'whois_server'):
|
||||
registrar_items.append(f"<strong>WHOIS Server:</strong> {data.whois_server}")
|
||||
if registrar_items:
|
||||
sections.append('<br>'.join(registrar_items))
|
||||
|
||||
# Dates
|
||||
date_items = []
|
||||
if hasattr(data, 'creation_date'):
|
||||
creation = data.creation_date
|
||||
if isinstance(creation, list):
|
||||
creation = creation[0]
|
||||
date_items.append(f"<strong>Created:</strong> {creation}")
|
||||
|
||||
if hasattr(data, 'updated_date'):
|
||||
updated = data.updated_date
|
||||
if isinstance(updated, list):
|
||||
updated = updated[0]
|
||||
date_items.append(f"<strong>Updated:</strong> {updated}")
|
||||
|
||||
if hasattr(data, 'expiration_date'):
|
||||
expiration = data.expiration_date
|
||||
if isinstance(expiration, list):
|
||||
expiration = expiration[0]
|
||||
date_items.append(f"<strong>Expires:</strong> {expiration}")
|
||||
|
||||
if date_items:
|
||||
sections.append('<br>'.join(date_items))
|
||||
|
||||
# Status
|
||||
if hasattr(data, 'status'):
|
||||
status = data.status
|
||||
if isinstance(status, list):
|
||||
status = '<br>'.join(status[:3]) # Limit to first 3 status entries
|
||||
sections.append(f"<strong>Status:</strong><br>{status}")
|
||||
|
||||
# Name Servers
|
||||
if hasattr(data, 'name_servers'):
|
||||
name_servers = data.name_servers
|
||||
if isinstance(name_servers, list):
|
||||
if len(name_servers) > 5:
|
||||
name_servers_list = '<br>'.join(sorted(name_servers)[:5])
|
||||
name_servers_list += f"<br><em>...(+{len(name_servers) - 5} more)</em>"
|
||||
else:
|
||||
name_servers_list = '<br>'.join(sorted(name_servers))
|
||||
else:
|
||||
name_servers_list = str(name_servers)
|
||||
sections.append(f"<strong>Name Servers:</strong><br>{name_servers_list}")
|
||||
|
||||
# Contact Information
|
||||
contact_items = []
|
||||
if hasattr(data, 'org'):
|
||||
contact_items.append(f"<strong>Organization:</strong> {data.org}")
|
||||
if hasattr(data, 'country'):
|
||||
contact_items.append(f"<strong>Country:</strong> {data.country}")
|
||||
if hasattr(data, 'state'):
|
||||
contact_items.append(f"<strong>State:</strong> {data.state}")
|
||||
if hasattr(data, 'city'):
|
||||
contact_items.append(f"<strong>City:</strong> {data.city}")
|
||||
|
||||
if contact_items:
|
||||
sections.append('<br>'.join(contact_items))
|
||||
|
||||
# Build the final message
|
||||
if sections:
|
||||
content = f"<strong>🌐 WHOIS Report: {domain}</strong><br><br>"
|
||||
content += '<br><br>'.join(sections)
|
||||
else:
|
||||
content = f"<strong>🌐 WHOIS Information for {domain}</strong><br><br>"
|
||||
content += "<em>No detailed information available or query returned minimal data.</em>"
|
||||
|
||||
# Wrap in collapsible details block for Matrix compatibility
|
||||
message = f"<details><summary><strong>🌐 WHOIS Report: {domain} (Click to expand)</strong></summary>{content}</details>"
|
||||
|
||||
return message
|
||||
|
||||
|
||||
async def handle_command(room, message, bot, prefix, config):
|
||||
"""
|
||||
Function to handle the !whois command.
|
||||
|
||||
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() and match.command("whois"):
|
||||
args = match.args()
|
||||
|
||||
if len(args) < 1:
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
"Usage: !whois <domain/ip>\nExample: !whois example.com\nExample: !whois 8.8.8.8"
|
||||
)
|
||||
return
|
||||
|
||||
query = args[0].strip()
|
||||
logging.info(f"Received !whois command for: {query}")
|
||||
|
||||
# Validate the query
|
||||
if not is_valid_domain(query) and not is_valid_ip(query):
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
f"Invalid domain or IP address format: {query}\nPlease provide a valid domain (e.g., example.com) or IP address."
|
||||
)
|
||||
logging.warning(f"Invalid WHOIS query format: {query}")
|
||||
return
|
||||
|
||||
try:
|
||||
# Perform WHOIS lookup
|
||||
logging.info(f"Performing WHOIS lookup for: {query}")
|
||||
await bot.api.send_text_message(room.room_id, f"🔍 Performing WHOIS lookup for {query}...")
|
||||
|
||||
# Use python-whois library
|
||||
whois_data = whois.whois(query)
|
||||
|
||||
# Format and send the results
|
||||
result_message = format_whois_data(query, whois_data)
|
||||
await bot.api.send_markdown_message(room.room_id, result_message)
|
||||
logging.info(f"Successfully sent WHOIS results for {query}")
|
||||
|
||||
except whois.parser.PywhoisError as e:
|
||||
error_msg = f"WHOIS lookup failed for {query}.\n"
|
||||
error_msg += "Possible reasons:\n- Domain/IP not found\n- WHOIS server unavailable\n- Rate limited by registrar"
|
||||
await bot.api.send_text_message(room.room_id, error_msg)
|
||||
logging.error(f"WHOIS lookup error for {query}: {e}")
|
||||
|
||||
except Exception as e:
|
||||
await bot.api.send_text_message(
|
||||
room.room_id,
|
||||
f"An unexpected error occurred during WHOIS lookup for {query}. Please try again later."
|
||||
)
|
||||
logging.error(f"Unexpected error in WHOIS plugin for {query}: {e}", exc_info=True)
|
||||
Reference in New Issue
Block a user