Bunch of new features and bug fixes
This commit is contained in:
parent
a63aa3661e
commit
e62583c645
21
README.md
21
README.md
|
@ -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**
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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,
|
||||
|
|
110
markov/markov.py
110
markov/markov.py
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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)
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"badges": {
|
||||
"Flower Power": {},
|
||||
"Fruit Brute": {},
|
||||
"Sporadic": {},
|
||||
"Odd-pod": {},
|
||||
"Greenfingers": {},
|
||||
"Nobel Peas Prize": {},
|
||||
"Annualsary": {}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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."
|
||||
]
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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"]]
|
|
@ -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:
|
||||
|
|
|
@ -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))
|
|
@ -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"]]
|
|
@ -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"]
|
||||
}
|
|
@ -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)
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue