Bunch of new features and bug fixes

This commit is contained in:
Brandon 2021-11-16 03:54:04 -05:00
parent a63aa3661e
commit e62583c645
21 changed files with 2230 additions and 109 deletions

View File

@ -60,6 +60,16 @@ Twitter style following system that is guild-agnostic. Allows users to follow so
- Works from DMs as well as in guilds, use user and channel IDs for easiest use.
### Image Magic
Image transformation commands to create funny or interesting photos.
**Features**
- Barrel effect
- Implode effect
- Zoom
- Black and white
- Sketch transformation
#### Isolate
Carbon copy of punish cog, except this one will remove all roles from a user and by default sets permissions so they cannot see or talk in any channel except the channel set for isolation.
@ -68,6 +78,10 @@ Carbon copy of punish cog, except this one will remove all roles from a user and
Based off of [Malarne's](https://github.com/Malarne/discord_cogs) cog. Has some bug fixes and reduces the starting EXP by 50. Also cleaned up the code a bit, and have features planned.
### Markov
Markov chains! This cog builds markov chain models per channel and optionally per user, allowing for funny and interesting text generation!
#### MoreAdmin
More admin commands that provide various functionality.
**Features**:
@ -102,6 +116,7 @@ Modified from Fixator10, added functionality of automatically creating/deleting
- Users can customize their role name and color through the bot.
- Blacklist words that aren't allowed in role names.
- Automatically create/manage personal roles.
- Users can add and remove role icons if the guild has the feature
#### Pony
Search derpibooru for pony images. Ported from [Alzarath](https://github.com/Alzarath/Booru-Cogs).
@ -189,11 +204,17 @@ Modifed from @saurichable. Adds a few features we needed.
- Listing the final vote count when a suggestion is approved or denied
- Added reasons for approved suggestions, since sometimes we accept a suggestion but we may modify it
- Approved reasons are marked green, denied are marked red
- Optionally create threads for each suggestion for easier discussion
#### Smart React
Auto react to messages based on keywords. Based off of [FlapJack's](https://github.com/flapjax/FlapJack-Cogs/) cog. Minor bug fixes and planned features, like using regex to parse messages.
### Thread Manager
A simple thread manager that allows guild staff to set certain roles to create a customable number of threads per channel. Manual archive by users is not supported right now.
#### Warnings Custom
Adds a few features that are needed for my server, modified from the built in warning cog.
**Added Features**

View File

@ -1,6 +1,6 @@
# redbot/discord
from redbot.core.utils.chat_formatting import *
from redbot.core import Config, checks, commands, modlog
from redbot.core import Config, checks, commands, modlog, bank
from redbot.core.data_manager import cog_data_path
from redbot.core.utils.mod import is_mod_or_superior
import discord
@ -88,6 +88,20 @@ class ActivityLogger(commands.Cog):
self.cache = {}
# remove userinfo since we are replacing it
self.badge_emojis = {
"staff": 848556248832016384,
"early_supporter": 706198530837970998,
"hypesquad_balance": 706198531538550886,
"hypesquad_bravery": 706198532998299779,
"hypesquad_brilliance": 706198535846101092,
"hypesquad": 706198537049866261,
"verified_bot_developer": 706198727953612901,
"bug_hunter": 848556247632052225,
"bug_hunter_level_2": 706199712402898985,
"partner": 848556249192202247,
"verified_bot": 848561838974697532,
"verified_bot2": 848561839260434482,
}
self.bot.remove_command("userinfo")
self.load_task = asyncio.create_task(self.initialize())
@ -142,91 +156,139 @@ class ActivityLogger(commands.Cog):
author = ctx.author
guild = ctx.guild
is_mod = await is_mod_or_superior(self.bot, author)
if not user or not is_mod:
user = author
if is_mod:
roles = [x for x in user.roles if x.name != "@everyone"]
else:
roles = [x.name for x in sorted(user.roles, reverse=True) if x.name != "@everyone"]
async with ctx.typing():
if is_mod:
roles = [x for x in user.roles if x.name != "@everyone"]
else:
roles = [x.name for x in sorted(user.roles, reverse=True) if x.name != "@everyone"]
joined_at = user.joined_at
since_created = (ctx.message.created_at - user.created_at).days
if joined_at is not None:
since_joined = (ctx.message.created_at - joined_at).days
user_joined = joined_at.strftime("%b %d, %Y %H:%M UTC")
else:
since_joined = "?"
user_joined = "Unknown"
user_created = user.created_at.strftime("%b %d, %Y %H:%M UTC")
member_number = sorted(guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user) + 1
joined_at = user.joined_at
since_created = (ctx.message.created_at - user.created_at).days
if joined_at is not None:
since_joined = (ctx.message.created_at - joined_at).days
user_joined = f"<t:{int(joined_at.timestamp())}>"
else:
since_joined = "?"
user_joined = "Unknown"
created_on = "{}\n({} days ago)".format(user_created, since_created)
joined_on = "{}\n({} days ago)".format(user_joined, since_joined)
user_created = f"<t:{int(user.created_at.timestamp())}>"
member_number = sorted(guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user) + 1
game = "Chilling in {} status".format(user.status)
created_on = "{}\n({} days ago)".format(user_created, since_created)
joined_on = "{}\n({} days ago)".format(user_joined, since_joined)
if user.activity is None: # Default status
activity = None
elif user.activity.type == discord.ActivityType.playing:
activity = "Playing {}".format(user.activity.name)
elif user.activity.type == discord.ActivityType.streaming:
activity = "Streaming [{}]({})".format(user.activity.name, user.activity.url)
elif user.activity.type == discord.ActivityType.listening:
activity = "Listening to {}".format(user.activity.name)
elif user.activity.type == discord.ActivityType.watching:
activity = "Watching {}".format(user.activity.name)
else:
activity = None
if user.is_on_mobile():
statusemoji = "\N{MOBILE PHONE}"
elif any(a.type is discord.ActivityType.streaming for a in user.activities):
statusemoji = "\N{LARGE PURPLE CIRCLE}"
elif user.status.name == "online":
statusemoji = "\N{LARGE GREEN CIRCLE}"
elif user.status.name == "offline":
statusemoji = "\N{MEDIUM WHITE CIRCLE}"
elif user.status.name == "dnd":
statusemoji = "\N{LARGE RED CIRCLE}"
elif user.status.name == "idle":
statusemoji = "\N{LARGE ORANGE CIRCLE}"
else:
statusemoji = "\N{MEDIUM BLACK CIRCLE}\N{VARIATION SELECTOR-16}"
if roles and is_mod:
roles = " ".join([x.mention for x in sorted(roles, reverse=True)])
elif roles:
roles = ", ".join(roles)
else:
roles = "None"
if user.activity is None: # Default status
activity = "No Status"
elif user.activity.type == discord.ActivityType.playing:
activity = "Playing {}".format(user.activity.name)
elif user.activity.type == discord.ActivityType.streaming:
activity = "Streaming [{}]({})".format(user.activity.name, user.activity.url)
elif user.activity.type == discord.ActivityType.listening:
activity = "Listening to {}".format(user.activity.name)
elif user.activity.type == discord.ActivityType.watching:
activity = "Watching {}".format(user.activity.name)
else:
activity = "No Status"
if user.id != self.bot.user.id:
stats, names = await self.userstats(guild, user)
else:
stats = "Stats are unavailable for this account."
names = None
if roles and is_mod:
roles = " ".join([x.mention for x in sorted(roles, reverse=True)])
elif roles:
roles = ", ".join(roles)
else:
roles = "None"
title = guild.name if not is_mod else None
if user.id != self.bot.user.id:
stats, names = await self.userstats(guild, user)
if is_mod:
# also add notes
moreadmin = self.bot.get_cog("MoreAdmin")
if moreadmin:
num_notes = len(await moreadmin.config.member(user).notes())
stats += f", Notes: `{num_notes}`"
else:
stats = "Stats are unavailable for this account."
names = None
data = discord.Embed(title=title, description=activity, colour=user.colour)
data.add_field(name="Joined Discord on", value=created_on)
data.add_field(name="Joined this server on", value=joined_on)
data.add_field(name="Roles", value=roles, inline=False)
data.add_field(name="Stats", value=stats)
if names:
names = pagify(names, page_length=1000)
for name in names:
data.add_field(name="Also known as:", value=name, inline=False)
data.set_footer(text="Member #{} | User ID:{}" "".format(member_number, user.id))
title = guild.name
name = str(user)
name = " ~ ".join((name, user.nick)) if user.nick else name
data = discord.Embed(title=title, description=f"{statusemoji} {activity}", colour=user.colour)
data.add_field(name="Joined Discord on", value=created_on)
data.add_field(name="Joined this server on", value=joined_on)
data.add_field(name="Roles", value=roles, inline=False)
data.add_field(name="Stats", value=stats)
if names:
names = pagify(names, page_length=1000)
for name in names:
data.add_field(name="Also known as:", value=name, inline=False)
data.set_footer(text="Member #{} | User ID: {}" "".format(member_number, user.id))
if user.avatar:
avatar = user.avatar_url_as(static_format="png")
data.set_author(name=name, url=avatar)
data.set_thumbnail(url=avatar)
else:
data.set_author(name=name)
name = str(user)
name = " ~ ".join((name, user.nick)) if user.nick else name
if is_mod:
try:
await ctx.send(embed=data, allowed_mentions=discord.AllowedMentions.all())
except discord.HTTPException:
await ctx.send("I need the `Embed links` permission to send this")
else:
try:
await author.send(embed=data)
except discord.HTTPException:
await ctx.send("Please allow messages from server members to get your info.")
except Exception as e:
print(f"Error in userinfo: {e}")
if user.avatar:
avatar = user.avatar_url_as(static_format="png")
data.set_author(name=name, url=avatar)
data.set_thumbnail(url=avatar)
else:
data.set_author(name=name)
flags = [f.name for f in user.public_flags.all()]
badges = ""
badge_count = 0
if flags:
for badge in sorted(flags):
if badge == "verified_bot":
emoji1 = self.badge_emojis["verified_bot"]
emoji2 = self.badge_emojis["verified_bot2"]
if emoji1:
emoji = f"{emoji1}{emoji2}"
else:
emoji = None
else:
emoji = self.badge_emojis[badge]
if emoji:
badges += f"{emoji} {badge.replace('_', ' ').title()}\n"
else:
badges += f"\N{BLACK QUESTION MARK ORNAMENT}\N{VARIATION SELECTOR-16} {badge.replace('_', ' ').title()}\n"
badge_count += 1
if badges:
data.add_field(name="Badges" if badge_count > 1 else "Badge", value=badges)
if "Economy" in self.bot.cogs:
balance_count = 1
bankstat = f"**Bank**: {str(humanize_number(await bank.get_balance(user)))} {await bank.get_currency_name(ctx.guild)}\n"
data.add_field(name="Balance", value=bankstat)
if is_mod:
try:
await ctx.send(embed=data, allowed_mentions=discord.AllowedMentions.all())
except discord.HTTPException:
await ctx.send("I need the `Embed links` permission to send this")
else:
try:
await author.send(embed=data)
except discord.HTTPException:
await ctx.send("Please allow messages from server members to get your info.")
except Exception as e:
print(f"Error in userinfo: {e}")
async def userstats(self, guild, user):
"""

View File

@ -60,7 +60,7 @@ class Birthday(commands.Cog):
await self.check_bdays()
await asyncio.sleep((tomorrow - now).total_seconds())
### TESTING:
# await asyncio.sleep(30)
# await asyncio.sleep(5)
async def check_bdays(self):
for guild in self.bot.guilds:
@ -101,7 +101,8 @@ class Birthday(commands.Cog):
embed.description = f"Happy Birthday to {member.mention}!"
# embed.set_footer("Add your birthday using the `bday` command!")
try:
await channel.send(embed=embed, allowed_mentions=discord.AllowedMentions.all())
content = f"Congratulations {member.mention}!"
await channel.send(content=content, embed=embed, allowed_mentions=discord.AllowedMentions.all())
except:
pass
@ -130,7 +131,8 @@ class Birthday(commands.Cog):
# @commands.command()
# async def test(self, ctx, *, member: discord.Member):
# await self.check_bdays()
# await self.check_bdays()
# await self.check_member_bday(member)
@commands.group(name="bdayset")
@commands.guild_only()
@ -246,30 +248,32 @@ class Birthday(commands.Cog):
@bday.command(name="list")
async def bday_list(self, ctx):
"""List birthdays in the server"""
embeds = []
msg = ""
for member in ctx.guild.members:
bday = await self.config.member(member).birthday()
if bday:
embed = discord.Embed(title=f"{member.display_name}", colour=ctx.guild.me.colour)
bday_datetime = self.parse_date(bday)
bday, age = self.get_date_and_age(bday_datetime)
embed.add_field(name="Birthday", value=bday)
msg += f"{member.display_name}: {bday}"
if age:
now = datetime.datetime.utcnow()
bday_datetime = bday_datetime.replace(year=now.year)
if now > bday_datetime:
embed.add_field(name="Turned", value=age)
msg += f", Turned {age}\n"
else:
embed.add_field(name="Turning", value=age)
embeds.append(embed)
msg += f", Turning {age}\n"
else:
msg += "\n"
for i, embed in enumerate(embeds):
embed.set_footer(text=f"Page {i+1} of {len(embeds)}")
pages = []
raw = list(pagify(msg, page_length=1700, delims=["\n"], priority=True))
for i, page in enumerate(raw):
pages.append(box(f"{page}-----------------\nPage {i+1} of {len(raw)}"))
if not embeds:
if not pages:
await ctx.send("No one has their birthday set in your server!")
else:
await menu(ctx, embeds, DEFAULT_CONTROLS)
await menu(ctx, pages, DEFAULT_CONTROLS)
async def red_delete_data_for_user(
self,

View File

@ -13,9 +13,12 @@ class Markov(commands.Cog):
self.bot = bot
self.config = Config.get_conf(self, identifier=5989735216541313, force_registration=True)
default_guild = {"model": {}, "prefixes": [], "max_len": 200}
default_guild = {"model": {}, "prefixes": [], "max_len": 200, "member_model": False}
default_member = {"model": {}}
self.config.register_guild(**default_guild)
self.config.register_member(**default_member)
self.cache = {}
self.mem_cache = {}
self.init_task = asyncio.create_task(self.init())
def cog_unload(self):
@ -23,6 +26,8 @@ class Markov(commands.Cog):
# save all the models before full unload/shutdown
for guild in self.bot.guilds:
asyncio.create_task(self.config.guild(guild).model.set(self.cache[guild.id]["model"]))
for member in guild.members:
asyncio.create_task(self.config.member(member).model.set(self.mem_cache[member.id]["model"]))
async def init(self):
await self.bot.wait_until_ready()
@ -30,11 +35,17 @@ class Markov(commands.Cog):
# slows down once file gets big otherwise
for guild in self.bot.guilds:
self.cache[guild.id] = await self.config.guild(guild).all()
if self.cache[guild.id]["member_model"]:
for member in guild.members:
self.mem_cache[member.id] = await self.config.member(member).all()
while True: # save model every 5 minutes
await asyncio.sleep(300)
for guild in self.bot.guilds:
await self.config.guild(guild).model.set(self.cache[guild.id]["model"])
if self.cache[guild.id]["member_model"]:
for member in guild.members:
await self.config.member(member).model.set(self.mem_cache[member.id]["model"])
@commands.group()
@checks.admin_or_permissions(administrator=True)
@ -43,6 +54,22 @@ class Markov(commands.Cog):
"""Manage Markov Settings"""
pass
@markovset.command(name="mem-model")
async def memmodel(self, ctx, toggle: bool = None):
"""
Enable/disable models for each guild member
**WARNING**: this can eat up a ton of RAM and space, use at your own risk!
"""
if toggle is None:
disabled = "enabled" if self.cache[ctx.guild.id]["member_model"] else "disabled"
await ctx.send(f"Member models are currently {disabled}.")
return
self.cache[ctx.guild.id]["member_model"] = toggle
await self.config.guild(ctx.guild).member_model.set(toggle)
await ctx.tick()
@markovset.command(name="clear")
async def markovset_clear(self, ctx, *, channel: discord.TextChannel):
"""Clear data for a specific channel"""
@ -101,17 +128,66 @@ class Markov(commands.Cog):
@commands.guild_only()
@commands.cooldown(rate=1, per=10, type=commands.BucketType.user)
@checks.bot_has_permissions(embed_links=True)
async def markov(self, ctx, *, starting_text: str = None):
async def markov(
self,
ctx,
num_text: Union[int, discord.Member, str] = None,
member: Union[discord.Member, str] = None,
*,
starting_text: str = None,
):
"""Generate text using markov chains!
Text generated is based on what users say in the current channel
You can generate a certain number of words using the num_text option, if num_text is not a number it is added to the starting text
"""
model = self.cache[ctx.guild.id]["model"]
try:
model = model[str(ctx.channel.id)]
except KeyError:
await ctx.send(error("This channel has no data, try talking in it for a bit first!"))
member_model = self.cache[ctx.guild.id]["member_model"]
if member_model:
if isinstance(member, discord.Member):
if member.id not in self.mem_cache:
self.mem_cache[member.id] = {}
self.mem_cache[member.id]["model"] = {}
model = self.mem_cache[member.id]["model"]
elif isinstance(num_text, discord.Member):
member = num_text
num_text = None
if member.id not in self.mem_cache:
self.mem_cache[member.id] = {}
self.mem_cache[member.id]["model"] = {}
model = self.mem_cache[member.id]["model"]
else:
member_model = False
model = self.cache[ctx.guild.id]["model"]
else:
member_model = False
model = self.cache[ctx.guild.id]["model"]
if member_model and not model:
await ctx.send(error("This member has no data."), delete_after=30)
return
elif not member_model and str(ctx.channel.id) not in model:
await ctx.send(error("This channel has no data."), delete_after=30)
return
if not member_model:
model = model[str(ctx.channel.id)]
# if num_text is valid
try:
num = int(num_text)
except:
num = None
add_num = num is None and num_text is not None
add_mem = not member_model and member is not None
if add_num and add_mem:
starting_text = f"{num_text} {member} {starting_text if starting_text is not None else ''}"
elif add_num:
starting_text = f"{num_text} {starting_text if starting_text is not None else ''}"
elif add_mem:
starting_text = f"{member} {starting_text if starting_text is not None else ''}"
starting_text = starting_text.split(" ") if starting_text else None
last_word = starting_text[-1] if starting_text else None
@ -128,7 +204,11 @@ class Markov(commands.Cog):
tries = 0
max_tries = 20
num_chars = len(" ".join(markov_text))
while num_chars < max_len and tries < max_tries:
num_words = len(markov_text)
if num is None:
num = 1e6 # i know its trashy but it makes things easier
while num_chars < max_len and tries < max_tries and num_words < num:
if "?" in markov_text[-1]:
break
if "\r" in markov_text[-1]:
@ -149,6 +229,8 @@ class Markov(commands.Cog):
markov_text.append(choice)
tries += 1
num_words += 1
markov_text = " ".join(markov_text)
if num_chars > max_len:
markov_text = markov_text[:max_len]
@ -167,6 +249,7 @@ class Markov(commands.Cog):
async def on_message(self, message):
if await self.bot.cog_disabled_in_guild(self, message.guild):
return
# updates model
content = message.content
guild = message.guild
@ -182,6 +265,12 @@ class Markov(commands.Cog):
content = content.split(" ")
model = self.cache[guild.id]["model"]
if self.cache[guild.id]["member_model"]:
if message.author.id not in self.mem_cache:
self.mem_cache[message.author.id] = {}
self.mem_cache[message.author.id]["model"] = {}
mem_model = self.mem_cache[message.author.id]["model"]
try:
model[str(message.channel.id)]
except KeyError:
@ -190,10 +279,15 @@ class Markov(commands.Cog):
for i in range(len(content) - 1):
if content[i] not in model[str(message.channel.id)]:
model[str(message.channel.id)][content[i]] = list()
if self.cache[guild.id]["member_model"]:
if content[i] not in mem_model:
mem_model[content[i]] = list()
mem_model[content[i]].append(content[i + 1])
model[str(message.channel.id)][content[i]].append(content[i + 1])
self.cache[guild.id]["model"] = model
self.mem_cache[message.author.id]["model"] = mem_model
async def red_delete_data_for_user(
self,

View File

@ -937,13 +937,14 @@ class MoreAdmin(commands.Cog):
try:
message = await channel.fetch_message(message_id)
except:
await ctx.send("Sorry, that message could not be found.")
await ctx.send("Sorry, that message could not be found.", delete_after=30)
return
try:
await message.edit(content=msg, allowed_mentions=discord.AllowedMentions.all())
await ctx.tick()
except:
await ctx.send("Could not edit message.")
await ctx.send("Could not edit message.", delete_after=30)
@commands.command()
@commands.guild_only()
@ -954,8 +955,35 @@ class MoreAdmin(commands.Cog):
"""
try:
await channel.send(msg, allowed_mentions=discord.AllowedMentions.all())
await ctx.tick()
except:
await ctx.send("Could not send message in that channel.")
await ctx.send("Could not send message in that channel.", delete_after=30)
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(administrator=True)
async def react(self, ctx, channel: discord.TextChannel, message_id: int, emoji: Union[discord.Emoji, str]):
"""
Have the bot react to a message
The bot must be able to access the emoji: i.e in the guild where the emoji is from
"""
try:
message = await channel.fetch_message(message_id)
except:
await ctx.send("Sorry, that message could not be found.", delete_after=30)
return
try:
await message.add_reaction(emoji)
await ctx.tick()
except discord.NotFound:
await ctx.send(f"I could not find the emoji `{emoji}`", delete_after=30)
except discord.Forbidden:
await ctx.send("I do not have permissions to react to that message.", delete_after=30)
except discord.HTTPException:
# assume it couldnt find Emoji
await ctx.send(f"I could not find the emoji `{emoji}`", delete_after=30)
@commands.command()
@commands.guild_only()
@ -977,7 +1005,7 @@ class MoreAdmin(commands.Cog):
filepaths.append(cog_data_path(cog_instance=self) / f"{ctx.author.id}_{a.filename}")
await a.save(filepaths[-1])
else:
await ctx.send("You must provide a Discord attachment.")
await ctx.send("You must provide a Discord attachment.", delete_after=30)
return
files = [discord.File(file) for file in filepaths]
@ -997,7 +1025,7 @@ class MoreAdmin(commands.Cog):
try:
message = await channel.fetch_message(message_id)
except:
await ctx.send("Sorry, that message could not be found.")
await ctx.send("Sorry, that message could not be found.", delete_after=30)
return
if message.content == "":
@ -1018,7 +1046,7 @@ class MoreAdmin(commands.Cog):
try:
message = await channel.fetch_message(message_id)
except:
await ctx.send("Sorry, that message could not be found.")
await ctx.send("Sorry, that message could not be found.", delete_after=30)
return
async for m in channel.history(limit=100, after=message.created_at):

10
planttycoon/__init__.py Normal file
View File

@ -0,0 +1,10 @@
from redbot.core import data_manager
from .planttycoon import PlantTycoon
async def setup(bot):
tycoon = PlantTycoon(bot)
data_manager.bundled_data_path(tycoon)
await tycoon._load_plants_products() # I can access protected members if I want, linter!!
bot.add_cog(tycoon)

View File

@ -0,0 +1,11 @@
{
"badges": {
"Flower Power": {},
"Fruit Brute": {},
"Sporadic": {},
"Odd-pod": {},
"Greenfingers": {},
"Nobel Peas Prize": {},
"Annualsary": {}
}
}

View File

@ -0,0 +1,22 @@
{
"points": {
"buy": 5,
"add_health": 5,
"fertilize": 10,
"pruning": 20,
"pesticide": 25,
"growing": 5,
"damage": 25
},
"timers": {
"degradation": 1,
"completion": 1,
"notification": 5
},
"degradation": {
"base_degradation": 1.5
},
"notification": {
"max_health": 50
}
}

View File

@ -0,0 +1,7 @@
{
"messages": [
"The soil seems dry, maybe you could give your plant some water?",
"Your plant seems a bit droopy. I would give it some fertilizer if I were you.",
"Your plant seems a bit too overgrown. You should probably trim it a bit."
]
}

View File

@ -0,0 +1,666 @@
{
"plants": [
{
"name": "Poppy",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/S4hjyUX.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Dandelion",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/emqnQP2.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Daisy",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/lcFq4AB.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Chrysanthemum",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/5jLtqWL.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Pansy",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/f7TgD1b.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Lavender",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/g3OmOSK.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Lily",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/0hzy7lO.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Petunia",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/rJm8ISv.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Sunflower",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/AzgzQK9.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Daffodil",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/pnCCRsH.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Clover",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/jNTgirw.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Tulip",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/kodIFjE.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Rose",
"article": "a",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/sdTNiOH.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Aster",
"article": "an",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/1tN04Hl.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Aloe Vera",
"article": "an",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/WFAYIpx.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Orchid",
"article": "an",
"time": 3600,
"rarity": "common",
"image": "http://i.imgur.com/IQrQYDC.jpg",
"health": 100,
"degradation": 0.625,
"threshold": 110,
"badge": "Flower Power",
"reward": 600
},
{
"name": "Dragon Fruit Plant",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/pfngpDS.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Mango Tree",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/ybR78Oc.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Lychee Tree",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/w9LkfhX.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Durian Tree",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/jh249fz.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Fig Tree",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/YkhnpEV.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Jack Fruit Tree",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/2D79TlA.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Prickly Pear Plant",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/GrcGAGj.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Pineapple Plant",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/VopYQtr.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Citron Tree",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/zh7Dr23.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Cherimoya Tree",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/H62gQK6.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Mangosteen Tree",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/McNnMqa.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Guava Tree",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/iy8WgPt.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Orange Tree",
"article": "an",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/lwjEJTm.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Apple Tree",
"article": "an",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/QI3UTR3.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Sapodilla Tree",
"article": "a",
"time": 5400,
"rarity": "uncommon",
"image": "http://i.imgur.com/6BvO5Fu.jpg",
"health": 100,
"degradation": 0.75,
"threshold": 110,
"badge": "Fruit Brute",
"reward": 1200
},
{
"name": "Franklin Tree",
"article": "a",
"time": 7200,
"rarity": "rare",
"image": "http://i.imgur.com/hoh17hp.jpg",
"health": 100,
"degradation": 1.5,
"threshold": 110,
"badge": "Sporadic",
"reward": 2400
},
{
"name": "Parrot's Beak",
"article": "a",
"time": 7200,
"rarity": "rare",
"image": "http://i.imgur.com/lhSjfQY.jpg",
"health": 100,
"degradation": 1.5,
"threshold": 110,
"badge": "Sporadic",
"reward": 2400
},
{
"name": "Koki'o",
"article": "a",
"time": 7200,
"rarity": "rare",
"image": "http://i.imgur.com/Dhw9ync.jpg",
"health": 100,
"degradation": 1.5,
"threshold": 110,
"badge": "Sporadic",
"reward": 2400
},
{
"name": "Jade Vine",
"article": "a",
"time": 7200,
"rarity": "rare",
"image": "http://i.imgur.com/h4fJo2R.jpg",
"health": 100,
"degradation": 1.5,
"threshold": 110,
"badge": "Sporadic",
"reward": 2400
},
{
"name": "Venus Fly Trap",
"article": "a",
"time": 7200,
"rarity": "rare",
"image": "http://i.imgur.com/NoSdxXh.jpg",
"health": 100,
"degradation": 1.5,
"threshold": 110,
"badge": "Sporadic",
"reward": 2400
},
{
"name": "Chocolate Cosmos",
"article": "a",
"time": 7200,
"rarity": "rare",
"image": "http://i.imgur.com/4ArSekX.jpg",
"health": 100,
"degradation": 1.5,
"threshold": 110,
"badge": "Sporadic",
"reward": 2400
},
{
"name": "Pizza Plant",
"article": "a",
"time": 9000,
"rarity": "super-rare",
"image": "http://i.imgur.com/ASZXr7C.png",
"health": 100,
"degradation": 2,
"threshold": 110,
"badge": "Odd-pod",
"reward": 3600
},
{
"name": "Piranha Plant",
"article": "a",
"time": 9000,
"rarity": "super-rare",
"image": "http://i.imgur.com/c03i9W7.jpg",
"health": 100,
"degradation": 2,
"threshold": 110,
"badge": "Odd-pod",
"reward": 3600
},
{
"name": "Peashooter",
"article": "a",
"time": 9000,
"rarity": "super-rare",
"image": "https://i.imgur.com/Vo4v2Ry.png",
"health": 100,
"degradation": 2,
"threshold": 110,
"badge": "Odd-pod",
"reward": 3600
},
{
"name": "Eldergleam Tree",
"article": "a",
"time": 10800,
"rarity": "epic",
"image": "https://i.imgur.com/pnZYKZc.jpg",
"health": 100,
"degradation": 2.5,
"threshold": 110,
"badge": "Greenfingers",
"reward": 5400
},
{
"name": "Pikmin",
"article": "a",
"time": 10800,
"rarity": "epic",
"image": "http://i.imgur.com/sizf7hE.png",
"health": 100,
"degradation": 2.5,
"threshold": 110,
"badge": "Greenfingers",
"reward": 5400
},
{
"name": "Flora Colossus",
"article": "a",
"time": 10800,
"rarity": "epic",
"image": "http://i.imgur.com/9f5QzaW.jpg",
"health": 100,
"degradation": 2.5,
"threshold": 110,
"badge": "Greenfingers",
"reward": 5400
},
{
"name": "Plantera Bulb",
"article": "a",
"time": 10800,
"rarity": "epic",
"image": "https://i.imgur.com/ExqLLHO.png",
"health": 100,
"degradation": 2.5,
"threshold": 110,
"badge": "Greenfingers",
"reward": 5400
},
{
"name": "Chorus Tree",
"article": "an",
"time": 10800,
"rarity": "epic",
"image": "https://i.imgur.com/tv2B72j.png",
"health": 100,
"degradation": 2.5,
"threshold": 110,
"badge": "Greenfingers",
"reward": 5400
},
{
"name": "Money Tree",
"article": "a",
"time": 35400,
"rarity": "legendary",
"image": "http://i.imgur.com/MIJQDLL.jpg",
"health": 100,
"degradation": 8,
"threshold": 110,
"badge": "Nobel Peas Prize",
"reward": 10800
},
{
"name": "Truffula Tree",
"article": "a",
"time": 35400,
"rarity": "legendary",
"image": "http://i.imgur.com/cFSmaHH.png",
"health": 100,
"degradation": 8,
"threshold": 110,
"badge": "Nobel Peas Prize",
"reward": 10800
},
{
"name": "Whomping Willow",
"article": "a",
"time": 35400,
"rarity": "legendary",
"image": "http://i.imgur.com/Ibwm2xY.jpg",
"health": 100,
"degradation": 8,
"threshold": 110,
"badge": "Nobel Peas Prize",
"reward": 10800
}
],
"event": {
"January": {
"name": "Tanabata Tree",
"article": "a",
"time": 70800,
"rarity": "event",
"image": "http://i.imgur.com/FD38JJj.jpg",
"health": 100,
"degradation": 9,
"threshold": 110,
"badge": "Annualsary",
"reward": 21600
},
"February": {
"name": "Chocolate Rose",
"article": "a",
"time": 70800,
"rarity": "event",
"image": "http://i.imgur.com/Sqg6pcG.jpg",
"health": 100,
"degradation": 9,
"threshold": 110,
"badge": "Annualsary",
"reward": 21600
},
"March": {
"name": "Shamrock",
"article": "a",
"time": 70800,
"rarity": "event",
"image": "http://i.imgur.com/kVig04M.jpg",
"health": 100,
"degradation": 9,
"threshold": 110,
"badge": "Annualsary",
"reward": 21600
},
"April": {
"name": "Easter Egg Eggplant",
"article": "an",
"time": 70800,
"rarity": "event",
"image": "http://i.imgur.com/5jltGQa.jpg",
"health": 100,
"degradation": 9,
"threshold": 110,
"badge": "Annualsary",
"reward": 21600
},
"October": {
"name": "Jack O' Lantern",
"article": "a",
"time": 70800,
"rarity": "event",
"image": "http://i.imgur.com/efApsxG.jpg",
"health": 100,
"degradation": 9,
"threshold": 110,
"badge": "Annualsary",
"reward": 21600
},
"November": {
"name": "Mayflower",
"article": "a",
"time": 70800,
"rarity": "event",
"image": "http://i.imgur.com/nntNtoL.jpg",
"health": 100,
"degradation": 9,
"threshold": 110,
"badge": "Annualsary",
"reward": 21600
},
"December": {
"name": "Holly",
"article": "a",
"time": 70800,
"rarity": "event",
"image": "http://i.imgur.com/maDLmJC.jpg",
"health": 100,
"degradation": 9,
"threshold": 110,
"badge": "Annualsary",
"reward": 21600
}
}
}

View File

@ -0,0 +1,42 @@
{
"water": {
"cost": 5,
"health": 10,
"damage": 45,
"modifier": 0,
"category": "water",
"uses": 1
},
"manure": {
"cost": 20,
"health": 20,
"damage": 55,
"modifier": -0.035,
"category": "fertilizer",
"uses": 1
},
"vermicompost": {
"cost": 35,
"health": 30,
"damage": 60,
"modifier": -0.5,
"category": "fertilizer",
"uses": 1
},
"nitrates": {
"cost": 70,
"health": 60,
"damage": 75,
"modifier": -0.08,
"category": "fertilizer",
"uses": 1
},
"pruner": {
"cost": 500,
"health": 40,
"damage": 90,
"modifier": -0.065,
"category": "tool",
"uses": 10
}
}

19
planttycoon/info.json Normal file
View File

@ -0,0 +1,19 @@
{
"author": [
"Bobloy",
"SnappyDragon",
"PaddoInWonderland"
],
"min_bot_version": "3.3.0",
"description": "Grow your own plants! Be sure to take care of it. Do `[p]gardening` to get started",
"hidden": false,
"install_msg": "Thank you for installing PlantTycoon. Check out all the commands with `[p]help PlantTycoon`",
"requirements": [],
"short": "Grow your own plants! Do `[p]gardening` to get started.",
"end_user_data_statement": "This cog stores user IDs along with their progress in the PlantTycoon game",
"tags": [
"bobloy",
"games",
"environment"
]
}

779
planttycoon/planttycoon.py Normal file
View File

@ -0,0 +1,779 @@
import asyncio
import collections
import copy
import datetime
import json
import time
from random import choice
from typing import Literal
import discord
from redbot.core import Config, bank, commands
from redbot.core.bot import Red
from redbot.core.data_manager import bundled_data_path
from redbot.core.utils import AsyncIter
class Gardener:
"""Gardener class"""
def __init__(self, user: discord.User, config: Config):
self.user = user
self.config = config
self.badges = []
self.points = 0
self.products = {}
self.current = {}
def __str__(self):
return (
"Gardener named {}\n"
"Badges: {}\n"
"Points: {}\n"
"Products: {}\n"
"Current: {}".format(self.user, self.badges, self.points, self.products, self.current)
)
def __repr__(self):
return "{} - {} - {} - {} - {}".format(self.user, self.badges, self.points, self.products, self.current)
async def load_config(self):
self.badges = await self.config.user(self.user).badges()
self.points = await self.config.user(self.user).points()
self.products = await self.config.user(self.user).products()
self.current = await self.config.user(self.user).current()
async def save_gardener(self):
await self.config.user(self.user).badges.set(self.badges)
await self.config.user(self.user).points.set(self.points)
await self.config.user(self.user).products.set(self.products)
await self.config.user(self.user).current.set(self.current)
async def is_complete(self, now):
message = None
if self.current:
then = self.current["timestamp"]
health = self.current["health"]
grow_time = self.current["time"]
badge = self.current["badge"]
reward = self.current["reward"]
if (now - then) > grow_time:
self.points += reward
if badge not in self.badges:
self.badges.append(badge)
message = (
"Your plant made it! "
"You are rewarded with the **{}** badge and you have received **{}** Thneeds.".format(badge, reward)
)
if health < 0:
message = "Your plant died!"
if message is not None:
self.current = {}
await self.save_gardener()
await self.user.send(message)
async def _die_in(gardener, degradation):
#
# Calculating how much time in minutes remains until the plant's health hits 0
#
return int(gardener.current["health"] / degradation.degradation)
async def _grow_time(gardener):
#
# Calculating the remaining grow time for a plant
#
now = int(time.time())
then = gardener.current["timestamp"]
return (gardener.current["time"] - (now - then)) / 60
async def _send_message(channel, message):
"""Sendsa message"""
em = discord.Embed(description=message, color=discord.Color.green())
await channel.send(embed=em)
async def _withdraw_points(gardener: Gardener, amount):
#
# Substract points from the gardener
#
if (gardener.points - amount) < 0:
return False
else:
gardener.points -= amount
return True
class PlantTycoon(commands.Cog):
"""Grow your own plants! Be sure to take proper care of it."""
def __init__(self, bot: Red, *args, **kwargs):
super().__init__(*args, **kwargs)
self.bot = bot
self.config = Config.get_conf(self, identifier=80108971101168412199111111110)
default_user = {"badges": [], "points": 0, "products": {}, "current": {}}
self.config.register_user(**default_user)
self.plants = None
self.products = None
self.defaults = {
"points": {
"buy": 5,
"add_health": 5,
"fertilize": 10,
"pruning": 20,
"pesticide": 25,
"growing": 5,
"damage": 25,
},
"timers": {"degradation": 1, "completion": 1, "notification": 5},
"degradation": {"base_degradation": 1.5},
"notification": {"max_health": 50},
}
self.badges = {
"badges": {
"Flower Power": {},
"Fruit Brute": {},
"Sporadic": {},
"Odd-pod": {},
"Greenfingers": {},
"Nobel Peas Prize": {},
"Annualsary": {},
}
}
self.notifications = {
"messages": [
"The soil seems dry, maybe you could give your plant some water?",
"Your plant seems a bit droopy. I would give it some fertilizer if I were you.",
"Your plant seems a bit too overgrown. You should probably trim it a bit.",
]
}
#
# Starting loops
#
self.completion_task = bot.loop.create_task(self.check_completion_loop())
# self.degradation_task = bot.loop.create_task(self.check_degradation())
self.notification_task = bot.loop.create_task(self.send_notification())
#
# Loading bank
#
# self.bank = bot.get_cog('Economy').bank
async def red_delete_data_for_user(
self,
*,
requester: Literal["discord_deleted_user", "owner", "user", "user_strict"],
user_id: int,
):
await self.config.user_from_id(user_id).clear()
async def _load_plants_products(self):
"""Runs in __init__.py before cog is added to the bot"""
plant_path = bundled_data_path(self) / "plants.json"
product_path = bundled_data_path(self) / "products.json"
with plant_path.open() as json_data:
self.plants = json.load(json_data)
await self._load_event_seeds()
with product_path.open() as json_data:
self.products = json.load(json_data)
for product in self.products:
print("PlantTycoon: Loaded {}".format(product))
async def _load_event_seeds(self):
self.plants["all_plants"] = copy.deepcopy(self.plants["plants"])
plant_options = self.plants["all_plants"]
d = datetime.date.today()
month = d.month
if month == 1:
plant_options.append(self.plants["event"]["January"])
elif month == 2:
plant_options.append(self.plants["event"]["February"])
elif month == 3:
plant_options.append(self.plants["event"]["March"])
elif month == 4:
plant_options.append(self.plants["event"]["April"])
elif month == 10:
plant_options.append(self.plants["event"]["October"])
elif month == 11:
plant_options.append(self.plants["event"]["November"])
elif month == 12:
plant_options.append(self.plants["event"]["December"])
async def _gardener(self, user: discord.User) -> Gardener:
#
# This function returns a Gardener object for the user
#
g = Gardener(user, self.config)
await g.load_config()
return g
async def _degradation(self, gardener: Gardener):
#
# Calculating the rate of degradation per check_completion_loop() cycle.
#
if self.products is None:
await self._load_plants_products()
modifiers = sum(
[self.products[product]["modifier"] for product in gardener.products if gardener.products[product] > 0]
)
degradation = (
100
/ (gardener.current["time"] / 60)
* (self.defaults["degradation"]["base_degradation"] + gardener.current["degradation"])
) + modifiers
d = collections.namedtuple("degradation", "degradation time modifiers")
return d(degradation=degradation, time=gardener.current["time"], modifiers=modifiers)
# async def _get_member(self, user_id):
#
# #
# # Return a member object
# #
#
# return discord.User(id=user_id) # I made it a string just to be sure
#
# async def _send_notification(self, user_id, message):
#
# #
# # Sends a Direct Message to the gardener
# #
#
# member = await self._get_member(user_id)
# em = discord.Embed(description=message, color=discord.Color.green())
# await self.bot.send_message(member, embed=em)
async def _add_health(self, channel, gardener: Gardener, product, product_category):
#
# The function to add health
#
if self.products is None:
await self._load_plants_products()
product = product.lower()
product_category = product_category.lower()
if product in self.products and self.products[product]["category"] == product_category:
if product in gardener.products:
if gardener.products[product] > 0:
gardener.current["health"] += self.products[product]["health"]
gardener.products[product] -= 1
if gardener.products[product] == 0:
del gardener.products[product.lower()]
if product_category == "water":
emoji = ":sweat_drops:"
elif product_category == "fertilizer":
emoji = ":poop:"
# elif product_category == "tool":
else:
emoji = ":scissors:"
message = "Your plant got some health back! {}".format(emoji)
if gardener.current["health"] > gardener.current["threshold"]:
gardener.current["health"] -= self.products[product]["damage"]
if product_category == "tool":
damage_msg = "You used {} too many times!".format(product)
else:
damage_msg = "You gave too much of {}.".format(product)
message = "{} Your plant lost some health. :wilted_rose:".format(damage_msg)
gardener.points += self.defaults["points"]["add_health"]
await gardener.save_gardener()
else:
message = "You have no {}. Go buy some!".format(product)
else:
if product_category == "tool":
message = "You don't have a {}. Go buy one!".format(product)
else:
message = "You have no {}. Go buy some!".format(product)
else:
message = "Are you sure you are using {}?".format(product_category)
if product_category == "water":
emcolor = discord.Color.blue()
elif product_category == "fertilizer":
emcolor = discord.Color.dark_gold()
# elif product_category == "tool":
else:
emcolor = discord.Color.dark_grey()
em = discord.Embed(description=message, color=emcolor)
await channel.send(embed=em)
@commands.group(name="gardening", autohelp=False)
async def _gardening(self, ctx: commands.Context):
"""Gardening commands."""
if ctx.invoked_subcommand is None:
prefix = ctx.prefix
title = "**Welcome to Plant Tycoon.**\n"
description = """'Grow your own plant. Be sure to take proper care of yours.\n
If it successfully grows, you get a reward.\n
As you nurture your plant, you gain Thneeds which can be exchanged for credits.\n\n
**Commands**\n\n
``{0}gardening seed``: Plant a seed inside the earth.\n
``{0}gardening profile``: Check your gardening profile.\n
``{0}gardening plants``: Look at the list of the available plants.\n
``{0}gardening plant``: Look at the details of a plant.\n
``{0}gardening state``: Check the state of your plant.\n
``{0}gardening buy``: Buy gardening supplies.\n
``{0}gardening convert``: Exchange Thneeds for credits.\n
``{0}shovel``: Shovel your plant out.\n
``{0}water``: Water your plant.\n
``{0}fertilize``: Fertilize the soil.\n
``{0}prune``: Prune your plant.\n"""
em = discord.Embed(
title=title,
description=description.format(prefix),
color=discord.Color.green(),
)
em.set_thumbnail(url="https://image.prntscr.com/image/AW7GuFIBSeyEgkR2W3SeiQ.png")
em.set_footer(
text="This cog was made by SnappyDragon18 and PaddoInWonderland. Inspired by The Lorax (2012)."
)
await ctx.send(embed=em)
@commands.cooldown(1, 60 * 10, commands.BucketType.user)
@_gardening.command(name="seed")
async def _seed(self, ctx: commands.Context):
"""Plant a seed inside the earth."""
if self.plants is None:
await self._load_plants_products()
author = ctx.author
# server = context.message.server
# if author.id not in self.gardeners:
# self.gardeners[author.id] = {}
# self.gardeners[author.id]['current'] = False
# self.gardeners[author.id]['points'] = 0
# self.gardeners[author.id]['badges'] = []
# self.gardeners[author.id]['products'] = {}
gardener = await self._gardener(author)
if not gardener.current:
plant_options = self.plants["all_plants"]
plant = choice(plant_options)
plant["timestamp"] = int(time.time())
plant["degrade_count"] = 0
# index = len(self.plants["plants"]) - 1
# del [self.plants["plants"][index]]
message = (
"During one of your many heroic adventures, you came across a mysterious bag that said "
'"pick one". To your surprise it had all kinds of different seeds in them. '
"And now that you're home, you want to plant it. "
"You went to a local farmer to identify the seed, and the farmer "
"said it was {} **{} ({})** seed.\n\n"
"Take good care of your seed and water it frequently. "
"Once it blooms, something nice might come from it. "
"If it dies, however, you will get nothing.".format(plant["article"], plant["name"], plant["rarity"])
)
if "water" not in gardener.products:
gardener.products["water"] = 0
gardener.products["water"] += 5
gardener.current = plant
await gardener.save_gardener()
em = discord.Embed(description=message, color=discord.Color.green())
else:
plant = gardener.current
message = "You're already growing {} **{}**, silly.".format(plant["article"], plant["name"])
em = discord.Embed(description=message, color=discord.Color.green())
await ctx.send(embed=em)
@_gardening.command(name="profile")
async def _profile(self, ctx: commands.Context, *, member: discord.Member = None):
"""Check your gardening profile."""
if member is not None:
author = member
else:
author = ctx.author
gardener = await self._gardener(author)
try:
await self._apply_degradation(gardener)
except discord.Forbidden:
await ctx.send("ERROR\nYou blocked me, didn't you?")
em = discord.Embed(color=discord.Color.green()) # , description='\a\n')
avatar = author.avatar_url if author.avatar else author.default_avatar_url
em.set_author(name="Gardening profile of {}".format(author.name), icon_url=avatar)
em.add_field(name="**Thneeds**", value=str(gardener.points))
if not gardener.current:
em.add_field(name="**Currently growing**", value="None")
else:
em.set_thumbnail(url=gardener.current["image"])
em.add_field(
name="**Currently growing**",
value="{0} ({1:.2f}%)".format(gardener.current["name"], gardener.current["health"]),
)
if not gardener.badges:
em.add_field(name="**Badges**", value="None")
else:
badges = ""
for badge in gardener.badges:
badges += "{}\n".format(badge.capitalize())
em.add_field(name="**Badges**", value=badges)
if not gardener.products:
em.add_field(name="**Products**", value="None")
else:
products = ""
for product_name, product_data in gardener.products.items():
if self.products[product_name] is None:
continue
products += "{} ({}) {}\n".format(
product_name.capitalize(),
product_data / self.products[product_name]["uses"],
self.products[product_name]["modifier"],
)
em.add_field(name="**Products**", value=products)
if gardener.current:
degradation = await self._degradation(gardener)
die_in = await _die_in(gardener, degradation)
to_grow = await _grow_time(gardener)
em.set_footer(
text="Total degradation: {0:.2f}% / {1} min (100 / ({2} / 60) * (BaseDegr {3:.2f} + PlantDegr {4:.2f}))"
" + ModDegr {5:.2f}) Your plant will die in {6} minutes "
"and {7:.1f} minutes to go for flowering.".format(
degradation.degradation,
self.defaults["timers"]["degradation"],
degradation.time,
self.defaults["degradation"]["base_degradation"],
gardener.current["degradation"],
degradation.modifiers,
die_in,
to_grow,
)
)
await ctx.send(embed=em)
@_gardening.command(name="plants")
async def _plants(self, ctx):
"""Look at the list of the available plants."""
if self.plants is None:
await self._load_plants_products()
tick = ""
tock = ""
tick_tock = 0
for plant in self.plants["all_plants"]:
if tick_tock == 0:
tick += "**{}**\n".format(plant["name"])
tick_tock = 1
else:
tock += "**{}**\n".format(plant["name"])
tick_tock = 0
em = discord.Embed(title="All plants that are growable", color=discord.Color.green())
em.add_field(name="\a", value=tick)
em.add_field(name="\a", value=tock)
await ctx.send(embed=em)
@_gardening.command(name="plant")
async def _plant(self, ctx: commands.Context, *, plantname):
"""Look at the details of a plant."""
if not plantname:
await ctx.send_help()
if self.plants is None:
await self._load_plants_products()
t = False
plant = None
for p in self.plants["all_plants"]:
if p["name"].lower() == plantname.lower().strip('"'):
plant = p
t = True
break
if t:
em = discord.Embed(
title="Plant statistics of {}".format(plant["name"]),
color=discord.Color.green(),
)
em.set_thumbnail(url=plant["image"])
em.add_field(name="**Name**", value=plant["name"])
em.add_field(name="**Rarity**", value=plant["rarity"].capitalize())
em.add_field(name="**Grow Time**", value="{0:.1f} minutes".format(plant["time"] / 60))
em.add_field(name="**Damage Threshold**", value="{}%".format(plant["threshold"]))
em.add_field(name="**Badge**", value=plant["badge"])
em.add_field(name="**Reward**", value="{} τ".format(plant["reward"]))
else:
message = "I can't seem to find that plant."
em = discord.Embed(description=message, color=discord.Color.red())
await ctx.send(embed=em)
@_gardening.command(name="state")
async def _state(self, ctx):
"""Check the state of your plant."""
author = ctx.author
gardener = await self._gardener(author)
try:
await self._apply_degradation(gardener)
except discord.Forbidden:
# Couldn't DM the degradation
await ctx.send("ERROR\nYou blocked me, didn't you?")
if not gardener.current:
message = "You're currently not growing a plant."
em_color = discord.Color.red()
else:
plant = gardener.current
degradation = await self._degradation(gardener)
die_in = await _die_in(gardener, degradation)
to_grow = await _grow_time(gardener)
message = (
"You're growing {0} **{1}**. "
"Its health is **{2:.2f}%** and still has to grow for **{3:.1f}** minutes. "
"It is losing **{4:.2f}%** per minute and will die in **{5:.1f}** minutes.".format(
plant["article"],
plant["name"],
plant["health"],
to_grow,
degradation.degradation,
die_in,
)
)
em_color = discord.Color.green()
em = discord.Embed(description=message, color=em_color)
await ctx.send(embed=em)
@_gardening.command(name="buy")
async def _buy(self, ctx, product=None, amount: int = 1):
"""Buy gardening supplies."""
if self.products is None:
await self._load_plants_products()
author = ctx.author
if product is None:
em = discord.Embed(
title="All gardening supplies that you can buy:",
color=discord.Color.green(),
)
for pd in self.products:
em.add_field(
name="**{}**".format(pd.capitalize()),
value="Cost: {} τ\n+{} health\n-{}% damage\nUses: {}\nCategory: {}".format(
self.products[pd]["cost"],
self.products[pd]["health"],
self.products[pd]["damage"],
self.products[pd]["uses"],
self.products[pd]["category"],
),
)
await ctx.send(embed=em)
else:
if amount <= 0:
message = "Invalid amount! Must be greater than 1"
else:
gardener = await self._gardener(author)
if product.lower() in self.products and amount > 0:
cost = self.products[product.lower()]["cost"] * amount
withdraw_points = await _withdraw_points(gardener, cost)
if withdraw_points:
if product.lower() not in gardener.products:
gardener.products[product.lower()] = 0
# gardener.products[product.lower()] += amount
# Only add it once
gardener.products[product.lower()] += amount * self.products[product.lower()]["uses"]
await gardener.save_gardener()
message = "You bought {}.".format(product.lower())
else:
message = "You don't have enough Thneeds. You have {}, but need {}.".format(
gardener.points,
self.products[product.lower()]["cost"] * amount,
)
else:
message = "I don't have this product."
em = discord.Embed(description=message, color=discord.Color.green())
await ctx.send(embed=em)
@_gardening.command(name="convert")
async def _convert(self, ctx: commands.Context, amount: int):
"""Exchange Thneeds for credits."""
author = ctx.author
gardener = await self._gardener(author)
withdraw_points = await _withdraw_points(gardener, amount)
plural = ""
if amount > 0:
plural = "s"
if withdraw_points:
await bank.deposit_credits(author, amount)
message = "{} Thneed{} successfully exchanged for credits.".format(amount, plural)
await gardener.save_gardener()
else:
message = "You don't have enough Thneed{}. " "You have {}, but need {}.".format(
plural, gardener.points, amount
)
em = discord.Embed(description=message, color=discord.Color.green())
await ctx.send(embed=em)
@commands.command(name="shovel")
async def _shovel(self, ctx: commands.Context):
"""Shovel your plant out."""
author = ctx.author
gardener = await self._gardener(author)
if not gardener.current:
message = "You're currently not growing a plant."
else:
gardener.current = {}
message = "You successfully shovelled your plant out."
if gardener.points < 0:
gardener.points = 0
await gardener.save_gardener()
em = discord.Embed(description=message, color=discord.Color.dark_grey())
await ctx.send(embed=em)
@commands.command(name="water")
async def _water(self, ctx):
"""Water your plant."""
author = ctx.author
channel = ctx.channel
gardener = await self._gardener(author)
try:
await self._apply_degradation(gardener)
except discord.Forbidden:
# Couldn't DM the degradation
await ctx.send("ERROR\nYou blocked me, didn't you?")
product = "water"
product_category = "water"
if not gardener.current:
message = "You're currently not growing a plant."
await _send_message(channel, message)
else:
await self._add_health(channel, gardener, product, product_category)
@commands.command(name="fertilize")
async def _fertilize(self, ctx, fertilizer):
"""Fertilize the soil."""
gardener = await self._gardener(ctx.author)
try:
await self._apply_degradation(gardener)
except discord.Forbidden:
# Couldn't DM the degradation
await ctx.send("ERROR\nYou blocked me, didn't you?")
channel = ctx.channel
product = fertilizer
product_category = "fertilizer"
if not gardener.current:
message = "You're currently not growing a plant."
await _send_message(channel, message)
else:
await self._add_health(channel, gardener, product, product_category)
@commands.command(name="prune")
async def _prune(self, ctx):
"""Prune your plant."""
gardener = await self._gardener(ctx.author)
try:
await self._apply_degradation(gardener)
except discord.Forbidden:
# Couldn't DM the degradation
await ctx.send("ERROR\nYou blocked me, didn't you?")
channel = ctx.channel
product = "pruner"
product_category = "tool"
if not gardener.current:
message = "You're currently not growing a plant."
await _send_message(channel, message)
else:
await self._add_health(channel, gardener, product, product_category)
# async def check_degradation(self):
# while "PlantTycoon" in self.bot.cogs:
# users = await self.config.all_users()
# for user_id in users:
# user = self.bot.get_user(user_id)
# gardener = await self._gardener(user)
# await self._apply_degradation(gardener)
# await asyncio.sleep(self.defaults["timers"]["degradation"] * 60)
async def _apply_degradation(self, gardener):
if gardener.current:
degradation = await self._degradation(gardener)
now = int(time.time())
timestamp = gardener.current["timestamp"]
degradation_count = (now - timestamp) // (self.defaults["timers"]["degradation"] * 60)
degradation_count -= gardener.current["degrade_count"]
gardener.current["health"] -= degradation.degradation * degradation_count
gardener.points += self.defaults["points"]["growing"] * degradation_count
gardener.current["degrade_count"] += degradation_count
await gardener.save_gardener()
await gardener.is_complete(now)
async def check_completion_loop(self):
while "PlantTycoon" in self.bot.cogs:
now = int(time.time())
users = await self.config.all_users()
for user_id in users:
user = self.bot.get_user(user_id)
if not user:
continue
gardener = await self._gardener(user)
if not gardener:
continue
try:
await self._apply_degradation(gardener)
await gardener.is_complete(now)
except discord.Forbidden:
# Couldn't DM the results
pass
await asyncio.sleep(self.defaults["timers"]["completion"] * 60)
async def send_notification(self):
while "PlantTycoon" in self.bot.cogs:
users = await self.config.all_users()
for user_id in users:
user = self.bot.get_user(user_id)
if not user:
continue
gardener = await self._gardener(user)
if not gardener:
continue
try:
await self._apply_degradation(gardener)
except discord.Forbidden:
# Couldn't DM the degradation
pass
if gardener.current:
health = gardener.current["health"]
if health < self.defaults["notification"]["max_health"]:
message = choice(self.notifications["messages"])
try:
await user.send(message)
except discord.Forbidden:
# Couldn't DM the results
pass
await asyncio.sleep(self.defaults["timers"]["notification"] * 60)
def __unload(self):
self.completion_task.cancel()
# self.degradation_task.cancel()
self.notification_task.cancel()

View File

@ -173,7 +173,7 @@ class ScriptGen(commands.Cog):
"""
### make sure model is load
if not self.model:
await ctx.send(error("Model not loaded! Contact bot owner!"))
await ctx.send(error("Model not loaded! Contact bot owner!"), delete_after=30)
return
# make sure we are not on cooldown
@ -181,18 +181,24 @@ class ScriptGen(commands.Cog):
last_ran = await self.config.guild(ctx.guild).last_ran()
now = time.time()
if now - last_ran < cooldown:
await ctx.send(f"Sorry, this command is on cooldown for {int((last_ran + cooldown) - now)} seconds")
await ctx.send(
f"Sorry, this command is on cooldown for {int((last_ran + cooldown) - now)} seconds",
delete_after=((last_ran + cooldown) - now),
)
return
# make sure max length isnt exceeded
max_len = await self.config.max_len()
if num_words > max_len:
await ctx.send(error(f"Maximum number of words that can be generated is: {max_len}"))
await ctx.send(error(f"Maximum number of words that can be generated is: {max_len}"), delete_after=30)
return
# check for current lock:
if self.lock:
await ctx.send(error("Sorry, I am currently busy generating for someone else! Please wait a few moments."))
await ctx.send(
error("Sorry, I am currently busy generating for someone else! Please wait a few moments."),
delete_after=30,
)
return
### lock if enabled

View File

@ -0,0 +1,84 @@
# NOTE: this file contains backports or unintroduced features of next versions of dpy (as for 1.7.3)
import discord
from discord.http import Route
async def create_thread(bot, channel: discord.TextChannel, message: discord.Message, name: str, archive: int = 1440):
"""
Creates a new thread in the channel from the message
Args:
channel (TextChannel): The channel the thread will be apart of
message (Message): The discord message the thread will start with
name (str): The name of the thread
archive (int): The archive duration. Can be 60, 1440, 4320, and 10080.
Returns:
int: The channel ID of the newly created thread
Note:
The guild must be boosted for longer thread durations then a day. The archive parameter will automatically be scaled down if the feature is not present.
Raises HTTPException 400 if thread creation fails
"""
guild = channel.guild
if archive > 4320 and "THREE_DAY_THREAD_ARCHIVE" not in guild.features:
archive = 1440
elif archive == 10080 and "SEVEN_DAY_THREAD_ARCHIVE" not in guild.features:
archive = 4320
fields = {"name": name, "auto_archive_duration": archive}
reason = "Thread Manager"
r = Route(
"POST",
"/channels/{channel_id}/messages/{message_id}/threads",
channel_id=channel.id,
message_id=message.id,
)
return (await bot.http.request(r, json=fields, reason=reason))["id"]
async def add_user_thread(bot, channel: int, member: discord.Member):
"""
Add a user to a thread
Args:
channel (int): The channel id that represents the thread
member (Member): The member to add to the thread
"""
reason = "Thread Manager"
r = Route(
"POST",
"/channels/{channel_id}/thread-members/{user_id}",
channel_id=channel,
user_id=member.id,
)
return await bot.http.request(r, reason=reason)
async def get_active_threads(bot, guild: discord.Guild):
"""
Get all active threads in the guild
Args:
guild (Guild): The guild to get active threads in
Returns:
list(int): List of thread IDs of each actuvate thread
"""
reason = "Thread Manager"
r = Route(
"GET",
"/guilds/{guild_id}/threads/active",
guild_id=guild.id,
)
res = await bot.http.request(r, reason=reason)
return [t["id"] for t in res["threads"]]

View File

@ -11,6 +11,7 @@ from redbot.core.utils.menus import start_adding_reactions
from redbot.core.utils.antispam import AntiSpam
from redbot.core.bot import Red
from .discord_thread_feature import create_thread, add_user_thread
class Suggestion(commands.Cog):
@ -37,6 +38,7 @@ class Suggestion(commands.Cog):
up_emoji=None,
down_emoji=None,
delete_suggest=False,
create_threads=False,
)
self.config.register_global(toggle=False, server_id=None, channel_id=None, next_id=1, ignore=[])
self.config.init_custom("SUGGESTION", 2)
@ -119,6 +121,15 @@ class Suggestion(commands.Cog):
await ctx.message.delete()
else:
await ctx.tick()
if await self.config.guild(ctx.guild).create_threads():
# always use max archive, function will clip it if needed
try:
thread = await create_thread(self.bot, channel, msg, name=content, archive=10080)
await add_user_thread(self.bot, thread, ctx.author)
except:
await ctx.send("Error in creating a thread for this suggestion, please check permissions!")
try:
await ctx.author.send(content="Your suggestion has been sent for approval!", embed=embed)
except discord.Forbidden:
@ -412,6 +423,14 @@ class Suggestion(commands.Cog):
"""Suggestion settings"""
pass
@setsuggest.command(name="threads")
async def setsuggest_threads(self, ctx: commands.Context, toggle: bool):
"""
Enable automatic thread creation for each suggestion, for discussion
"""
await self.config.guild(ctx.guild).create_threads.set(toggle)
await ctx.tick()
@checks.bot_has_permissions(manage_channels=True)
@setsuggest.command(name="setup")
async def setsuggest_setup(self, ctx: commands.Context):
@ -563,6 +582,20 @@ class Suggestion(commands.Cog):
rejected = predchan.result
await self.config.guild(ctx.guild).reject_id.set(rejected.id)
await msg.delete()
msg = await ctx.send(
"Do you want to automatically create threads for each suggestion to allow discussion of each suggestion to be seperated?"
)
start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS)
pred = ReactionPredicate.yes_or_no(msg, ctx.author)
try:
await self.bot.wait_for("reaction_add", timeout=30, check=pred)
except asyncio.TimeoutError:
await msg.delete()
return await ctx.send("You took too long. Try again, please.")
if pred.result:
await self.config.guild(ctx.guild).create_threads.set(True)
await ctx.send("You have finished the setup! Please, move your channels to the category you want them in.")
@checks.bot_has_permissions(add_reactions=True)
@ -596,7 +629,9 @@ class Suggestion(commands.Cog):
@checks.bot_has_permissions(manage_messages=True)
@setsuggest.command(name="autodelete")
async def setsuggest_autodelete(self, ctx: commands.Context, on_off: bool = None):
"""Toggle whether after `[p]suggest`, the bot deletes the message."""
"""
Toggle whether after `[p]suggest`, the bot deletes the message.
"""
target_state = on_off if on_off else not (await self.config.guild(ctx.guild).delete_suggest())
await self.config.guild(ctx.guild).delete_suggest.set(target_state)
if target_state:

View File

@ -0,0 +1,7 @@
from .thread_manager import ThreadManager
__red_end_user_data_statement__ = "This doesn't store any user data."
def setup(bot):
bot.add_cog(ThreadManager(bot))

View File

@ -0,0 +1,84 @@
# NOTE: this file contains backports or unintroduced features of next versions of dpy (as for 1.7.3)
import discord
from discord.http import Route
async def create_thread(bot, channel: discord.TextChannel, message: discord.Message, name: str, archive: int = 1440):
"""
Creates a new thread in the channel from the message
Args:
channel (TextChannel): The channel the thread will be apart of
message (Message): The discord message the thread will start with
name (str): The name of the thread
archive (int): The archive duration. Can be 60, 1440, 4320, and 10080.
Returns:
int: The channel ID of the newly created thread
Note:
The guild must be boosted for longer thread durations then a day. The archive parameter will automatically be scaled down if the feature is not present.
Raises HTTPException 400 if thread creation fails
"""
guild = channel.guild
if archive > 4320 and "THREE_DAY_THREAD_ARCHIVE" not in guild.features:
archive = 1440
elif archive == 10080 and "SEVEN_DAY_THREAD_ARCHIVE" not in guild.features:
archive = 4320
fields = {"name": name, "auto_archive_duration": archive}
reason = "Thread Manager"
r = Route(
"POST",
"/channels/{channel_id}/messages/{message_id}/threads",
channel_id=channel.id,
message_id=message.id,
)
return (await bot.http.request(r, json=fields, reason=reason))["id"]
async def add_user_thread(bot, channel: int, member: discord.Member):
"""
Add a user to a thread
Args:
channel (int): The channel id that represents the thread
member (Member): The member to add to the thread
"""
reason = "Thread Manager"
r = Route(
"POST",
"/channels/{channel_id}/thread-members/{user_id}",
channel_id=channel,
user_id=member.id,
)
return await bot.http.request(r, reason=reason)
async def get_active_threads(bot, guild: discord.Guild):
"""
Get all active threads in the guild
Args:
guild (Guild): The guild to get active threads in
Returns:
list(int): List of thread IDs of each actuvate thread
"""
reason = "Thread Manager"
r = Route(
"GET",
"/guilds/{guild_id}/threads/active",
guild_id=guild.id,
)
res = await bot.http.request(r, reason=reason)
return [t["id"] for t in res["threads"]]

8
threadmanager/info.json Normal file
View File

@ -0,0 +1,8 @@
{
"author" : ["brandons209"],
"install_msg" : "Thank you for installing my cog!",
"name" : "Thread Manager",
"short" : "Manage access to threads for users.",
"description" : "Allows a more finer management of users and threads.",
"tags" : ["Threads"]
}

View File

@ -0,0 +1,130 @@
import asyncio
import discord
from typing import Optional, Literal
from redbot.core import Config, checks, commands
from .discord_thread_feature import create_thread, add_user_thread, get_active_threads
class ThreadManager(commands.Cog):
"""
Better Thread Manager
"""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=165164165133023130, force_registration=True)
# allowed roles maps role id (str) -> number of threads each user can create with this role (int)
# threads maps member id str -> list of active thread ids (int) that the user created in the channel
default_channel = {"allowed_roles": {}, "threads": {}}
default_guild = {"archive": 60}
self.config.register_channel(**default_channel)
self.config.register_guild(**default_guild)
@commands.group()
@commands.guild_only()
@checks.admin_or_permissions(administrator=True)
async def threadset(self, ctx):
"""
Manage threads
"""
pass
@threadset.command(name="archive")
async def threadset_archive(self, ctx, archive: int):
"""
Set the archive duration of user created threads
Must be one of: 60, 1440, 4320, and 10080
If your guild doesn't have longer thread archival features, the archive value is clipped to the highest value available.
"""
if archive not in [60, 1440, 4320, 10080]:
return await ctx.send("Invalid archive time, try again.", delete_after=30)
await self.config.guild(ctx.guild).archive.set(archive)
await ctx.tick()
@threadset.command(name="add")
async def threadset_add(self, ctx, channel: discord.TextChannel, num_threads: int, *, role: discord.Role):
"""
Set the number for threads anyone with role can create for channel
If a user has multiple roles, whatever role has the highest value is used
"""
async with self.config.channel(channel).allowed_roles() as allowed_roles:
allowed_roles[str(role.id)] = num_threads
await ctx.tick()
@threadset.command(name="del")
async def threadset_del(self, ctx, channel: discord.TextChannel, *, role: discord.Role):
"""
Delete a role from a channel
Does not cleanup threads currently active
"""
async with self.config.channel(channel).allowed_roles() as allowed_roles:
if str(role.id) in allowed_roles:
del allowed_roles[str(role.id)]
await ctx.tick()
@commands.command()
@commands.guild_only()
async def thread(self, ctx, *, name: str):
"""
Create a new thread from this channel
You must have proper permissions set
"""
channel = ctx.channel
guild = ctx.guild
user = ctx.author
allowed_roles = await self.config.channel(channel).allowed_roles()
roles = {int(r) for r in allowed_roles.keys()}
u_roles = {r.id for r in user.roles}
if not (roles & u_roles):
return await ctx.send(
"Sorry, you do not have a role that allows you to create threads here.", delete_after=15
)
possible_roles = roles & u_roles
num_threads = sorted([allowed_roles[str(r)] for r in possible_roles])[-1]
threads = await self.config.channel(channel).threads()
if str(user.id) not in threads:
threads[str(user.id)] = []
user_threads = threads[str(user.id)]
if len(user_threads) >= num_threads:
# first, need to update active threads for this channel
activate_threads = set(await get_active_threads(self.bot, guild))
still_active = set(user_threads) & activate_threads
# remove not active threads
user_threads = [t for t in user_threads if t in still_active]
# update config
threads[str(user.id)] = user_threads
await self.config.channel(channel).threads.set(threads)
if len(user_threads) >= num_threads:
return await ctx.send(
f"You have reached the maximum number ({num_threads}) of threads you can create for this channel. Please have a staff member archive one of your threads.",
delete_after=15,
)
# now we can create a thread
archive = await self.config.guild(guild).archive()
try:
thread = await create_thread(self.bot, channel, ctx.message, name=name, archive=archive)
await add_user_thread(self.bot, thread, user)
except:
return await ctx.send(
"Something went wrong, most likely a permissions issue. Please contact a staff member.", delete_after=30
)
threads[str(user.id)].append(thread)
await self.config.channel(channel).threads.set(threads)

View File

@ -422,28 +422,30 @@ class Warnings_Custom(commands.Cog):
# get context of reason, if provided
context = ""
if await self.config.guild(guild).allow_context():
msg = await ctx.send("Would you like to provide more context to the warning? (react with yes or no)")
msg = await ctx.send(
"Would you like to provide more context to the warning? (react with yes or no)", delete_after=31
)
start_adding_reactions(msg, ReactionPredicate.YES_OR_NO_EMOJIS)
pred = ReactionPredicate.yes_or_no(msg, ctx.author)
try:
await self.bot.wait_for("reaction_add", check=pred, timeout=30)
except asyncio.TimeoutError:
await ctx.send(error("Took too long, cancelling warning!"))
await ctx.send(error("Took too long, cancelling warning!"), delete_after=30)
return
if pred.result:
done = False
while not done:
await ctx.send("Please provide context as text and/or an attachment.")
await ctx.send("Please provide context as text and/or an attachment.", delete_after=240)
pred = MessagePredicate.same_context(ctx)
try:
msg = await self.bot.wait_for("message", check=pred, timeout=240)
except asyncio.TimeoutError:
await ctx.send(error("Took too long, cancelling warning!"))
await ctx.send(error("Took too long, cancelling warning!"), delete_after=30)
return
yes_or_no = await ctx.send("Continue with provided context? React no to redo.")
yes_or_no = await ctx.send("Continue with provided context? React no to redo.", delete_after=31)
start_adding_reactions(yes_or_no, ReactionPredicate.YES_OR_NO_EMOJIS)
pred = ReactionPredicate.yes_or_no(yes_or_no, ctx.author)
try: