Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP][Welcome] Add dynamic welcome image generation #582

Open
wants to merge 48 commits into
base: V3/testing
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
554ecb7
defined data & image paths
jlacsamana Sep 11, 2022
ea1c441
add check that creates folder for random image pool if it doiesnt exist
jlacsamana Sep 11, 2022
f3ed2d9
add boilerplate code for web requests
jlacsamana Sep 11, 2022
e2a5f10
implemented toggle
jlacsamana Sep 11, 2022
60c1eca
implement image add function
jlacsamana Sep 11, 2022
6e3ddd0
implemented helper function that handles welcome image generation
jlacsamana Sep 11, 2022
ab31e25
implement welcome image send
jlacsamana Sep 11, 2022
40d1eb3
implement random welcome image generation
jlacsamana Sep 11, 2022
64f654d
black reformat
jlacsamana Sep 11, 2022
147be39
implement function to ensure random image configs exist
jlacsamana Sep 11, 2022
505ef40
rework toggle image config key into constants.py
jlacsamana Sep 11, 2022
d2a9042
added check to ensure image randomiser isnt enabled if size of image …
jlacsamana Sep 11, 2022
6b7d084
black reformat
jlacsamana Sep 11, 2022
d374cf7
black reformats
jlacsamana Sep 11, 2022
10b9b8f
black reformats
jlacsamana Sep 12, 2022
21b511a
black reformats
jlacsamana Sep 12, 2022
354c980
.
jlacsamana Sep 13, 2022
c7216db
Revert "."
jlacsamana Sep 13, 2022
bd01f0d
Revert "black reformats"
jlacsamana Sep 13, 2022
7307a8b
Revert "black reformats"
jlacsamana Sep 13, 2022
9faf5f5
Revert "black reformats"
jlacsamana Sep 13, 2022
e18247b
Revert "black reformat"
jlacsamana Sep 13, 2022
02ea4cf
reformatted welcome
jlacsamana Sep 13, 2022
faa5c87
revert reformats for heist & casino
jlacsamana Sep 13, 2022
8e46337
revert gitignore
jlacsamana Sep 13, 2022
7e1e9b1
.
jlacsamana Sep 13, 2022
2f5d46b
.
jlacsamana Sep 13, 2022
2d6420c
addressed nits
jlacsamana Sep 19, 2022
775ae9b
closed all used images after image gen resolves
jlacsamana Sep 19, 2022
9a900cf
changed http request session to local variable
jlacsamana Sep 19, 2022
bfd86ab
Merge branch 'SFUAnime:V3/testing' into welcomes-upgrade
jlacsamana Feb 17, 2023
a335116
.
jlacsamana Feb 17, 2023
037ceda
.
jlacsamana Feb 17, 2023
1939069
Merge branch 'welcomes-upgrade' of https://github.com/jlacsamana/Ren …
jlacsamana Feb 17, 2023
5e62e93
implement remove command
jlacsamana Feb 17, 2023
dbe239d
implement view image
jlacsamana Feb 17, 2023
cc72c3a
implemented image removal & viewing and listing
jlacsamana Feb 19, 2023
7015c9a
implemented per server image caches
jlacsamana Feb 21, 2023
79e8f84
refactored variables into camelCase 🐫🐫🐫
jlacsamana Feb 21, 2023
1577752
refactor image gen to use bundled datapath
jlacsamana Feb 21, 2023
7467381
simplify all config modifying code
jlacsamana Feb 21, 2023
659d340
reworked filepath function calls
jlacsamana Feb 21, 2023
72c515b
implemented show template command
jlacsamana Feb 21, 2023
87722d6
😩😩😩implemented injabie grammar nits 😩😩😩
jlacsamana Mar 5, 2023
3277d9b
👹👹👹addressed Tatsu nits👹👹👹
jlacsamana Apr 28, 2023
5e9956e
😭🙏 🐍❌🐪✔️ 🐛🔨
jlacsamana Apr 28, 2023
097de76
😬😬😬
jlacsamana Apr 28, 2023
b6e6cef
refactored all uses of os.path.join
jlacsamana May 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ Pipfile.lock
.vscode/
*.sublime-project
*.sublime-workspace

## Plugin-specific files:

# IntelliJ
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Binary file added .vs/Repo/v17/.suo
Binary file not shown.
9 changes: 9 additions & 0 deletions .vs/VSWorkspaceState.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"ExpandedNodes": [
"",
"\\cogs",
"\\cogs\\welcome"
],
"SelectedNode": "\\cogs\\welcome\\welcome.py",
"PreviewInSolutionExplorer": false
}
Binary file added .vs/slnx.sqlite
Binary file not shown.
6 changes: 6 additions & 0 deletions cogs/.vs/VSWorkspaceState.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ExpandedNodes": [
"\\heist"
],
"PreviewInSolutionExplorer": false
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Binary file added cogs/.vs/cogs/v17/.suo
Binary file not shown.
Binary file added cogs/.vs/slnx.sqlite
Binary file not shown.
3 changes: 3 additions & 0 deletions cogs/welcome/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
KEY_WELCOME_CHANNEL_SETTINGS = "welcomeChannelSettings"
KEY_POST_FAILED_DM = "postFailedDm"
KEY_JOINED_USER_IDS = "joinedUserIds"
KEY_TOGGLE_RANDOM_MSG = "toggleImg"
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
WELCOME_IMG_FOLDER = "welcomeImgs"

MAX_MESSAGE_LENGTH = 2000
MAX_DESCRIPTION_LENGTH = 500
Expand All @@ -38,6 +40,7 @@
KEY_POST_FAILED_DM: False,
},
KEY_JOINED_USER_IDS: [],
KEY_TOGGLE_RANDOM_MSG: False,
}


Expand Down
Binary file added cogs/welcome/data/BORDER.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added cogs/welcome/data/BORDER_mask.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added cogs/welcome/data/MASK.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added cogs/welcome/data/welcome_template.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
211 changes: 208 additions & 3 deletions cogs/welcome/welcome.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
"""Welcome cog
Sends welcome DMs to users that join the server.
"""

import asyncio
import discord
import logging
import random
import aiohttp
import os
import io

from redbot.core import Config, checks, commands
from PIL import Image, ImageChops, ImageOps
from redbot.core import Config, checks, commands, data_manager
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
from redbot.core.bot import Red
from redbot.core.commands.context import Context
from redbot.core.utils.chat_formatting import box, info, pagify, warning
Expand All @@ -28,8 +31,23 @@ class Welcome(commands.Cog): # pylint: disable=too-many-instance-attributes
def __init__(self, bot: Red):
self.bot = bot
self.config = Config.get_conf(self, identifier=5842647, force_registration=True)

self.config.register_guild(**DEFAULT_GUILD)

self.dataDir = data_manager.cog_data_path(cog_instance=self)
self.imgDir = self.dataDir / WELCOME_IMG_FOLDER
self.bundled_assets = data_manager.bundled_data_path(self)
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved

# create folder to hold welcome images
try:
os.makedirs(self.imgDir, exist_ok=True)
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
except OSError as error:
LOGGER.error(
"Could not create folder for images!",
exc_info=True,
)
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
raise OSError("Could not create folder for images!")
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved

