5 Commits

Author SHA1 Message Date
3955e61a62 Merge pull request 'iservice' (#3) from iservice into main
Reviewed-on: #3
2025-09-11 17:29:20 -04:00
a3e210a7ce Merge pull request 'feat: add initial files for KofiShop cog' (#2) from kofishop into main
Reviewed-on: #2
2025-09-11 17:29:02 -04:00
5fd4e08d90 Merge pull request 'kbump' (#1) from kbump into main
Reviewed-on: #1
2025-09-11 17:28:31 -04:00
eec57c7e23 feat: adds ServiceReview cog
Introduces functionality for users to submit independent service reviews, sent to a designated staff channel.

Includes metadata and initial structure files.
2025-09-11 17:24:31 -04:00
e0958f6f2a feat: Add RPG cog with character and inventory features
Sets up RPG system as a Redbot cog with core mechanics
Implements character creation, class selection, and stat management
Adds action commands for attacking and healing
Provides inventory and shop functionality for item management
Introduces interactive UI menus for user engagement
2025-09-11 17:23:45 -04:00
13 changed files with 771 additions and 0 deletions

6
rpg/__init__.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

View File

15
servicereview/info.json Normal file
View 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."
}

View File