Compare commits
17 Commits
kbump
...
d65197e552
| Author | SHA1 | Date | |
|---|---|---|---|
| d65197e552 | |||
| 0909717fb6 | |||
| 82e48c2383 | |||
| 2d199d9247 | |||
| f546eaa633 | |||
| e552ba7552 | |||
| e0330148c2 | |||
| 3955e61a62 | |||
| a3e210a7ce | |||
| 5fd4e08d90 | |||
| 8a7621836f | |||
| eec57c7e23 | |||
| e0958f6f2a | |||
| 859ac84b68 | |||
| 5ed8002b40 | |||
| c94667ee52 | |||
| 83facc2fb9 |
0
kofi-shop/README.md
Normal file
0
kofi-shop/README.md
Normal file
1
kofi-shop/__init__.py
Normal file
1
kofi-shop/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .kofi_shop import setup
|
||||
16
kofi-shop/info.json
Normal file
16
kofi-shop/info.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"author": [ "unstableCogs" ],
|
||||
"install_msg": "Thank you for installing the Ko-fi Shop cog! Use `[p]help KofiShop` for commands.",
|
||||
"name": "KofiShop",
|
||||
"short": "An interactive front-end for a Ko-fi store.",
|
||||
"description": "Allows users to place orders and submit reviews for items available on the client's Ko-fi store. This cog acts as a bridge to an external shop.",
|
||||
"tags": [
|
||||
"kofi",
|
||||
"shop",
|
||||
"store",
|
||||
"review",
|
||||
"utility"
|
||||
],
|
||||
"requirements": [ "rich" ],
|
||||
"end_user_data_statement": "This cog persistently stores user IDs, order details, and submitted reviews to manage shop history and transactions."
|
||||
}
|
||||
175
kofi-shop/kofi_shop.py
Normal file
175
kofi-shop/kofi_shop.py
Normal file
@@ -0,0 +1,175 @@
|
||||
import discord
|
||||
from redbot.core import commands, Config
|
||||
from redbot.core.bot import Red
|
||||
from typing import Optional
|
||||
|
||||
# --- Modals for the Commands ---
|
||||
|
||||
class OrderModal(discord.ui.Modal, title="Commission/Shop Order"):
|
||||
def __init__(self, cog: "KofiShop"):
|
||||
super().__init__()
|
||||
self.cog = cog
|
||||
|
||||
comm_type = discord.ui.TextInput(label="What type of commission/item is this?")
|
||||
payment_status = discord.ui.TextInput(label="Is this free or paid?")
|
||||
description = discord.ui.TextInput(label="Please describe your request.", style=discord.TextStyle.paragraph)
|
||||
questions = discord.ui.TextInput(label="Any questions for the artist?", style=discord.TextStyle.paragraph, required=False)
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
if not interaction.guild:
|
||||
return await interaction.response.send_message("This must be used in a server.", ephemeral=True)
|
||||
|
||||
order_channel_id = await self.cog.config.guild(interaction.guild).order_channel()
|
||||
if not order_channel_id:
|
||||
return await interaction.response.send_message("The order channel has not been set by an admin.", ephemeral=True)
|
||||
|
||||
order_channel = interaction.guild.get_channel(order_channel_id)
|
||||
if not isinstance(order_channel, discord.TextChannel):
|
||||
return await interaction.response.send_message("The configured order channel is invalid.", ephemeral=True)
|
||||
|
||||
embed = discord.Embed(
|
||||
title="New Order Placed",
|
||||
description=f"Submitted by {interaction.user.mention}",
|
||||
color=0x00ff00 # Green for new orders
|
||||
)
|
||||
embed.add_field(name="Item/Commission Type", value=self.comm_type.value, inline=False)
|
||||
embed.add_field(name="Payment Status", value=self.payment_status.value, inline=False)
|
||||
embed.add_field(name="Description", value=self.description.value, inline=False)
|
||||
if self.questions.value:
|
||||
embed.add_field(name="Questions", value=self.questions.value, inline=False)
|
||||
|
||||
try:
|
||||
await order_channel.send(embed=embed)
|
||||
await interaction.response.send_message("Your order has been successfully submitted!", ephemeral=True)
|
||||
except discord.Forbidden:
|
||||
await interaction.response.send_message("I don't have permission to send messages in the order channel.", ephemeral=True)
|
||||
|
||||
class ReviewModal(discord.ui.Modal, title="Leave a Review"):
|
||||
def __init__(self, cog: "KofiShop"):
|
||||
super().__init__()
|
||||
self.cog = cog
|
||||
|
||||
item_name = discord.ui.TextInput(label="What item/commission are you reviewing?")
|
||||
rating = discord.ui.TextInput(label="Rating (e.g., 10/10)")
|
||||
review_text = discord.ui.TextInput(label="Your Review", style=discord.TextStyle.paragraph)
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
if not interaction.guild:
|
||||
return await interaction.response.send_message("This must be used in a server.", ephemeral=True)
|
||||
|
||||
review_channel_id = await self.cog.config.guild(interaction.guild).review_channel()
|
||||
if not review_channel_id:
|
||||
return await interaction.response.send_message("The review channel has not been set by an admin.", ephemeral=True)
|
||||
|
||||
review_channel = interaction.guild.get_channel(review_channel_id)
|
||||
if not isinstance(review_channel, discord.TextChannel):
|
||||
return await interaction.response.send_message("The configured review channel is invalid.", ephemeral=True)
|
||||
|
||||
embed = discord.Embed(
|
||||
title=f"New Review for: {self.item_name.value}",
|
||||
description=f"Submitted by {interaction.user.mention}",
|
||||
color=0xadd8e6 # Pastel Blue
|
||||
)
|
||||
embed.add_field(name="Rating", value=self.rating.value, inline=False)
|
||||
embed.add_field(name="Review", value=self.review_text.value, inline=False)
|
||||
|
||||
try:
|
||||
await review_channel.send(embed=embed)
|
||||
await interaction.response.send_message("Thank you! Your review has been submitted.", ephemeral=True)
|
||||
except discord.Forbidden:
|
||||
await interaction.response.send_message("I don't have permission to send messages in the review channel.", ephemeral=True)
|
||||
|
||||
|
||||
class KofiShop(commands.Cog):
|
||||
"""
|
||||
A cog to manage Ko-fi shop orders and reviews.
|
||||
"""
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, identifier=5566778899, force_registration=True)
|
||||
|
||||
default_guild = {
|
||||
"order_channel": None,
|
||||
"review_channel": None,
|
||||
"waitlist_channel": None
|
||||
}
|
||||
self.config.register_guild(**default_guild)
|
||||
|
||||
# --- Commands ---
|
||||
@commands.hybrid_command()
|
||||
@commands.guild_only()
|
||||
async def order(self, ctx: commands.Context):
|
||||
"""Place an order for a shop or commission item."""
|
||||
if not ctx.interaction:
|
||||
return
|
||||
# We pass `self` (the cog instance) to the modal
|
||||
await ctx.interaction.response.send_modal(OrderModal(self))
|
||||
|
||||
@commands.hybrid_command()
|
||||
@commands.guild_only()
|
||||
async def review(self, ctx: commands.Context):
|
||||
"""Leave a review for a completed shop or commission item."""
|
||||
if not ctx.interaction:
|
||||
return
|
||||
await ctx.interaction.response.send_modal(ReviewModal(self))
|
||||
|
||||
@commands.hybrid_command() # type: ignore
|
||||
@commands.guild_only()
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
async def waitlist(self, ctx: commands.Context, user: discord.Member, *, item: str):
|
||||
"""Add a user and their requested item to the waitlist."""
|
||||
if not ctx.guild or not isinstance(ctx.channel, discord.TextChannel):
|
||||
return await ctx.send("This command must be used in a server's text channel.", ephemeral=True)
|
||||
|
||||
waitlist_channel_id = await self.config.guild(ctx.guild).waitlist_channel()
|
||||
if not waitlist_channel_id:
|
||||
return await ctx.send("The waitlist channel has not been set by an admin.", ephemeral=True)
|
||||
|
||||
waitlist_channel = ctx.guild.get_channel(waitlist_channel_id)
|
||||
if not isinstance(waitlist_channel, discord.TextChannel):
|
||||
return await ctx.send("The configured waitlist channel is invalid.", ephemeral=True)
|
||||
|
||||
message = f"**{item}** ིྀ {user.mention} ✧ in {ctx.channel.mention}"
|
||||
|
||||
try:
|
||||
await waitlist_channel.send(message)
|
||||
await ctx.send(f"{user.mention} has been added to the waitlist for '{item}'.", ephemeral=True)
|
||||
except discord.Forbidden:
|
||||
await ctx.send(f"I don't have permission to send messages in the waitlist channel.", ephemeral=True)
|
||||
|
||||
# --- Settings Commands ---
|
||||
@commands.group(aliases=["kset"]) # type: ignore
|
||||
@commands.guild_only()
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
async def kofiset(self, ctx: commands.Context):
|
||||
"""
|
||||
Configure the KofiShop settings.
|
||||
"""
|
||||
pass
|
||||
|
||||
@kofiset.command(name="orderchannel")
|
||||
async def kofiset_order(self, ctx: commands.Context, channel: discord.TextChannel):
|
||||
"""Set the channel where new orders will be sent."""
|
||||
if not ctx.guild: return
|
||||
await self.config.guild(ctx.guild).order_channel.set(channel.id)
|
||||
await ctx.send(f"Order channel has been set to {channel.mention}.")
|
||||
|
||||
@kofiset.command(name="reviewchannel")
|
||||
async def kofiset_review(self, ctx: commands.Context, channel: discord.TextChannel):
|
||||
"""Set the channel where new reviews will be sent."""
|
||||
if not ctx.guild: return
|
||||
await self.config.guild(ctx.guild).review_channel.set(channel.id)
|
||||
await ctx.send(f"Review channel has been set to {channel.mention}.")
|
||||
|
||||
@kofiset.command(name="waitlistchannel")
|
||||
async def kofiset_waitlist(self, ctx: commands.Context, channel: discord.TextChannel):
|
||||
"""Set the channel where waitlist notifications will be sent."""
|
||||
if not ctx.guild: return
|
||||
await self.config.guild(ctx.guild).waitlist_channel.set(channel.id)
|
||||
await ctx.send(f"Waitlist channel has been set to {channel.mention}.")
|
||||
|
||||
|
||||
async def setup(bot: Red):
|
||||
await bot.add_cog(KofiShop(bot))
|
||||
|
||||
0
modmail/README.md
Normal file
0
modmail/README.md
Normal file
1
modmail/__init__.py
Normal file
1
modmail/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .modmail import setup
|
||||
16
modmail/info.json
Normal file
16
modmail/info.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"author": [ "unstableCogs" ],
|
||||
"install_msg": "Thank you for installing the ModMail cog! Please use `[p]help ModMail` for setup commands.",
|
||||
"name": "ModMail",
|
||||
"short": "A private, forum-based ModMail system.",
|
||||
"description": "A comprehensive ModMail system that relays user DMs to a private forum channel for staff to review and respond to. This serves as the core for all ticket-based interactions.",
|
||||
"tags": [
|
||||
"modmail",
|
||||
"moderation",
|
||||
"support",
|
||||
"tickets",
|
||||
"utility"
|
||||
],
|
||||
"requirements": [ "rich" ],
|
||||
"end_user_data_statement": "This cog persistently stores user IDs and message content from ModMail threads for moderation and support history."
|
||||
}
|
||||
218
modmail/modmail.py
Normal file
218
modmail/modmail.py
Normal file
@@ -0,0 +1,218 @@
|
||||
import discord
|
||||
from redbot.core import commands, Config
|
||||
from redbot.core.bot import Red
|
||||
from typing import Optional
|
||||
|
||||
class ModMail(commands.Cog):
|
||||
"""
|
||||
A configurable, forum-based ModMail system.
|
||||
"""
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
# Initialize Red's Config system for storing settings per-server.
|
||||
self.config = Config.get_conf(self, identifier=9876543210, force_registration=True)
|
||||
|
||||
# Define the default settings for each server.
|
||||
default_guild = {
|
||||
"modmail_forum": None, # The ID of the forum channel for tickets
|
||||
"enabled": False, # Whether the system is on or off
|
||||
"active_threads": {} # A dictionary to track {user_id: thread_id}
|
||||
}
|
||||
|
||||
# Register the default settings.
|
||||
self.config.register_guild(**default_guild)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_message(self, message: discord.Message):
|
||||
"""
|
||||
This is the core event listener. It handles both incoming DMs from users
|
||||
and outgoing replies from staff.
|
||||
"""
|
||||
# Ignore messages from bots to prevent loops.
|
||||
if message.author.bot:
|
||||
return
|
||||
|
||||
# --- Part 1: Handling DMs from Users ---
|
||||
if isinstance(message.channel, discord.DMChannel):
|
||||
await self.handle_dm(message)
|
||||
|
||||
# --- Part 2: Handling Replies from Staff ---
|
||||
elif isinstance(message.channel, discord.Thread):
|
||||
await self.handle_staff_reply(message)
|
||||
|
||||
async def handle_dm(self, message: discord.Message):
|
||||
"""Handles messages sent directly to the bot."""
|
||||
# Find a mutual server with the user.
|
||||
guild = next((g for g in self.bot.guilds if g.get_member(message.author.id)), None)
|
||||
if not guild:
|
||||
return
|
||||
|
||||
settings = await self.config.guild(guild).all()
|
||||
if not settings["enabled"] or not settings["modmail_forum"]:
|
||||
return
|
||||
|
||||
forum_channel = guild.get_channel(settings["modmail_forum"])
|
||||
if not isinstance(forum_channel, discord.ForumChannel):
|
||||
return
|
||||
|
||||
active_threads = settings["active_threads"]
|
||||
user_id_str = str(message.author.id)
|
||||
|
||||
# Check if the user already has an active thread.
|
||||
if user_id_str in active_threads:
|
||||
thread_id = active_threads[user_id_str]
|
||||
thread = guild.get_thread(thread_id)
|
||||
if thread:
|
||||
# Relay the message to the existing thread.
|
||||
await thread.send(f"**{message.author.display_name}:** {message.content}")
|
||||
await message.add_reaction("✅")
|
||||
return
|
||||
else:
|
||||
# The thread was deleted, so we clean up our records.
|
||||
async with self.config.guild(guild).active_threads() as threads:
|
||||
del threads[user_id_str]
|
||||
|
||||
# Create a new thread for the user.
|
||||
try:
|
||||
thread_name = f"ModMail | {message.author.name}"
|
||||
embed = discord.Embed(
|
||||
title=f"New ModMail Thread",
|
||||
description=f"**User:** {message.author.mention} (`{message.author.id}`)",
|
||||
color=0xadd8e6 # Light grey pastel blue
|
||||
)
|
||||
embed.add_field(name="Initial Message", value=message.content, inline=False)
|
||||
embed.set_footer(text="Staff can reply in this thread to send a message.")
|
||||
|
||||
thread_with_message = await forum_channel.create_thread(name=thread_name, embed=embed)
|
||||
thread = thread_with_message.thread
|
||||
|
||||
async with self.config.guild(guild).active_threads() as threads:
|
||||
threads[user_id_str] = thread.id
|
||||
|
||||
await message.channel.send("Your message has been received, and a ModMail ticket has been opened. Staff will be with you shortly.")
|
||||
await message.add_reaction("✅")
|
||||
except discord.Forbidden:
|
||||
print(f"ModMail: I don't have permission to create threads in {forum_channel.name}.")
|
||||
except Exception as e:
|
||||
print(f"ModMail: An unexpected error occurred: {e}")
|
||||
|
||||
async def handle_staff_reply(self, message: discord.Message):
|
||||
"""Handles messages sent by staff inside a ModMail thread."""
|
||||
guild = message.guild
|
||||
if not guild:
|
||||
return
|
||||
|
||||
active_threads = await self.config.guild(guild).active_threads()
|
||||
|
||||
# Find which user this thread belongs to by checking our records.
|
||||
thread_id_str = str(message.channel.id)
|
||||
user_id = None
|
||||
for uid, tid in active_threads.items():
|
||||
if str(tid) == thread_id_str:
|
||||
user_id = int(uid)
|
||||
break
|
||||
|
||||
if not user_id:
|
||||
# This is a regular thread, not a ModMail thread we're tracking.
|
||||
return
|
||||
|
||||
user = guild.get_member(user_id)
|
||||
if not user:
|
||||
# User might have left the server.
|
||||
await message.channel.send("⚠️ **Error:** Could not find the user. They may have left the server.")
|
||||
return
|
||||
|
||||
# Send the staff's message to the user's DMs.
|
||||
try:
|
||||
embed = discord.Embed(
|
||||
description=message.content,
|
||||
color=0xadd8e6 # Light grey pastel blue
|
||||
)
|
||||
embed.set_author(name="Staff Response") # Anonymize the staff member
|
||||
|
||||
await user.send(embed=embed)
|
||||
await message.add_reaction("📨") # Add a mail icon to show it was sent
|
||||
except discord.Forbidden:
|
||||
await message.channel.send("⚠️ **Error:** I could not send a DM to this user. They may have DMs disabled.")
|
||||
except Exception as e:
|
||||
await message.channel.send(f"⚠️ **Error:** An unexpected error occurred: {e}")
|
||||
|
||||
|
||||
# --- Settings and Management Commands ---
|
||||
@commands.group(aliases=["mmset"]) # type: ignore
|
||||
@commands.guild_only()
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
async def modmailset(self, ctx: commands.Context):
|
||||
"""
|
||||
Configure the ModMail settings for this server.
|
||||
"""
|
||||
pass
|
||||
|
||||
@modmailset.command(name="forum")
|
||||
async def modmailset_forum(self, ctx: commands.Context, channel: discord.ForumChannel):
|
||||
"""Set the forum channel where ModMail tickets will be created."""
|
||||
if not ctx.guild:
|
||||
return
|
||||
await self.config.guild(ctx.guild).modmail_forum.set(channel.id)
|
||||
await ctx.send(f"The ModMail forum has been set to {channel.mention}.")
|
||||
|
||||
@modmailset.command(name="toggle")
|
||||
async def modmailset_toggle(self, ctx: commands.Context):
|
||||
"""Enable or disable the ModMail system on this server."""
|
||||
if not ctx.guild:
|
||||
return
|
||||
current_status = await self.config.guild(ctx.guild).enabled()
|
||||
new_status = not current_status
|
||||
await self.config.guild(ctx.guild).enabled.set(new_status)
|
||||
status_text = "enabled" if new_status else "disabled"
|
||||
await ctx.send(f"The ModMail system has been {status_text}.")
|
||||
|
||||
@modmailset.command(name="close")
|
||||
async def modmailset_close(self, ctx: commands.Context, *, reason: Optional[str] = "No reason provided."):
|
||||
"""
|
||||
Close the current ModMail thread.
|
||||
|
||||
You must run this command inside the thread you wish to close.
|
||||
"""
|
||||
if not ctx.guild or not isinstance(ctx.channel, discord.Thread):
|
||||
await ctx.send("This command can only be run inside a ModMail thread.")
|
||||
return
|
||||
|
||||
active_threads = await self.config.guild(ctx.guild).active_threads()
|
||||
thread_id_str = str(ctx.channel.id)
|
||||
user_id = None
|
||||
for uid, tid in active_threads.items():
|
||||
if str(tid) == thread_id_str:
|
||||
user_id = int(uid)
|
||||
break
|
||||
|
||||
if not user_id:
|
||||
await ctx.send("This does not appear to be an active ModMail thread.")
|
||||
return
|
||||
|
||||
# Clean up our records.
|
||||
async with self.config.guild(ctx.guild).active_threads() as threads:
|
||||
del threads[str(user_id)]
|
||||
|
||||
# Notify the user.
|
||||
user = self.bot.get_user(user_id)
|
||||
if user:
|
||||
try:
|
||||
embed = discord.Embed(
|
||||
title="ModMail Ticket Closed",
|
||||
description=f"Your ticket has been closed by staff.\n\n**Reason:** {reason}",
|
||||
color=0xadd8e6
|
||||
)
|
||||
await user.send(embed=embed)
|
||||
except discord.Forbidden:
|
||||
pass # Can't notify user if DMs are closed
|
||||
|
||||
# Archive the thread.
|
||||
await ctx.send(f"Ticket closed by {ctx.author.mention}. Archiving thread...")
|
||||
await ctx.channel.edit(archived=True, locked=True)
|
||||
|
||||
# This required function allows Red to load the cog.
|
||||
async def setup(bot: Red):
|
||||
await bot.add_cog(ModMail(bot))
|
||||
|
||||
0
mors/README.md
Normal file
0
mors/README.md
Normal file
0
mors/__init__.py
Normal file
0
mors/__init__.py
Normal file
16
mors/info.json
Normal file
16
mors/info.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"author": [ "unstableCogs" ],
|
||||
"install_msg": "Thank you for installing the Mass Outreach & Review System! For a full command list, please see the wiki.",
|
||||
"name": "MassOutreach",
|
||||
"short": "A multi-step system for mass outreach and reviews.",
|
||||
"description": "Manages a complete workflow for user outreach. The process begins with /game, uses /over for time selection, and concludes with a /pudding-head command for the user to submit a mass review.",
|
||||
"tags": [
|
||||
"mass",
|
||||
"outreach",
|
||||
"review",
|
||||
"tickets",
|
||||
"utility"
|
||||
],
|
||||
"requirements": [ "rich" ],
|
||||
"end_user_data_statement": "This cog stores user IDs and ticket information temporarily for the duration of the outreach process. Submitted reviews are stored persistently."
|
||||
}
|
||||
0
mors/mors.py
Normal file
0
mors/mors.py
Normal file
5
pp/__init__.py
Normal file
5
pp/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .passwdprotect import Passwd
|
||||
|
||||
async def setup(bot):
|
||||
cog = Passwd(bot)
|
||||
await bot.add_cog(cog)
|
||||
10
pp/info.json
Normal file
10
pp/info.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"author": ["kitsunic"],
|
||||
"description": "A cog for password protected channels",
|
||||
"end_user_data_statement": "This cog stores user, avatar, and guild data. A simple delete request to bot to remove your data or guild data",
|
||||
"install_msg": "Thanks for installing & testing.",
|
||||
"min_bot_version": "3.5.0",
|
||||
"short": "A pwd channel protection cog",
|
||||
"tags": ["embed"],
|
||||
"type": "COG"
|
||||
}
|
||||
366
pp/passwdprotect.py
Normal file
366
pp/passwdprotect.py
Normal file
@@ -0,0 +1,366 @@
|
||||
import discord
|
||||
import hashlib
|
||||
import asyncio
|
||||
import random
|
||||
import string
|
||||
import datetime
|
||||
|
||||
|
||||
from redbot.core import commands, checks, Config
|
||||
|
||||
|
||||
class Passwd(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.config = Config.get_conf(self, identifier=50204090) # Unique identifier
|
||||
# Default settings within the config
|
||||
default_guild = {
|
||||
"password_channels": {},
|
||||
"password_attempts": {},
|
||||
"admin_channel": None,
|
||||
"password_recovery_requests": {},
|
||||
"channel_data": {},
|
||||
"whitelist": [],
|
||||
"blacklist": [],
|
||||
}
|
||||
self.config.register_guild(**default_guild)
|
||||
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
@commands.group(name="passwdprotect", aliases=["pwd"])
|
||||
async def passwdprotect(self, ctx):
|
||||
"""Password management commands"""
|
||||
if not ctx.invoked_subcommand:
|
||||
await ctx.send("Missing subcommand.")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
@passwdprotect.command(name="verify", aliases=["ver"])
|
||||
async def verify(self, ctx, password: str):
|
||||
"""Verifies the password for the current channel."""
|
||||
channel = ctx.channel
|
||||
|
||||
async with self.config.guild(ctx.guild).password_channels() as password_channels:
|
||||
if channel.id in password_channels:
|
||||
stored_hash = password_channels[channel.id]["password_hash"]
|
||||
provided_hash = hashlib.sha256(password.encode("utf-8")).hexdigest()
|
||||
|
||||
if stored_hash == provided_hash:
|
||||
# Password verified successfully
|
||||
role_id = password_channels[channel.id]["role_id"]
|
||||
role = ctx.guild.get_role(role_id)
|
||||
if role:
|
||||
await ctx.author.add_roles(role)
|
||||
await ctx.send(f"Password verified! You now have access to {channel.mention}.")
|
||||
else:
|
||||
await ctx.send("Password verified, but the access role seems to be missing. Please contact an admin.")
|
||||
else:
|
||||
await ctx.send("Incorrect password.")
|
||||
else:
|
||||
await ctx.send("This channel is not password protected.")
|
||||
|
||||
@passwdprotect.group(name="channels", aliases=["chs", "--ch"])
|
||||
async def channels(self, ctx):
|
||||
"""Manage protected channels"""
|
||||
if not ctx.invoked_subcommand:
|
||||
await ctx.send("Invalid subcommand.")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
@channels.command(name="add", aliases=["+"])
|
||||
@commands.has_permissions(manage_channels=True)
|
||||
async def channels_add(self, ctx, channel: discord.TextChannel, password: str):
|
||||
"""Adds a password to a channel"""
|
||||
hashed_password = self.generate_hashed_password(password)
|
||||
async with self.config.guild(ctx.guild).password_channels() as password_channels:
|
||||
# Check if the channel already has a password
|
||||
if channel.id in password_channels:
|
||||
await ctx.send(f"{channel.mention} already has a password. Use the `remove` command first.")
|
||||
return
|
||||
|
||||
# Create the role
|
||||
role_name = f"{channel.name}_access"
|
||||
role = await ctx.guild.create_role(name=role_name)
|
||||
|
||||
# Grant permissions to the role
|
||||
await channel.set_permissions(role, view_channel=True, send_messages=True)
|
||||
await channel.set_permissions(ctx.guild.default_role, view_channel=False, read_message_history=False) # Restrict access for default role
|
||||
|
||||
# Store password, channel ID, and role ID in the config
|
||||
password_channels[channel.id] = {
|
||||
"password_hash": hashed_password,
|
||||
"role_id": role.id
|
||||
}
|
||||
|
||||
await ctx.send(f"Password set for {channel.mention}")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
@channels.command(name="remove", aliases=["-"])
|
||||
@commands.has_permissions(manage_channels=True)
|
||||
async def channels_remove(self, ctx, channel: discord.TextChannel):
|
||||
"""Removes the password from a text channel."""
|
||||
async with self.config.guild(ctx.guild).password_channels() as password_channels:
|
||||
if channel.id in password_channels:
|
||||
# Delete the channel's data from the config
|
||||
del password_channels[channel.id]
|
||||
|
||||
# Delete the associated role (optional)
|
||||
role_id = channel.get("role_id")
|
||||
if role_id:
|
||||
role = ctx.guild.get_role(role_id)
|
||||
if role:
|
||||
await role.delete()
|
||||
|
||||
await ctx.send(f"Password removed from {channel.mention}")
|
||||
else:
|
||||
await ctx.send(f"{channel.mention} does not have a password.")
|
||||
|
||||
@channels.command(name="fails", aliases=["--fail"])
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
async def channels_fails(self, ctx, limit: int = None):
|
||||
"""Sets or checks the maximum failed password attempts."""
|
||||
# ... handle failed attempt limit ...
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
@channels.command(name="whitelist", aliases=["wlist"])
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
async def channels_white(self, ctx, subcommand: str, user_or_role: discord.Object = None):
|
||||
"""Manages the whitelist."""
|
||||
guild_id = str(ctx.guild.id)
|
||||
password_collection = self.get_password_collection(guild_id)
|
||||
|
||||
if not user_or_role:
|
||||
await ctx.send("Please specify a user or role to add/remove.")
|
||||
return
|
||||
|
||||
if subcommand == "add":
|
||||
await self.add_to_whitelist(password_collection, user_or_role)
|
||||
elif subcommand == "remove":
|
||||
await self.remove_from_whitelist(password_collection, user_or_role)
|
||||
else:
|
||||
await ctx.send("Invalid subcommand. Use `add` or `remove`.")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
@channels.command(name="blacklist", aliases=["blist"])
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
async def channels_black(self, ctx, subcommand: str, user_or_role: discord.Object = None):
|
||||
"""Manages the blacklist."""
|
||||
if not user_or_role:
|
||||
await ctx.send("Please specify a user or role to add/remove.")
|
||||
return
|
||||
|
||||
if subcommand == "add":
|
||||
await self.add_to_blacklist(ctx, user_or_role)
|
||||
elif subcommand == "remove":
|
||||
await self.remove_from_blacklist(ctx, user_or_role)
|
||||
else:
|
||||
await ctx.send("Invalid subcommand. Use `add` or `remove`.")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
@passwdprotect.group(name="admin", aliases=["adm"])
|
||||
async def admin(self, ctx):
|
||||
"""Admin commands for password protection"""
|
||||
if not ctx.invoked_subcommand:
|
||||
await ctx.send("Missing subcommand.")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
@admin.command(name="recover", aliases=["rec"])
|
||||
async def admin_recover(self, ctx):
|
||||
"""Initiates password recovery process."""
|
||||
requests = await self.config.guild(ctx.guild).password_recovery_requests()
|
||||
if not requests:
|
||||
await ctx.send("No pending password recovery requests.")
|
||||
return
|
||||
|
||||
admin_channel_id = await self.config.guild(ctx.guild).admin_channel()
|
||||
if not admin_channel_id:
|
||||
await ctx.send("Admin notification channel not set. Use `[p]passwdprotect admin notify` to set it.")
|
||||
return
|
||||
|
||||
admin_channel = self.bot.get_channel(admin_channel_id)
|
||||
# Add Approve/Deny buttons using View
|
||||
view = ui.View()
|
||||
view.add_item(ui.Button(style=discord.ButtonStyle.green, label="Approve", custom_id=f"approve_{user_id}_{channel.id}"))
|
||||
view.add_item(ui.Button(style=discord.ButtonStyle.red, label="Deny", custom_id=f"deny_{user_id}_{channel.id}"))
|
||||
|
||||
for user_id, request_data in requests.items():
|
||||
user = self.bot.get_user(int(user_id))
|
||||
channel = self.bot.get_channel(request_data["channel_id"])
|
||||
timestamp = request_data["timestamp"]
|
||||
|
||||
# Log the request to the admin channel
|
||||
embed = discord.Embed(title="Password Recovery Request", color=discord.Color.gold())
|
||||
embed.add_field(name="User", value=user.mention if user else f"User ID: {user_id}", inline=False)
|
||||
embed.add_field(name="Server", value=ctx.guild.name, inline=False)
|
||||
embed.add_field(name="Channel", value=channel.mention if channel else f"Channel ID: {request_data['channel_id']}", inline=False)
|
||||
embed.add_field(name="Highest Role", value=user.top_role.mention if user else "Unknown", inline=False)
|
||||
embed.set_footer(text=f"Requested at {timestamp}")
|
||||
|
||||
# Add Approve/Deny buttons
|
||||
view = SimpleView()
|
||||
view.add_item(Button(style=discord.ButtonStyle.green, label="Approve", custom_id=f"approve_{user_id}_{channel.id}"))
|
||||
view.add_item(Button(style=discord.ButtonStyle.red, label="Deny", custom_id=f"deny_{user_id}_{channel.id}"))
|
||||
|
||||
# Ping the role (if set) in the admin channel
|
||||
role_to_ping = await self.config.guild(ctx.guild).get_raw("admin_role") # Assuming you store the role ID in config
|
||||
if role_to_ping:
|
||||
await admin_channel.send(f"<@&{role_to_ping}>", embed=embed, view=view)
|
||||
else:
|
||||
await admin_channel.send(embed=embed, view=view)
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
@admin.command(name="notify", aliases=["noti"])
|
||||
@commands.has_permissions(manage_guild=True)
|
||||
async def admin_notify(self, ctx, channel: discord.TextChannel = None):
|
||||
"""Sets or removes the admin notification channel."""
|
||||
if channel:
|
||||
# Set the admin notification channel
|
||||
await self.config.guild(ctx.guild).admin_channel.set(channel.id)
|
||||
await ctx.send(f"Admin notification channel set to {channel.mention}")
|
||||
else:
|
||||
# Remove the admin notification channel
|
||||
await self.config.guild(ctx.guild).admin_channel.set(None)
|
||||
await ctx.send("Admin notification channel removed.")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
@commands.Cog.listener()
|
||||
async def on_interaction(self, interaction, channel: discord.TextChannel):
|
||||
if interaction.type == discord.InteractionType.component:
|
||||
custom_id = interaction.data["custom_id"]
|
||||
if custom_id.startswith("approve_") or custom_id.startswith("deny_"):
|
||||
action, user_id, channel_id = custom_id.split("_")
|
||||
user_id = int(user_id)
|
||||
channel_id = int(channel_id)
|
||||
|
||||
async with self.config.guild(interaction.guild).password_recovery_requests() as requests:
|
||||
if user_id in requests:
|
||||
del requests[user_id] # Remove the request
|
||||
|
||||
if action == "approve":
|
||||
temp_code = self.generate_temporary_access_code() # Implement this function
|
||||
expiry_time = datetime.datetime.now() + datetime.timedelta(hours=1) # 1-hour validity
|
||||
|
||||
async with self.config.guild(interaction.guild).temp_access_codes() as temp_codes:
|
||||
temp_codes[temp_code] = {"user_id": user_id, "channel_id": channel_id, "expiry": expiry_time.isoformat()}
|
||||
|
||||
user = self.bot.get_user(user_id)
|
||||
if user:
|
||||
await user.send(f"Here's your temporary access code for channel {channel.mention}: `{temp_code}`. It's valid for 1 hour.")
|
||||
elif user:
|
||||
await interaction.response.send_message(f"Approved password recovery for {user.mention if user else user_id} on channel {channel.mention}. Temporary code sent.", ephemeral=True)
|
||||
else:
|
||||
await interaction.response.send_message("Channel data not found. Cannot reset password.", ephemeral=True)
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
# Helper functions
|
||||
def generate_hashed_password(self, password):
|
||||
# This remains the same, no changes needed
|
||||
return hashlib.sha256(password.encode("utf-8")).hexdigest()
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
async def set_channel_password(self, ctx: commands.Context, channel_id, password):
|
||||
"""Sets the password for a channel using the config.
|
||||
|
||||
Args:
|
||||
ctx (commands.Context): The command context to access guild information.
|
||||
channel_id (int): The ID of the channel.
|
||||
password (str): The password to set.
|
||||
"""
|
||||
hashed_password = self.generate_hashed_password(password)
|
||||
async with self.config.guild(ctx.guild).password_channels() as password_channels:
|
||||
password_channels[channel_id] = {
|
||||
"password_hash": hashed_password
|
||||
}
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
def generate_temporary_access_code(self):
|
||||
"""Generates a temporary access code."""
|
||||
characters = string.ascii_letters + string.digits
|
||||
code = ''.join(random.choice(characters) for i in range(10)) # 10-character code
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
||||
return f"{code}-{timestamp}"
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
async def add_to_whitelist(self, ctx, collection, user_or_role):
|
||||
"""Adds a user or role to the whitelist."""
|
||||
async with self.config.guild(ctx.guild).whitelist() as whitelist:
|
||||
user_or_role_id = user_or_role.id
|
||||
if any(item["id"] == user_or_role_id for item in whitelist):
|
||||
await ctx.send(f"{user_or_role} is already in the whitelist.")
|
||||
return
|
||||
|
||||
whitelist.append({"id": user_or_role_id, "type": "user" if isinstance(user_or_role, discord.Member) else "role"})
|
||||
|
||||
embed = discord.Embed(title="Whitelist Confirmation", description=f"Are you sure you want to add {user_or_role} to the whitelist?")
|
||||
embed.add_field(name="Action", value="Add to Whitelist")
|
||||
embed.set_footer(text="React with ✅ to confirm or ❌ to cancel.")
|
||||
confirmation_msg = await ctx.send(embed=embed)
|
||||
|
||||
try:
|
||||
reaction, user = await ctx.wait_for('reaction_add', timeout=30.0, check=lambda r, u: r.message.id == confirmation_msg.id and u == ctx.author and str(r.emoji) in ['✅', '❌'])
|
||||
if str(reaction.emoji) == '✅':
|
||||
await confirmation_msg.delete()
|
||||
await ctx.send(f"{user_or_role} added to the whitelist.")
|
||||
else:
|
||||
await confirmation_msg.delete()
|
||||
await ctx.send("Action cancelled.")
|
||||
except asyncio.TimeoutError:
|
||||
await confirmation_msg.delete()
|
||||
await ctx.send("Confirmation timed out. Action cancelled.")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
async def remove_from_whitelist(self, ctx, collection, user_or_role):
|
||||
"""Removes a user or role from the whitelist."""
|
||||
async with self.config.guild(ctx.guild).whitelist() as whitelist:
|
||||
user_or_role_id = user_or_role.id
|
||||
whitelist[:] = [item for item in whitelist if item["id"] != user_or_role_id]
|
||||
await ctx.send(f"{user_or_role} removed from the whitelist.")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
async def add_to_blacklist(self, ctx, user_or_role):
|
||||
"""Adds a user or role to the blacklist."""
|
||||
async with self.config.guild(ctx.guild).blacklist() as blacklist:
|
||||
user_or_role_id = user_or_role.id
|
||||
if any(item["id"] == user_or_role_id for item in blacklist):
|
||||
await ctx.send(f"{user_or_role} is already in the blacklist.")
|
||||
return
|
||||
|
||||
blacklist.append({"id": user_or_role_id, "type": "user" if isinstance(user_or_role, discord.Member) else "role"})
|
||||
|
||||
await ctx.send(f"{user_or_role} added to the blacklist.")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
async def remove_from_blacklist(self, ctx, user_or_role):
|
||||
"""Removes a user or role from the blacklist."""
|
||||
async with self.config.guild(ctx.guild).blacklist() as blacklist:
|
||||
user_or_role_id = user_or_role.id
|
||||
blacklist[:] = [item for item in blacklist if item["id"] != user_or_role_id]
|
||||
await ctx.send(f"{user_or_role} removed from the blacklist.")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
async def cog_load(self):
|
||||
self.bot.add_listener(self.on_cog_reload_error, "Package loading failed")
|
||||
self.bot.add_listener(self.on_cog_reload_error, "SyntaxError:")
|
||||
self.bot.add_listener(self.on_cog_reload_error, "IndentationError:")
|
||||
#
|
||||
# -------------------------------------------------------------------------------------------------------------
|
||||
#
|
||||
async def on_cog_reload_error(self, ctx, error):
|
||||
error_message = f"Error reloading cog '{ctx.cog.qualified_name}':\n```{error}```"
|
||||
self.error_logs.append(error_message)
|
||||
6
rpg/__init__.py
Normal file
6
rpg/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .rpg import RPG
|
||||
|
||||
async def setup(bot):
|
||||
cog = RPG(bot)
|
||||
await bot.add_cog(cog)
|
||||
await bot.add_cog(cog.commands)
|
||||
126
rpg/actions.py
Normal file
126
rpg/actions.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import asyncio
|
||||
import random
|
||||
import discord
|
||||
from .check import Check
|
||||
from redbot.core import commands, Config
|
||||
from .inventory import RPGInventory
|
||||
from typing import Literal
|
||||
|
||||
class RPGActions:
|
||||
"""
|
||||
Implements the core RPG mechanics and actions.
|
||||
"""
|
||||
|
||||
def __init__(self, rpg_cog):
|
||||
self.rpg_cog = rpg_cog
|
||||
self.inventory = RPGInventory(rpg_cog)
|
||||
|
||||
async def create_character(self, interaction: discord.Interaction, user, character_name):
|
||||
"""
|
||||
Creates a new character for the user.
|
||||
"""
|
||||
|
||||
# Character Name Validation
|
||||
check = Check(interaction, length=30)
|
||||
if not check.length_under(interaction.message) or not character_name.isalnum():
|
||||
return await self.rpg_cog.send_message(interaction, "Invalid character name. Please choose a name between 2 and 30 characters long, containing only letters and numbers.")
|
||||
|
||||
# Retrieve the 'characters' group
|
||||
characters_group = self.rpg_cog.config.member(user).characters
|
||||
|
||||
# Then, get all characters within that group (await the .all() call)
|
||||
existing_characters = await characters_group.all()
|
||||
|
||||
# Check for duplicate character names (case-insensitive)
|
||||
if any(existing_name.lower() == character_name.lower() for existing_name in existing_characters):
|
||||
return await self.rpg_cog.send_message(interaction, f"You already have a character named '{character_name}'. Choose another name.")
|
||||
|
||||
# Retrieve available classes from config
|
||||
try:
|
||||
available_classes = await self.rpg_cog.config.guild(user.guild).get_raw("classes", default=[])
|
||||
except KeyError:
|
||||
return await self.rpg_cog.send_message(interaction, "No classes have been configured yet. Please contact an admin.")
|
||||
|
||||
if not available_classes:
|
||||
return await self.rpg_cog.send_message(interaction, "No classes are available yet. Please contact an admin.")
|
||||
|
||||
# Prompt user to choose a class
|
||||
class_options = "\n".join([f"{i+1}. {class_name}" for i, class_name in enumerate(available_classes)])
|
||||
await self.rpg_cog.send_message(interaction, f"Choose a class for your character:\n{class_options}")
|
||||
|
||||
def check(m):
|
||||
return m.author == user and m.channel == interaction.channel and m.content.isdigit() and 1 <= int(m.content) <= len(available_classes)
|
||||
|
||||
try:
|
||||
class_choice = await self.rpg_cog.bot.wait_for("message", check=check, timeout=30.0)
|
||||
except asyncio.TimeoutError:
|
||||
return await self.rpg_cog.send_message(interaction, "Class selection timed out. Character creation canceled.")
|
||||
|
||||
selected_class = available_classes[int(class_choice.content) - 1]
|
||||
|
||||
# Retrieve default stats for the selected class
|
||||
try:
|
||||
default_stats = await self.rpg_cog.config.guild(user.guild).get_raw("characters", "classes", selected_class, "stats")
|
||||
except KeyError:
|
||||
return await self.rpg_cog.send_message(interaction, f"Default stats for class '{selected_class}' haven't been configured yet. Please contact an admin.")
|
||||
|
||||
# Create character data
|
||||
character_data = {
|
||||
"name": character_name,
|
||||
"class": [selected_class], # Store the selected class
|
||||
"level": 1,
|
||||
"experience": 0,
|
||||
"stats": default_stats.copy(),
|
||||
"inventory": [],
|
||||
"equipment": {},
|
||||
"skills": [],
|
||||
"gold": 0,
|
||||
"max_health": 100,
|
||||
"health": 100,
|
||||
"mana": 50,
|
||||
"max_mana": 50,
|
||||
}
|
||||
|
||||
# Store character data in config
|
||||
await characters_group.set_raw(character_name, value=character_data)
|
||||
await self.rpg_cog.config.member(user).active_character.set(character_name)
|
||||
|
||||
await interaction.response.send_message(interaction, f"Character '{character_name}' (Class: {selected_class}) created for {user.mention}!")
|
||||
|
||||
async def attack(self, ctx, attacker, target):
|
||||
"""
|
||||
Handles the attack action.
|
||||
"""
|
||||
# Check if attacker and target have active characters
|
||||
if not await self._has_active_character(ctx, attacker):
|
||||
return
|
||||
if not await self._has_active_character(ctx, target):
|
||||
return
|
||||
|
||||
attacker_data = await self.rpg_cog.config.member(attacker).active_character
|
||||
target_data = await self.rpg_cog.config.member(target).active_character
|
||||
|
||||
# ... (rest of the attack logic)
|
||||
|
||||
async def heal(self, ctx, user):
|
||||
"""
|
||||
Handles the heal action.
|
||||
"""
|
||||
# ... (rest of the heal logic)
|
||||
|
||||
# Helper function to check if a user has an active character
|
||||
async def _has_active_character(self, ctx, user):
|
||||
active_character = await self.rpg_cog.config.member(user).active_character
|
||||
if not active_character:
|
||||
await self.rpg_cog.send_message(ctx, f"{user.mention}, you don't have an active character. Create one using `[p]create_character <name>`.")
|
||||
return False
|
||||
return True
|
||||
|
||||
async def use_item(self, ctx, user, item_name):
|
||||
"""
|
||||
Handles the use of an item from the inventory.
|
||||
"""
|
||||
# ... (logic to check if the user has the item and handle its effects)
|
||||
|
||||
# Remove the used item from the inventory
|
||||
await self.inventory.remove_item(ctx, user, item_name)
|
||||
94
rpg/check.py
Normal file
94
rpg/check.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from redbot.core import Config, commands
|
||||
from collections.abc import Iterable
|
||||
import validators as vals
|
||||
import logging
|
||||
import discord
|
||||
|
||||
|
||||
log = logging.getLogger("red.rpg")
|
||||
|
||||
class Check:
|
||||
|
||||
def __init__(self, ctx_or_interaction, custom: Iterable = None, length: int = None):
|
||||
self.ctx_or_interaction = ctx_or_interaction # Store the context or interaction object
|
||||
self.custom = custom
|
||||
self.length = length
|
||||
|
||||
def _get_author(self):
|
||||
"""
|
||||
Helper function to get the author from either ctx or interaction.
|
||||
"""
|
||||
if isinstance(self.ctx_or_interaction, commands.Context):
|
||||
return self.ctx_or_interaction.author
|
||||
elif isinstance(self.ctx_or_interaction, discord.Interaction):
|
||||
return self.ctx_or_interaction.user
|
||||
else:
|
||||
log.error(f"Unexpected object type in Check._get_author: {type(self.ctx_or_interaction)}")
|
||||
return None
|
||||
|
||||
def same(self, m):
|
||||
author = self._get_author()
|
||||
if author is None:
|
||||
return False # Handle the case where author couldn't be determined
|
||||
|
||||
if isinstance(m, discord.Message):
|
||||
return author == m.author
|
||||
elif isinstance(m, discord.Interaction):
|
||||
return author == m.user
|
||||
else:
|
||||
log.error(f"Unexpected object type in Check.same: {type(m)}")
|
||||
return False
|
||||
|
||||
|
||||
def comfirm(self, m):
|
||||
return self.same(m) and m.content.lower() in ("yes", "no")
|
||||
|
||||
def valid_int(self, m):
|
||||
return self.same and m.content.isdigit()
|
||||
|
||||
def valid_float(self, m):
|
||||
try:
|
||||
return self.same(m) and float(m.content) >= 1
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def positive(self, m):
|
||||
return self.same(m) and m.content.isdigit() and int(m.content) > 0
|
||||
|
||||
def role(self, m):
|
||||
roles = [r.name for r in self.ctx.guild.roles if r.name != "Bot"]
|
||||
return self.same(m) and m.content in roles
|
||||
|
||||
def member(self, m):
|
||||
return self.same(m) and m.content in [x.name for x in self.ctx.guild.members]
|
||||
|
||||
def length_under(self, m):
|
||||
try:
|
||||
if isinstance(m, discord.Message):
|
||||
content = m.content
|
||||
elif isinstance(m, discord.Interaction):
|
||||
content = m.data['components'][0]['components'][0]['value'] # Access modal input value
|
||||
else:
|
||||
raise ValueError("Unsupported object type for length_under check")
|
||||
|
||||
return self.same(m) and len(content) <= self.length
|
||||
except TypeError:
|
||||
raise ValueError("Length was not specified in Check")
|
||||
|
||||
def valid_image_url(self, m):
|
||||
url = m.content.strip()
|
||||
|
||||
if not vals.url(url):
|
||||
return False
|
||||
|
||||
valid_extensions = (".jpg", ".jpeg", ".png", ".gif")
|
||||
if not any(url.lower().endswith(ext) for ext in valid_extensions):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def content(self, m):
|
||||
try:
|
||||
return self.same(m) and m.content in self.custom
|
||||
except TypeError:
|
||||
raise ValueError("A custom iterable was not set in Check")
|
||||
21
rpg/commands.py
Normal file
21
rpg/commands.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import discord
|
||||
from redbot.core import commands
|
||||
from .menu import RPGMenu
|
||||
|
||||
class RPGCommands(commands.Cog):
|
||||
"""
|
||||
Handles user commands (primarily menus) for the RPG system.
|
||||
"""
|
||||
|
||||
def __init__(self, rpg_cog, bot):
|
||||
self.rpg_cog = rpg_cog
|
||||
self.bot = bot
|
||||
super().__init__()
|
||||
|
||||
@commands.command(name="menu")
|
||||
async def menu_command(self, ctx):
|
||||
"""
|
||||
Display the RPG menu.
|
||||
"""
|
||||
view = RPGMenu(self.rpg_cog)
|
||||
await ctx.send("Choose an action:", view=view)
|
||||
11
rpg/info.json
Normal file
11
rpg/info.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"author" : ["UnstableKitsune (unstablekitsune)"],
|
||||
"install _msg" : "Oh you installed me! How dare you obtain me! return me right now!",
|
||||
"name" : "RPG",
|
||||
"short" : "Its a TTRPG",
|
||||
"requirements" : [""],
|
||||
"permissions" : [""],
|
||||
"tags" : [""],
|
||||
"min_python_version" : [3, 1, 1],
|
||||
"end_user_data_statement" : ""
|
||||
}
|
||||
80
rpg/inventory.py
Normal file
80
rpg/inventory.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import discord
|
||||
from redbot.core import commands, Config
|
||||
|
||||
class RPGInventory:
|
||||
"""
|
||||
Manages character inventories for the RPG system.
|
||||
"""
|
||||
|
||||
def __init__(self, rpg_cog):
|
||||
self.rpg_cog = rpg_cog
|
||||
super().__init__()
|
||||
|
||||
@commands.group()
|
||||
async def inventory(self, ctx):
|
||||
"""Main RPG command group."""
|
||||
pass
|
||||
|
||||
@inventory.command(name="add")
|
||||
async def add_item(self, ctx, user: discord.Member, item_name, quantity=1):
|
||||
"""
|
||||
Adds an item to the user's active character's inventory.
|
||||
"""
|
||||
character_data = await self.rpg_cog.config.member(user).get_raw("active_character")
|
||||
if not character_data:
|
||||
return await self.rpg_cog.send_message(ctx, f"{user.mention}, you don't have an active character.")
|
||||
|
||||
inventory = character_data.get("inventory", {})
|
||||
if item_name in inventory:
|
||||
inventory[item_name] += quantity
|
||||
else:
|
||||
inventory[item_name] = quantity
|
||||
|
||||
await self.rpg_cog.config.member(user).set_raw("active_character", "inventory", value=inventory)
|
||||
await self.rpg_cog.send_message(ctx, f"{quantity} {item_name}(s) added to your inventory!")
|
||||
|
||||
@inventory.command(name="remove")
|
||||
async def remove_item(self, ctx, user: discord.Member, item_name, quantity=1):
|
||||
"""
|
||||
Removes an item from the user's active character's inventory.
|
||||
"""
|
||||
character_data = await self.rpg_cog.config.member(user).get_raw("active_character")
|
||||
if not character_data:
|
||||
return await self.rpg_cog.send_message(ctx, f"{user.mention}, you don't have an active character.")
|
||||
|
||||
inventory = character_data.get("inventory", {})
|
||||
if item_name in inventory:
|
||||
if inventory[item_name] >= quantity:
|
||||
inventory[item_name] -= quantity
|
||||
if inventory[item_name] == 0:
|
||||
del inventory[item_name]
|
||||
await self.rpg_cog.config.member(user).set_raw("active_character", "inventory", value=inventory)
|
||||
await self.rpg_cog.send_message(ctx, f"{quantity} {item_name}(s) removed from your inventory!")
|
||||
else:
|
||||
await self.rpg_cog.send_message(ctx, f"You don't have enough {item_name}s.")
|
||||
else:
|
||||
await self.rpg_cog.send_message(ctx, f"You don't have any {item_name}s in your inventory.")
|
||||
|
||||
@inventory.command(name="bag")
|
||||
async def get_inventory(self, user):
|
||||
"""
|
||||
Retrieves the inventory of the user's active character.
|
||||
"""
|
||||
character_data = await self.rpg_cog.config.member(user).get_raw("active_character")
|
||||
if not character_data:
|
||||
return None # Or handle the case where the user has no active character
|
||||
return character_data.get("inventory", {})
|
||||
|
||||
@inventory.command(name="showbag")
|
||||
async def display_inventory(self, ctx, user: discord.Member):
|
||||
"""
|
||||
Displays the user's inventory in a user-friendly format.
|
||||
"""
|
||||
inventory = await self.get_inventory(user)
|
||||
if not inventory:
|
||||
return await self.rpg_cog.send_message(ctx, f"{user.mention}, your inventory is empty!")
|
||||
|
||||
# Format the inventory for display (you can customize this)
|
||||
inventory_str = "\n".join([f"{item}: {quantity}" for item, quantity in inventory.items()])
|
||||
embed = discord.Embed(title=f"{user.name}'s Inventory", description=inventory_str)
|
||||
await self.rpg_cog.send_message(ctx, embed=embed)
|
||||
55
rpg/menu.py
Normal file
55
rpg/menu.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import discord
|
||||
from .check import Check
|
||||
|
||||
class RPGMenu(discord.ui.View):
|
||||
def __init__(self, rpg_cog):
|
||||
super().__init__()
|
||||
self.rpg_cog = rpg_cog
|
||||
|
||||
@discord.ui.button(label="Create Character", style=discord.ButtonStyle.primary, custom_id="create_character_button")
|
||||
async def create_character_button_callback(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
await interaction.response.send_modal(CreateCharacterModal(self.rpg_cog))
|
||||
|
||||
@discord.ui.button(label="Attack", style=discord.ButtonStyle.red, custom_id="attack_button")
|
||||
async def attack_button_callback(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
# You'll need to implement the target selection logic here (e.g., using a dropdown or another interaction)
|
||||
target = None # Placeholder for now
|
||||
if target:
|
||||
await self.rpg_cog.actions.attack(interaction, interaction.user, target)
|
||||
else:
|
||||
await interaction.response.send_message("You need to select a target to attack.", ephemeral=True)
|
||||
|
||||
@discord.ui.button(label="Heal", style=discord.ButtonStyle.green, custom_id="heal_button")
|
||||
async def heal_button_callback(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
await self.rpg_cog.actions.heal(interaction, interaction.user)
|
||||
|
||||
@discord.ui.button(label="Stats", style=discord.ButtonStyle.blurple, custom_id="stats_button")
|
||||
async def stats_button_callback(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
await self.rpg_cog.actions.display_stats(interaction, interaction.user)
|
||||
|
||||
@discord.ui.button(label="Inventory", style=discord.ButtonStyle.grey, custom_id="inventory_button")
|
||||
async def inventory_button_callback(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
await self.rpg_cog.inventory.display_inventory(interaction, interaction.user)
|
||||
|
||||
|
||||
class CreateCharacterModal(discord.ui.Modal):
|
||||
def __init__(self, rpg_cog):
|
||||
super().__init__(title="Create Character")
|
||||
self.rpg_cog = rpg_cog
|
||||
self.character_name = discord.ui.TextInput(
|
||||
label="Enter your character's name:",
|
||||
placeholder="e.g., BraveAdventurer",
|
||||
required=True,
|
||||
max_length=30
|
||||
)
|
||||
self.add_item(self.character_name)
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
character_name = self.character_name.value
|
||||
#author = interaction.user
|
||||
|
||||
try:
|
||||
await self.rpg_cog.actions.create_character(interaction, interaction.user, character_name)
|
||||
except Exception as e:
|
||||
await interaction.response.send_message(f"```An error occurred while creating your character: {e}```")
|
||||
self.rpg_cog.log.error(f"Error in create_character: {e}")
|
||||
187
rpg/rpg.py
Normal file
187
rpg/rpg.py
Normal file
@@ -0,0 +1,187 @@
|
||||
from atexit import register
|
||||
import logging
|
||||
import discord
|
||||
|
||||
from distutils import config
|
||||
from redbot.core import commands, Config
|
||||
from .check import Check
|
||||
from .commands import RPGCommands
|
||||
from .actions import RPGActions
|
||||
from .shop import RPGShops
|
||||
from .menu import RPGMenu
|
||||
from .inventory import RPGInventory
|
||||
|
||||
|
||||
class RPG(commands.Cog):
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
self.menu = RPGMenu(self)
|
||||
self.commands = RPGCommands(self, bot)
|
||||
self.actions = RPGActions(self)
|
||||
self.shop = RPGShops(self)
|
||||
self.inventory = RPGInventory(self)
|
||||
self.log = logging.getLogger("red.rpg")
|
||||
self.config = Config.get_conf(self, identifier=6857309412)
|
||||
|
||||
default_guild_settings = {
|
||||
"characters": {
|
||||
"default_stats": { # Nested under 'characters'
|
||||
"Strength": 1,
|
||||
"Defense": 1,
|
||||
"Agility": 1,
|
||||
"Magic": 1,
|
||||
"Luck": 1,
|
||||
"Health": 100,
|
||||
"Mana": 50,
|
||||
"Stamina": 50,
|
||||
"Experience": 0,
|
||||
"Gold": 0,
|
||||
"Damage": 1,
|
||||
"Recovery": 1,
|
||||
"Resist": 1,
|
||||
"Critical": 1,
|
||||
"Speed": 1,
|
||||
"Level": 1,
|
||||
"Class": [],
|
||||
"Skills": [],
|
||||
"Inventory": [],
|
||||
"Equipment": {},
|
||||
"Quests": [],
|
||||
"Guild": None,
|
||||
"Location": None,
|
||||
"Followers": None,
|
||||
"Pets": None,
|
||||
},
|
||||
"default_statsmax": { # Nested under 'characters'
|
||||
"Strength": 999,
|
||||
"Defense": 999,
|
||||
"Agility": 999,
|
||||
"Magic": 999,
|
||||
"Luck": 999,
|
||||
"Health": 999,
|
||||
"Mana": 999,
|
||||
"Stamina": 999,
|
||||
"Experience": 999999999,
|
||||
"Gold": 999999999,
|
||||
"Damage": 999,
|
||||
"Recovery": 999,
|
||||
"Resist": 999,
|
||||
"Critical": 999,
|
||||
"Speed": 999,
|
||||
"Level": 999999999,
|
||||
}
|
||||
},
|
||||
"classes": { # Definitions of available classes
|
||||
"Warrior": {
|
||||
"stats": {
|
||||
"Strength": 5,
|
||||
"Defense": 4,
|
||||
"Agility": 3,
|
||||
# ... other stats with appropriate values for a warrior
|
||||
},
|
||||
"description": "A brave and sturdy warrior, skilled in melee combat."
|
||||
},
|
||||
"Mage": {
|
||||
"stats": {
|
||||
"Strength": 2,
|
||||
"Defense": 2,
|
||||
"Magic": 5,
|
||||
# ... other stats with appropriate values for a mage
|
||||
},
|
||||
"description": "A wise and powerful mage, capable of wielding arcane magic."
|
||||
},
|
||||
"Rogue": {
|
||||
"stats": {
|
||||
"Strength": 3,
|
||||
"Defense": 3,
|
||||
"Agility": 5,
|
||||
# ... other stats with appropriate values for a rogue
|
||||
},
|
||||
"description": "A nimble and cunning rogue, adept at stealth and trickery."
|
||||
}
|
||||
# ... add more classes as needed
|
||||
},
|
||||
"active_character": {}, # Store the name of the currently active character for each member
|
||||
}
|
||||
|
||||
|
||||
default_user_settings = {
|
||||
"active_character": None,
|
||||
}
|
||||
|
||||
default_shop = {
|
||||
"Items": [],
|
||||
"Prices": [],
|
||||
"Currency": "Gold",
|
||||
"Currency_Symbol": "G",
|
||||
"Currency_Plural": "Gold",
|
||||
"Currency_Plural_Symbol": "G",
|
||||
"Currency_Emoji": "💰",
|
||||
}
|
||||
|
||||
default_quest = {
|
||||
"Name": None,
|
||||
"Description": None,
|
||||
"Reward": None,
|
||||
"Requirements": None,
|
||||
"Progress": None,
|
||||
"Status": None,
|
||||
"Type": None,
|
||||
"Repeat": None,
|
||||
"Time": None,
|
||||
"Location": None,
|
||||
"Guild": None,
|
||||
"Followers": None,
|
||||
"Pets": None,
|
||||
}
|
||||
self.config.register_guild(**default_guild_settings)
|
||||
self.config.register_user(**default_user_settings)
|
||||
self.config.register_global(**default_shop)
|
||||
self.config.register_global(**default_quest)
|
||||
|
||||
|
||||
self.commands = RPGCommands(self, bot)
|
||||
self.actions = RPGActions(self)
|
||||
self.shop = RPGShops(self)
|
||||
self.menu = RPGMenu(self)
|
||||
self.inventory = RPGInventory(self)
|
||||
|
||||
async def register_guild(self, ctx):
|
||||
await self.config.register_guild(ctx.guild, **default_guild_settings)
|
||||
await self.send_message(ctx, "Guild registered for RPG system.")
|
||||
|
||||
async def register_user(self, ctx):
|
||||
await self.config.register_user(ctx.author, **default_user_settings)
|
||||
await self.send_message(ctx, "User registered for RPG system.")
|
||||
|
||||
async def register_shop(self, ctx):
|
||||
await self.config.register_global(**default_shop)
|
||||
await self.send_message(ctx, "Shop registered for RPG system.")
|
||||
|
||||
async def register_quest(self, ctx):
|
||||
await self.config.register_global(**default_quest)
|
||||
await self.send_message(ctx, "Quest registered for RPG system.")
|
||||
|
||||
async def unregister_guild(self, ctx):
|
||||
await self.config.unregister_guild(ctx.guild)
|
||||
await self.send_message(ctx, "Guild unregistered for RPG system.")
|
||||
|
||||
async def unregister_user(self, ctx):
|
||||
await self.config.unregister_user(ctx.author)
|
||||
await self.send_message(ctx, "User unregistered for RPG system.")
|
||||
|
||||
async def unregister_shop(self, ctx):
|
||||
await self.config.unregister_global("Shop")
|
||||
await self.send_message(ctx, "Shop unregistered for RPG system.")
|
||||
|
||||
async def unregister_quest(self, ctx):
|
||||
await self.config.unregister_global("Quest")
|
||||
await self.send_message(ctx, "Quest unregistered for RPG system.")
|
||||
|
||||
async def send_message(self, ctx, message, interaction, embed=None):
|
||||
if embed:
|
||||
await ctx.send(message, embed=embed)
|
||||
elif interaction:
|
||||
await interaction.response.send(message)
|
||||
else:
|
||||
await ctx.send(message)
|
||||
176
rpg/shop.py
Normal file
176
rpg/shop.py
Normal file
@@ -0,0 +1,176 @@
|
||||
from ast import alias
|
||||
from enum import member
|
||||
from locale import currency
|
||||
import discord
|
||||
from redbot.core import commands
|
||||
from .inventory import RPGInventory
|
||||
|
||||
|
||||
class RPGShops(commands.Cog):
|
||||
|
||||
def __init__(self, rpg_cog):
|
||||
self.rpg_cog = rpg_cog
|
||||
self.config = self.rpg_cog
|
||||
|
||||
default_guild = {
|
||||
"shops": {},
|
||||
"status": {
|
||||
"active": bool,
|
||||
"deactive": bool,
|
||||
},
|
||||
"currency": {
|
||||
"name": str,
|
||||
"symbol": str,
|
||||
"emoji": str,
|
||||
}
|
||||
}
|
||||
#self.config.register_guild(**default_guild_settings)
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
# --------------- SHOP GROUP COMMANDS -----------------------------------------------------
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
@commands.group(name="shop", alias=["sp"])
|
||||
async def shop(self, ctx):
|
||||
pass
|
||||
|
||||
# Comes from Base Shop Group Above
|
||||
@shop.group(name="manager", alias=["mgr"])
|
||||
async def manager(self, ctx):
|
||||
pass
|
||||
|
||||
# Comes from Manager Group Above
|
||||
@manager.group(name="set", alias=["st"])
|
||||
async def set(self, ctx):
|
||||
pass
|
||||
|
||||
# Comes From Set Group From Above
|
||||
@set.group(name="status", alias=["sta"])
|
||||
async def status(self, ctx):
|
||||
pass
|
||||
|
||||
# Comes From Set Group From Above
|
||||
@set.group(name="currency", alias=["cur"])
|
||||
async def currency(self, ctx):
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
# ------------------ SHOP BASE COMMANDS --------------------------------------------------
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
@shop.command(name="buy", alias=["by"]) # shop buy
|
||||
async def buy(self, ctx, shop_name: str, item_name: str, quantity: int = 1):
|
||||
"""
|
||||
Buy an item from the shop.
|
||||
"""
|
||||
# Retrieve shop data
|
||||
shops = await self.config.guild(ctx.guild).shops.all()
|
||||
if shop_name not in shops:
|
||||
return await ctx.send(f"Shop '{shop_name}' not found.")
|
||||
|
||||
shop_data = shops[shop_name]
|
||||
items = shop_data.get("items", {}) # Assuming you store items in an 'items' dictionary within the shop data
|
||||
if item_name not in items:
|
||||
return await ctx.send(f"Item '{item_name}' not found in '{shop_name}'.")
|
||||
|
||||
item_data = items[item_name]
|
||||
price = item_data.get("price")
|
||||
stock = item_data.get("quantity")
|
||||
|
||||
# Check if the shop has enough stock
|
||||
if stock is not None and stock < quantity:
|
||||
return await ctx.send(f"Not enough '{item_name}' in stock. Only {stock} available.")
|
||||
|
||||
# Check if the user has enough currency
|
||||
currency_name = await self.config.guild(ctx.guild).shops_currency_name.get()
|
||||
user_balance = await self.rpg_cog.config.member(ctx.author).get_raw("gold") # Assuming gold is the currency
|
||||
total_cost = price * quantity
|
||||
if user_balance < total_cost:
|
||||
return await ctx.send(f"You don't have enough {currency_name} to buy {quantity} {item_name}(s).")
|
||||
|
||||
# Deduct currency and add item to inventory
|
||||
await self.rpg_cog.config.member(ctx.author).set_raw("gold", value=user_balance - total_cost)
|
||||
await self.rpg_cog.inventory.add_item(ctx, ctx.author, item_name, quantity)
|
||||
|
||||
# Update shop inventory (if applicable)
|
||||
if stock is not None:
|
||||
shop_data["items"][item_name]["quantity"] -= quantity
|
||||
await self.config.guild(ctx.guild).shops.set_raw(shop_name, value=shop_data)
|
||||
|
||||
await ctx.send(f"You bought {quantity} {item_name}(s) for {total_cost} {currency_name}!")
|
||||
|
||||
@shop.command(name="sell", alias=["sl"]) # shop sell
|
||||
async def buy(self, ctx, item_name: str):
|
||||
"""
|
||||
Sell an item back to the shop.
|
||||
"""
|
||||
# ... (logic to handle the purchase and currency)
|
||||
|
||||
# Add the purchased item to the user's inventory
|
||||
await self.inventory.remove_item(ctx, ctx.author, item_name)
|
||||
|
||||
@shop.command(name="give", alias=["gi"]) # shop give
|
||||
async def give(self, ctx, item_name: str, target: member):
|
||||
"""
|
||||
Give an item to another player.
|
||||
"""
|
||||
# ... (logic to handle the transfer of items between players)
|
||||
await self.inventory.remove_item(ctx, ctx.author, item_name)
|
||||
await self.inventory.add_item(ctx, target, item_name)
|
||||
|
||||
@shop.command(name="list", alias=["ls"]) # shop list
|
||||
async def list(self, ctx, shop_name: str):
|
||||
"""
|
||||
List the items available in a shop.
|
||||
"""
|
||||
# ... (logic to retrieve and display the items in the shop)
|
||||
items = await self.rpg_cog.config.guild(ctx.guild).shops_items.get_raw(shop_name)
|
||||
prices = await self.rpg_cog.config.guild(ctx.guild).shops_prices.get_raw(shop_name)
|
||||
|
||||
if items:
|
||||
item_list = "\n".join([f"{item} - {price}" for item, price in zip(items, prices)])
|
||||
await ctx.send(f"Items available in {shop_name}:\n{item_list}")
|
||||
else:
|
||||
await ctx.send(f"No items available in {shop_name}.")
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
# ------------------------- Manager Base Commands -----------------------------------------
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
@manager.command(name="create", alias=["cr"])
|
||||
async def create_shop(self, ctx, shop_name: str, *, description: str):
|
||||
await ctx.send(f"Shop '{shop_name}' created!")
|
||||
|
||||
@manager.command(name="delete", alias=["del"])
|
||||
async def delete_shop(self, ctx, shop_name: str):
|
||||
await ctx.send(f"Shop '{shop_name}' deleted!")
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
# --------------------------- SET BASE GROUP ----------------------------------------------
|
||||
# -----------------------------------------------------------------------------------------
|
||||
# --------------------------- STATUS BASE COMMANDS ----------------------------------------
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
@status.command(name="active", alias=["act"])
|
||||
async def active_shop(self, ctx, shop_name: str):
|
||||
await ctx.send(f"Shop '{shop_name}' activated!")
|
||||
|
||||
@status.command(name="deactive", alias=["deact"])
|
||||
async def deactive_shop(self, ctx, shop_name: str):
|
||||
await ctx.send(f"Shop '{shop_name}' deactivated!")
|
||||
|
||||
# -----------------------------------------------------------------------------------------
|
||||
# --------------------------- CURRENCY BASE COMMANDS --------------------------------------
|
||||
# -----------------------------------------------------------------------------------------
|
||||
|
||||
@currency.command(name="name", alias=["nm"]) # Use @currency.command
|
||||
async def name_currency(self, ctx, currency_name: str):
|
||||
await ctx.send(f"Currency name set to '{currency_name}'!")
|
||||
|
||||
@currency.command(name="symbol", alias=["sym"]) # Use @currency.command
|
||||
async def symbol_currency(self, ctx, currency_symbol: str):
|
||||
await ctx.send(f"Currency symbol set to '{currency_symbol}'!")
|
||||
|
||||
@currency.command(name="emoji", alias=["emo"]) # Use @currency.command
|
||||
async def emoji_currency(self, ctx, currency_emoji: str):
|
||||
await ctx.send(f"Currency emoji set to '{currency_emoji}'!")
|
||||
0
servicereview/README.md
Normal file
0
servicereview/README.md
Normal file
0
servicereview/__init__.py
Normal file
0
servicereview/__init__.py
Normal file
15
servicereview/info.json
Normal file
15
servicereview/info.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"author": [ "unstableCogs" ],
|
||||
"install_msg": "Thank you for installing the Service Review cog!",
|
||||
"name": "ServiceReview",
|
||||
"short": "A command for users to leave a service review.",
|
||||
"description": "Allows users to submit a service review at any time, independently of any ticket system. Submissions are sent to a designated channel for staff.",
|
||||
"tags": [
|
||||
"review",
|
||||
"service",
|
||||
"feedback",
|
||||
"utility"
|
||||
],
|
||||
"requirements": [ "rich" ],
|
||||
"end_user_data_statement": "This cog persistently stores the user's ID and the content of their submitted review for record-keeping purposes."
|
||||
}
|
||||
0
servicereview/iservice.py
Normal file
0
servicereview/iservice.py
Normal file
82
welcomer/README.md
Normal file
82
welcomer/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Welcomer Cog - ver-1.0.0
|
||||
A configurable cog to automatically welcome new users when they join your server.
|
||||
|
||||
# Features
|
||||
- Fully Configurable: Set a custom welcome message and channel for each server.
|
||||
- Enable/Disable: Easily toggle the welcomer on or off without losing your settings.
|
||||
- Placeholder Support: Personalize your welcome message with user and server details.
|
||||
- Easy Setup: A simple command group for admins to manage all settings.
|
||||
- Permissions: All settings commands require `Manage Server` permissions to use.
|
||||
|
||||
# Commands
|
||||
All configuration is handled through the `[p]welcomeset` command group.
|
||||
|
||||
[p]welcomeset channel <#channel>
|
||||
Sets the channel where welcome messages will be sent.
|
||||
|
||||
Alias: chnl
|
||||
|
||||
Example: [p]welcomeset channel #welcome
|
||||
|
||||
[p]welcomeset message <message>
|
||||
Sets the custom welcome message. See the "Placeholders" section below for available variables.
|
||||
|
||||
Alias: msg
|
||||
|
||||
Example: [p]welcomeset message Welcome {user.mention} to {server_name}! We're glad you're here.
|
||||
|
||||
[p]welcomeset toggle
|
||||
Toggles the welcomer system on or off for the server.
|
||||
|
||||
Aliases: on, off
|
||||
|
||||
Example: [p]welcomeset toggle
|
||||
|
||||
[p]welcomeset settings
|
||||
Displays the current settings for the welcomer in an embed.
|
||||
|
||||
Aliases: show, status
|
||||
|
||||
Example: [p]welcomeset settings
|
||||
|
||||
[p]welcomeset test
|
||||
Sends a preview of the current welcome message to the channel where the command is run.
|
||||
|
||||
Example: [p]welcomeset test
|
||||
|
||||
[p]welcomeset reset
|
||||
Resets all welcomer settings for the server to their default values.
|
||||
|
||||
Example: [p]welcomeset reset
|
||||
|
||||
Quick Setup Guide
|
||||
Load the Cog:
|
||||
|
||||
[p]load welcomer
|
||||
|
||||
Set the Welcome Channel:
|
||||
|
||||
[p]welcomeset channel #your-welcome-channel
|
||||
|
||||
Set Your Custom Message:
|
||||
|
||||
[p]welcomeset message Welcome, {user.mention}! Enjoy your stay in {server_name}!
|
||||
|
||||
Enable the System:
|
||||
|
||||
[p]welcomeset toggle
|
||||
|
||||
The bot will now welcome new members in the channel you specified. You can use [p]welcomeset settings at any time to check your configuration.
|
||||
|
||||
Placeholders for the Welcome Message
|
||||
You can use the following placeholders in your custom welcome message. They will be automatically replaced with the correct information when a new user joins.
|
||||
|
||||
{user}: The user object itself.
|
||||
|
||||
{user.mention}: Pings the new user (e.g., @UnstableKitsune).
|
||||
|
||||
{user_name}: The new user's name (e.g., UnstableKitsune).
|
||||
|
||||
{server_name}: The name of the server they joined.
|
||||
|
||||
For full documentation, please visit the [repository wiki](https://git.kitsunic.org/kitsunicWorks/unstable-cogs/wiki).
|
||||
1
welcomer/__init__.py
Normal file
1
welcomer/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .welcomer import setup
|
||||
14
welcomer/info.json
Normal file
14
welcomer/info.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"author": [ "unstableCogs" ],
|
||||
"install_msg": "Thank you for installing the Welcomer cog!",
|
||||
"name": "Welcomer",
|
||||
"short": "A simple cog to welcome new users.",
|
||||
"description": "Greets new members in a designated channel with a customizable message. This cog is part of the Unified Bot Suite.",
|
||||
"tags": [
|
||||
"welcome",
|
||||
"utility",
|
||||
"greeting"
|
||||
],
|
||||
"requirements": [],
|
||||
"end_user_data_statement": "This cog does not persistently store any end user data."
|
||||
}
|
||||
195
welcomer/welcomer.py
Normal file
195
welcomer/welcomer.py
Normal file
@@ -0,0 +1,195 @@
|
||||
# welcomer.py
|
||||
|
||||
import discord
|
||||
from redbot.core import commands, Config
|
||||
from redbot.core.bot import Red
|
||||
|
||||
class Welcomer(commands.Cog):
|
||||
"""
|
||||
A configurable cog to welcome new users to a server.
|
||||
"""
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
self.bot = bot
|
||||
# Initialize Red's Config system. This will store our settings.
|
||||
# The "GUILD" identifier means settings will be saved on a per-server basis.
|
||||
self.config = Config.get_conf(self, identifier=1234567890, force_registration=True)
|
||||
|
||||
# Define the default settings for each server.
|
||||
default_guild = {
|
||||
"welcome_channel": None, # The ID of the channel to send welcomes to
|
||||
"welcome_message": "Welcome to the server, {user.mention}!", # Default message
|
||||
"enabled": False # Whether the system is on or off
|
||||
}
|
||||
|
||||
# Register the default settings.
|
||||
self.config.register_guild(**default_guild)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_member_join(self, member: discord.Member):
|
||||
"""
|
||||
The event listener that runs when a new member joins.
|
||||
"""
|
||||
guild = member.guild
|
||||
|
||||
# Check if the welcomer is enabled for this server.
|
||||
if not await self.config.guild(guild).enabled():
|
||||
return
|
||||
|
||||
# Get the welcome channel ID from our saved settings.
|
||||
channel_id = await self.config.guild(guild).welcome_channel()
|
||||
if not channel_id:
|
||||
return # If no channel is set, do nothing.
|
||||
|
||||
# Try to find the channel object in the server.
|
||||
channel = guild.get_channel(channel_id)
|
||||
if not channel:
|
||||
# The channel might have been deleted.
|
||||
return
|
||||
|
||||
# Explicitly check if the channel is a TextChannel before trying to send a message.
|
||||
if not isinstance(channel, discord.TextChannel):
|
||||
print(f"Welcomer: Configured channel '{channel.name}' in {guild.name} is not a text channel.")
|
||||
return
|
||||
|
||||
# Get the custom welcome message from our settings.
|
||||
message_template = await self.config.guild(guild).welcome_message()
|
||||
|
||||
# Format the message with the new member's info.
|
||||
# .format() is a safe way to replace placeholders.
|
||||
formatted_message = message_template.format(
|
||||
user=member,
|
||||
user_mention=member.mention,
|
||||
user_name=member.name,
|
||||
server_name=guild.name
|
||||
)
|
||||
|
||||
# Check if we have permission to send messages in the channel.
|
||||
if not channel.permissions_for(guild.me).send_messages:
|
||||
# We can't send a message, so we'll just log this internally.
|
||||
print(f"Welcomer: No permission to send messages in {channel.name} in {guild.name}.")
|
||||
return
|
||||
|
||||
try:
|
||||
await channel.send(formatted_message)
|
||||
except discord.Forbidden:
|
||||
# This is a final safety check in case permissions change suddenly.
|
||||
pass
|
||||
except discord.HTTPException as e:
|
||||
# This can happen if the message is too long or there's a Discord API error.
|
||||
print(f"Welcomer: Failed to send welcome message in {guild.name}: {e}")
|
||||
|
||||
# Create a command group for all our settings commands.
|
||||
@commands.group(aliases=["wset"]) # type: ignore
|
||||
@commands.guild_only() # Ensures this command and its subcommands can only be run in a server
|
||||
@commands.admin_or_permissions(manage_guild=True)
|
||||
async def welcomeset(self, ctx: commands.Context):
|
||||
"""
|
||||
Configure the welcomer settings for this server.
|
||||
"""
|
||||
pass
|
||||
|
||||
@welcomeset.command(name="channel", aliases=["chnl"])
|
||||
async def welcomeset_channel(self, ctx: commands.Context, channel: discord.TextChannel):
|
||||
"""
|
||||
Set the channel where welcome messages will be sent.
|
||||
|
||||
Example:
|
||||
[p]welcomeset channel #welcome
|
||||
"""
|
||||
if not ctx.guild:
|
||||
return # This check satisfies the type checker
|
||||
await self.config.guild(ctx.guild).welcome_channel.set(channel.id)
|
||||
await ctx.send(f"The welcome channel has been set to {channel.mention}.")
|
||||
|
||||
@welcomeset.command(name="message", aliases=["msg"])
|
||||
async def welcomeset_message(self, ctx: commands.Context, *, message: str):
|
||||
"""
|
||||
Set the custom welcome message.
|
||||
|
||||
You can use these placeholders:
|
||||
{user} - The user object.
|
||||
{user_mention} - Pings the user.
|
||||
{user_name} - The user's name.
|
||||
{server_name} - The name of this server.
|
||||
|
||||
Example:
|
||||
[p]welcomeset message Hello {user_mention}, welcome to {server_name}!
|
||||
"""
|
||||
if not ctx.guild:
|
||||
return
|
||||
await self.config.guild(ctx.guild).welcome_message.set(message)
|
||||
await ctx.send(f"The welcome message has been updated.")
|
||||
# Send a preview of the new message.
|
||||
preview = message.format(
|
||||
user=ctx.author,
|
||||
user_mention=ctx.author.mention,
|
||||
user_name=ctx.author.name,
|
||||
server_name=ctx.guild.name
|
||||
)
|
||||
await ctx.send(f"**Preview:**\n{preview}")
|
||||
|
||||
@welcomeset.command(name="toggle", aliases=["on", "off"])
|
||||
async def welcomeset_toggle(self, ctx: commands.Context):
|
||||
"""
|
||||
Enable or disable the welcomer system on this server.
|
||||
"""
|
||||
if not ctx.guild:
|
||||
return
|
||||
current_status = await self.config.guild(ctx.guild).enabled()
|
||||
new_status = not current_status
|
||||
await self.config.guild(ctx.guild).enabled.set(new_status)
|
||||
status_text = "enabled" if new_status else "disabled"
|
||||
await ctx.send(f"The welcomer system has been {status_text}.")
|
||||
|
||||
@welcomeset.command(name="settings", aliases=["show", "status"])
|
||||
async def welcomeset_settings(self, ctx: commands.Context):
|
||||
"""
|
||||
Show the current welcomer settings for this server.
|
||||
"""
|
||||
if not ctx.guild:
|
||||
return
|
||||
settings = await self.config.guild(ctx.guild).all()
|
||||
channel_id = settings['welcome_channel']
|
||||
channel = ctx.guild.get_channel(channel_id) if channel_id else None
|
||||
message = settings['welcome_message']
|
||||
enabled = "Enabled" if settings['enabled'] else "Disabled"
|
||||
|
||||
embed = discord.Embed(title="Welcomer Settings", color=await ctx.embed_color())
|
||||
embed.add_field(name="Status", value=enabled, inline=False)
|
||||
embed.add_field(name="Channel", value=channel.mention if isinstance(channel, discord.TextChannel) else "Not Set", inline=False)
|
||||
embed.add_field(name="Message", value=f"```{message}```", inline=False)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
@welcomeset.command(name="test")
|
||||
async def welcomeset_test(self, ctx: commands.Context):
|
||||
"""
|
||||
Test the welcome message by sending a preview to this channel.
|
||||
"""
|
||||
if not ctx.guild:
|
||||
return
|
||||
message_template = await self.config.guild(ctx.guild).welcome_message()
|
||||
preview = message_template.format(
|
||||
user=ctx.author,
|
||||
user_mention=ctx.author.mention,
|
||||
user_name=ctx.author.name,
|
||||
server_name=ctx.guild.name
|
||||
)
|
||||
await ctx.send(f"**Welcome Message Preview**:\n{preview}")
|
||||
|
||||
@welcomeset.command(name="reset")
|
||||
async def welcomeset_reset(self, ctx: commands.Context):
|
||||
"""
|
||||
Reset all welcomer settings to their defaults.
|
||||
"""
|
||||
if not ctx.guild:
|
||||
return
|
||||
await self.config.guild(ctx.guild).clear()
|
||||
await ctx.send("The welcomer settings have been reset to their defaults.")
|
||||
|
||||
|
||||
# This function allows Red to load the cog.
|
||||
# It is required in every cog file.
|
||||
async def setup(bot: Red):
|
||||
await bot.add_cog(Welcomer(bot))
|
||||
Reference in New Issue
Block a user