async def getRandomMessage(self, guild: discord.Guild, pool: Optional[GreetingPools] = None):
"""Gets a random message from a greeting pool.

Expand Down Expand Up @@ -149,6 +167,7 @@ async def sendWelcomeMessageChannel(self, newUser: discord.Member):
isSet = await self.config.guild(guild).get_attr(KEY_WELCOME_CHANNEL_ENABLED)()
# if channel isn't set
if not isSet:
print("not set")
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
return
channel = discord.utils.get(guild.channels, id=channelID)

Expand All @@ -161,7 +180,12 @@ async def sendWelcomeMessageChannel(self, newUser: discord.Member):
message = rawMessage.replace("{USER}", newUser.mention)

try:
await channel.send(message)
img = await self.generateRandWelcomeImg(newUser, newUser.guild)
if await self.config.guild(guild).get_attr(KEY_TOGGLE_RANDOM_MSG)():
await channel.send(message, file=discord.File(img, filename="generated.png"))
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
else:
await channel.send(message)
img.close()
except (discord.Forbidden, discord.HTTPException) as errorMsg:
LOGGER.error(
"Could not send message, please make sure the bot "
Expand Down Expand Up @@ -318,6 +342,71 @@ async def logServerLeave(self, leaveUser: discord.Member):
leaveUser.id,
)

async def generateRandWelcomeImg(self, user, guild):
"""create an image for the specific player using their avatar and an image from the random image pool, then returns it"""
base = Image.open(
self.imgDir
/ str(guild.id)
/ random.choice(os.listdir(os.path.join(self.imgDir, str(guild.id))))
)
mask = Image.open(os.path.join(data_manager.bundled_data_path(self), "MASK.png"))
borderOverlay = Image.open(
os.path.join(data_manager.bundled_data_path(self), "BORDER.png")
)
borderOverlayMask = Image.open(
os.path.join(data_manager.bundled_data_path(self), "BORDER_mask.png")
)
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
# get avatar from User
avatar: bytes
session = aiohttp.ClientSession()
# a header to successfully download user avatars for use
usedHeader = {"User-agent": "Mozilla/5.0"}

try:
async with session.get(str(user.avatar.url), headers=usedHeader) as webp:
avatar = await webp.read()
except aiohttp.ClientResponseError:
pass

with Image.open(io.BytesIO(avatar)) as retrievedAvatar:
if not retrievedAvatar:
base.close()
mask.close()
borderOverlay.close()
borderOverlayMask.close()
return
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
else:
retrievedAvatar = retrievedAvatar.resize((325, 325), 1)
base.paste(borderOverlay, (434, 0), borderOverlayMask)
base.paste(retrievedAvatar, (434, 0), mask)
generated = io.BytesIO()
base.save(generated, format="png")
generated.seek(0)
base.close()
mask.close()
borderOverlay.close()
borderOverlayMask.close()
return generated

async def ensureCurrentServerHasImgCache(self, channel):
"""
Check if there is a folder in the image cache for the associated server. If one doesn't exist, creates it.
"""
idStr = str(channel.guild.id)
fp = os.path.join(self.imgDir, idStr)
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
if os.path.exists(fp):
return

try:
os.makedirs(fp, exist_ok=True)
await channel.send("No image cache folder found for this server! Created one")

except OSError as info:
LOGGER.info(
"No folder for given server ID found. Created folder for server: %s" % idStr,
exc_info=True,
)

####################
# MESSAGE COMMANDS #
####################
Expand Down Expand Up @@ -470,6 +559,24 @@ async def greetings(self, ctx: Context):
- `returning`: pool of greetings that are sent to returning users
"""

# [p]welcomeset greetings toggleimg
@greetings.command(name="toggleimg")
async def toggleImg(self, ctx: Context):
"""Toggle the random image on and off"""
await self.ensureCurrentServerHasImgCache(ctx.channel)
toggleImgConfig = self.config.guild(ctx.guild).get_attr(KEY_TOGGLE_RANDOM_MSG)
randomImageEnabled = await toggleImgConfig()

if randomImageEnabled:
await toggleImgConfig.set(False)
else:
if len(os.listdir(os.path.join(self.imgDir, str(ctx.guild.id)))) < 1:
await ctx.send("Add at least one image before turning the randomiser on")
return
await toggleImgConfig.set(True)

await ctx.send(f"Sending randomised welcome image: {await toggleImgConfig()}")

# [p]welcomeset greetings add
@greetings.command(name="add")
async def greetAdd(self, ctx: Context, name: str, pool: Optional[str] = None):
Expand Down Expand Up @@ -537,6 +644,104 @@ def check(message: discord.Message):
await self.config.guild(ctx.guild).get_attr(key).set(greetings)
return

# [p]welcomeset greetings image
@greetings.group(name="image")
async def image(self, ctx: Context):
"""Base command for the image command group"""
pass
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved

# [p]welcomeset greetings image template
@image.command(name="template")
async def imageTemplate(self, ctx: Context):
await ctx.send(
"Here is the welcome image template so you can make your own! For best results please render the image at 72dpi, 1193 x 671. The bot will try to make it conform automatically but mileage may vary.",
file=discord.File(
os.path.join(data_manager.bundled_data_path(self), "welcome_template.png")
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
),
)

# [p]welcomeset greetings image add
@image.command(name="add")
async def imgAdd(self, ctx: Context, name: str):
"""adds the attached image to the pool of random based images used to generate custom welcome images. Attaches only the first image attached.
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved


Additionally automatically makes the sent image conform to the dimensions and dpi that's been tested for: 72dpi, 1193x671. Mileage may vary

"""
await self.ensureCurrentServerHasImgCache(ctx.channel)
file_name = f"{name}.png"
fp = os.path.join(self.imgDir, str(ctx.channel.guild.id), file_name)
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved

if os.path.exists(fp):
await ctx.reply(
"This name is already in use! For ease of management, please use another name."
)
return

image = None
if len(ctx.message.attachments) == 1:
image = ctx.message.attachments[0]

else:
await ctx.reply(
"You need to attach exactly 1 image in the message that uses this command"
)
return

await image.save(fp)

# Performing necessary checks to ensure that this base can produce a good generated image
temp = Image.open(fp)
temp_resize = temp.resize((1193, 671), 2)
temp_resize.save(fp, dpi=(72, 72))

# alert user that their image has been added
await ctx.reply("Image added to this server's image cache")

# [p]welcomeset greetings image remove
@image.command(name="remove")
async def imgRemove(self, ctx: Context, img_name: str):
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
"""Removes the specified image from the pool"""
await self.ensureCurrentServerHasImgCache(ctx.channel)
file_name = f"{img_name}.png"
try:
os.remove(os.path.join(self.imgDir, str(ctx.channel.guild.id), file_name))
except:
await ctx.reply("The named image doesn't exist")
return

if len(os.listdir(os.path.join(self.imgDir, str(ctx.channel.guild.id)))) == 0:
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved
self.config.guild(ctx.guild).get_attr(KEY_TOGGLE_RANDOM_MSG).set(False)
await ctx.reply("Last image deleted. Image randomiser turned off.")

await ctx.reply("Image sucessfully removed")

# [p]welcomeset greetings image view
@image.command(name="view")
async def showImg(self, ctx: Context, img_name: str):
"""Show the named image from the image pool if it exists"""
await self.ensureCurrentServerHasImgCache(ctx.channel)
file_name = f"{img_name}.png"
try:
await ctx.send(
img_name + ":",
file=discord.File(os.path.join(self.imgDir, str(ctx.channel.guild.id), file_name)),
)
except:
await ctx.reply("The named image doesn't exist")

# [p]welcomeset greetings image list
@image.command(name="list")
async def listImg(self, ctx: Context):
"""Display a list of all the images in this server's image cache"""
await self.ensureCurrentServerHasImgCache(ctx.channel)
listOfImages = "\n".join(os.listdir(os.path.join(self.imgDir, str(ctx.channel.guild.id))))
if len(listOfImages) == 0:
await ctx.reply("No images added yet.")
return
await ctx.reply(listOfImages.replace(".png", ""))
jlacsamana marked this conversation as resolved.
Show resolved Hide resolved

# [p]welcomeset greetings channelset
@greetings.group(name="channelset", aliases=["channelconfig", "chconfig", "chset"])
async def greetChannelConfig(self, ctx: Context):
Expand Down