Update info.json to reflect new leveling system details, including author, description, and installation message. Modify pyproject.toml to remove Wand dependency. Enhance modern.py to generate SVG profile cards with improved stats presentation and avatar handling, streamlining profile generation process.
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run
This commit is contained in:
parent
653b16cb5e
commit
58b7e586d6
3 changed files with 248 additions and 103 deletions
44
info.json
44
info.json
|
@ -1,16 +1,42 @@
|
|||
{
|
||||
"author": [
|
||||
"Vertyco",
|
||||
"Valerie"
|
||||
],
|
||||
"install_msg": "Thanks for adding Ruby Cogs! Join our Discord @ https://discord.gg/5CA8sewarU",
|
||||
"name": "Ruby Cogs",
|
||||
"short": "Ruby Cogs, for our fork of Red-DiscordBot (Ruby-DiscordBot)",
|
||||
"description": "Ruby Cogs, for our fork of Red-DiscordBot (Ruby-DiscordBot)",
|
||||
"description": "Your friendly neighborhood leveling system",
|
||||
"disabled": false,
|
||||
"end_user_data_statement": "This cog stores Discord IDs, counts of user messages, and their time spent in voice channels. No private info is stored about users.",
|
||||
"hidden": false,
|
||||
"install_msg": "Thank you for installing LevelUp! To enable leveling in this server type `[p]lset toggle`.\n\nDOCUMENTATION: https://github.com/vertyco/vrt-cogs/blob/main/levelup/README.md",
|
||||
"min_bot_version": "3.5.0",
|
||||
"min_python_version": [
|
||||
3,
|
||||
9,
|
||||
0
|
||||
],
|
||||
"permissions": [
|
||||
"read_messages",
|
||||
"send_messages",
|
||||
"manage_roles",
|
||||
"attach_files",
|
||||
"embed_links"
|
||||
],
|
||||
"required_cogs": {},
|
||||
"requirements": [
|
||||
"pillow",
|
||||
"svgwrite>=1.4.3",
|
||||
"wand>=0.6.11"
|
||||
],
|
||||
"short": "Discord leveling system",
|
||||
"tags": [
|
||||
"rubycogs",
|
||||
"pokemon",
|
||||
"activity",
|
||||
"level",
|
||||
"leveler",
|
||||
"leveling",
|
||||
"levelup",
|
||||
"rank",
|
||||
"utility",
|
||||
"information",
|
||||
"various"
|
||||
]
|
||||
"vert"
|
||||
],
|
||||
"type": "COG"
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import typing as t
|
||||
from pathlib import Path
|
||||
from io import BytesIO
|
||||
from io import BytesIO, StringIO
|
||||
import logging
|
||||
import svgwrite
|
||||
from wand.image import Image
|
||||
from wand.drawing import Drawing
|
||||
from wand.color import Color
|
||||
|
@ -14,6 +15,166 @@ except ImportError:
|
|||
|
||||
log = logging.getLogger("red.vrt.levelup.generator.styles.modern")
|
||||
|
||||
def create_vector_profile(
|
||||
width: int = 1050,
|
||||
height: int = 320,
|
||||
avatar_size: int = 200,
|
||||
username: str = "",
|
||||
level: int = 1,
|
||||
messages: int = 0,
|
||||
voicetime: int = 0,
|
||||
stars: int = 0,
|
||||
prestige: int = 0,
|
||||
balance: t.Optional[int] = None,
|
||||
currency_name: str = "",
|
||||
progress: float = 0,
|
||||
current_xp: int = 0,
|
||||
next_xp: int = 0,
|
||||
position: int = 0,
|
||||
user_color: t.Tuple[int, int, int] = (88, 101, 242),
|
||||
stat_color: t.Tuple[int, int, int] = (255, 255, 255),
|
||||
level_bar_color: t.Tuple[int, int, int] = (88, 101, 242),
|
||||
) -> str:
|
||||
"""Generate SVG profile card"""
|
||||
# Create SVG document
|
||||
dwg = svgwrite.Drawing(size=(width, height))
|
||||
|
||||
# Define gradients
|
||||
# Main background gradient
|
||||
grad = dwg.defs.add(dwgwrite.gradients.LinearGradient(id="background"))
|
||||
grad.add_stop_color(0, "rgb(20, 20, 30)")
|
||||
grad.add_stop_color(1, "rgb(40, 40, 60)")
|
||||
|
||||
# Progress bar gradient
|
||||
prog_grad = dwg.defs.add(dwgwrite.gradients.LinearGradient(id="progress", x1="0%", y1="0%", x2="0%", y2="100%"))
|
||||
r, g, b = level_bar_color
|
||||
prog_grad.add_stop_color(0, f"rgb({r}, {g}, {b})")
|
||||
prog_grad.add_stop_color(0.5, f"rgb({min(r+20, 255)}, {min(g+20, 255)}, {min(b+20, 255)})")
|
||||
prog_grad.add_stop_color(1, f"rgb({r}, {g}, {b})")
|
||||
|
||||
# Add background
|
||||
dwg.add(dwg.rect(insert=(0, 0), size=(width, height), fill="url(#background)"))
|
||||
|
||||
# Add glass panel with blur effect (using SVG filters)
|
||||
blur = dwg.defs.add(dwg.filter(id="blur"))
|
||||
blur.feGaussianBlur(in_="SourceGraphic", stdDeviation=8)
|
||||
glass = dwg.add(dwg.rect(
|
||||
insert=(15, 15),
|
||||
size=(width-30, height-30),
|
||||
rx=25, ry=25,
|
||||
fill="rgba(255, 255, 255, 0.1)",
|
||||
filter="url(#blur)",
|
||||
stroke="rgba(255, 255, 255, 0.2)",
|
||||
stroke_width=1
|
||||
))
|
||||
|
||||
# Add stats container
|
||||
stats = dwg.add(dwg.g(id="stats", transform=f"translate({avatar_size + 80}, 40)"))
|
||||
|
||||
# Add level text with glow effect
|
||||
glow = dwg.defs.add(dwg.filter(id="glow"))
|
||||
glow.feGaussianBlur(in_="SourceAlpha", stdDeviation=2)
|
||||
glow.feOffset(dx=0, dy=0)
|
||||
glow.feComponentTransfer().feFuncA(type="linear", slope=0.5)
|
||||
glow.feBlend(in2="SourceGraphic", mode="normal")
|
||||
|
||||
level_text = f"LEVEL {level}"
|
||||
if prestige > 0:
|
||||
level_text = f"P{prestige} • {level_text}"
|
||||
|
||||
r, g, b = user_color
|
||||
stats.add(dwg.text(
|
||||
level_text,
|
||||
insert=(0, 0),
|
||||
fill=f"rgb({r}, {g}, {b})",
|
||||
filter="url(#glow)",
|
||||
style="font-size:56px; font-weight:bold; font-family:Arial"
|
||||
))
|
||||
|
||||
# Add stats with vector icons
|
||||
def add_stat(x: int, y: int, icon: str, label: str, value: str):
|
||||
group = dwg.g(transform=f"translate({x}, {y})")
|
||||
# Add icon
|
||||
group.add(dwg.text(
|
||||
icon,
|
||||
insert=(0, 0),
|
||||
fill="white",
|
||||
style="font-size:28px; font-family:Arial"
|
||||
))
|
||||
# Add label
|
||||
group.add(dwg.text(
|
||||
f"{label}:",
|
||||
insert=(40, 0),
|
||||
fill="rgb(220, 220, 220)",
|
||||
style="font-size:28px; font-family:Arial"
|
||||
))
|
||||
# Add value with glow
|
||||
group.add(dwg.text(
|
||||
value,
|
||||
insert=(140, 0),
|
||||
fill="white",
|
||||
filter="url(#glow)",
|
||||
style="font-size:32px; font-weight:bold; font-family:Arial"
|
||||
))
|
||||
return group
|
||||
|
||||
# Left column stats
|
||||
stats.add(add_stat(0, 80, "💬", "Messages", humanize_number(messages)))
|
||||
stats.add(add_stat(0, 130, "🎤", "Voice", imgtools.abbreviate_time(voicetime)))
|
||||
stats.add(add_stat(0, 180, "⭐", "Stars", humanize_number(stars)))
|
||||
|
||||
# Right column stats
|
||||
if balance is not None:
|
||||
stats.add(add_stat(320, 80, "💰", "Balance", f"{humanize_number(balance)} {currency_name}"))
|
||||
stats.add(add_stat(320, 130, "🏆", "Rank", f"#{humanize_number(position)}"))
|
||||
|
||||
# Progress bar
|
||||
bar_width = 620
|
||||
bar_height = 30
|
||||
bar_x = 0
|
||||
bar_y = 220
|
||||
|
||||
# Progress bar background
|
||||
stats.add(dwg.rect(
|
||||
insert=(bar_x, bar_y),
|
||||
size=(bar_width, bar_height),
|
||||
rx=bar_height//2,
|
||||
ry=bar_height//2,
|
||||
fill="rgba(0, 0, 0, 0.3)"
|
||||
))
|
||||
|
||||
# Progress bar fill
|
||||
progress_width = int(bar_width * progress)
|
||||
if progress_width > 0:
|
||||
stats.add(dwg.rect(
|
||||
insert=(bar_x, bar_y),
|
||||
size=(progress_width, bar_height),
|
||||
rx=bar_height//2,
|
||||
ry=bar_height//2,
|
||||
fill="url(#progress)"
|
||||
))
|
||||
|
||||
# XP Text
|
||||
xp_text = f"XP: {humanize_number(current_xp)} / {humanize_number(next_xp)}"
|
||||
stats.add(dwg.text(
|
||||
xp_text,
|
||||
insert=(bar_x + (bar_width - len(xp_text) * 10) / 2, bar_y - 15),
|
||||
fill="white",
|
||||
style="font-size:20px; font-family:Arial"
|
||||
))
|
||||
|
||||
# Progress percentage
|
||||
percent_text = f"{int(progress * 100)}%"
|
||||
stats.add(dwg.text(
|
||||
percent_text,
|
||||
insert=(bar_x + progress_width - 30, bar_y + 20),
|
||||
fill="white",
|
||||
style="font-size:24px; font-weight:bold; font-family:Arial"
|
||||
))
|
||||
|
||||
# Return SVG as string
|
||||
return dwg.tostring()
|
||||
|
||||
def generate_modern_profile(
|
||||
background_bytes: t.Optional[t.Union[bytes, str]] = None,
|
||||
avatar_bytes: t.Optional[t.Union[bytes, str]] = None,
|
||||
|
@ -42,16 +203,38 @@ def generate_modern_profile(
|
|||
debug: bool = False,
|
||||
**kwargs,
|
||||
) -> t.Tuple[bytes, bool]:
|
||||
"""Generate a modern, sleek profile card using Wand/ImageMagick"""
|
||||
"""Generate a modern, sleek profile card using vector graphics"""
|
||||
|
||||
# Set default colors for modern theme
|
||||
user_color = user_color or (88, 101, 242) # Discord Blurple
|
||||
stat_color = stat_color or (255, 255, 255) # White
|
||||
level_bar_color = level_bar_color or (88, 101, 242) # Discord Blurple
|
||||
|
||||
# Create base canvas (1050x320)
|
||||
# Calculate progress
|
||||
progress = (current_xp - previous_xp) / (next_xp - previous_xp)
|
||||
|
||||
# Generate SVG
|
||||
svg_data = create_vector_profile(
|
||||
username=username,
|
||||
level=level,
|
||||
messages=messages,
|
||||
voicetime=voicetime,
|
||||
stars=stars,
|
||||
prestige=prestige,
|
||||
balance=balance,
|
||||
currency_name=currency_name,
|
||||
progress=progress,
|
||||
current_xp=current_xp,
|
||||
next_xp=next_xp,
|
||||
position=position,
|
||||
user_color=user_color,
|
||||
stat_color=stat_color,
|
||||
level_bar_color=level_bar_color,
|
||||
)
|
||||
|
||||
# Convert SVG to image
|
||||
with Image(width=1050, height=320) as card:
|
||||
# Set up background
|
||||
# Set up background if provided
|
||||
if background_bytes:
|
||||
with Image(blob=background_bytes) as bg:
|
||||
bg.transform(resize='1050x320^')
|
||||
|
@ -60,98 +243,35 @@ def generate_modern_profile(
|
|||
bg.gaussian_blur(sigma=15)
|
||||
card.composite(bg, 0, 0)
|
||||
else:
|
||||
# Create gradient background
|
||||
# Use gradient background
|
||||
card.gradient('linear-gradient', 'navy-darkblue')
|
||||
|
||||
# Add frosted glass effect overlay
|
||||
with Image(width=1050, height=320, background=Color('rgba(255, 255, 255, 0.1)')) as overlay:
|
||||
overlay.gaussian_blur(sigma=50)
|
||||
card.composite(overlay, 0, 0)
|
||||
|
||||
# Create circular avatar mask
|
||||
with Drawing() as draw:
|
||||
# Avatar circle (left side)
|
||||
avatar_size = 200
|
||||
avatar_x = 60
|
||||
avatar_y = 60
|
||||
|
||||
if avatar_bytes:
|
||||
with Image(blob=avatar_bytes) as avatar:
|
||||
# Resize avatar
|
||||
avatar.resize(avatar_size, avatar_size)
|
||||
# Create circular mask
|
||||
with Image(width=avatar_size, height=avatar_size, background=Color('transparent')) as mask:
|
||||
draw.circle((avatar_size/2, avatar_size/2), (0, avatar_size/2))
|
||||
draw.draw(mask)
|
||||
# Apply mask to avatar
|
||||
avatar.composite_channel('alpha', mask, 'copy_alpha', 0, 0)
|
||||
# Add avatar to card
|
||||
card.composite(avatar, avatar_x, avatar_y)
|
||||
|
||||
# Add modern stats panel
|
||||
with Image(width=700, height=280, background=Color('rgba(0, 0, 0, 0.5)')) as stats_panel:
|
||||
stats_panel.gaussian_blur(sigma=2)
|
||||
stats_panel.border_color = Color('rgba(255, 255, 255, 0.1)')
|
||||
stats_panel.border_width = 1
|
||||
card.composite(stats_panel, 310, 20)
|
||||
|
||||
# Add text elements
|
||||
draw.font = str(font_path) if font_path else '/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf'
|
||||
draw.font_size = 40
|
||||
draw.fill_color = Color('rgb({}, {}, {})'.format(*user_color))
|
||||
|
||||
# Level text
|
||||
level_text = f"LEVEL {level}"
|
||||
if prestige > 0:
|
||||
level_text = f"P{prestige} • {level_text}"
|
||||
draw.text(340, 70, level_text)
|
||||
|
||||
# Stats
|
||||
draw.font_size = 25
|
||||
draw.fill_color = Color('white')
|
||||
y_pos = 120
|
||||
|
||||
# Left column
|
||||
draw.text(340, y_pos, f"Messages: {humanize_number(messages)}")
|
||||
draw.text(340, y_pos + 40, f"Voice Time: {imgtools.abbreviate_time(voicetime)}")
|
||||
draw.text(340, y_pos + 80, f"Stars: {humanize_number(stars)}")
|
||||
|
||||
# Right column
|
||||
if balance is not None:
|
||||
draw.text(600, y_pos, f"Balance: {humanize_number(balance)} {currency_name}")
|
||||
draw.text(600, y_pos + 40, f"Rank: #{humanize_number(position)}")
|
||||
|
||||
# Progress bar
|
||||
progress = (current_xp - previous_xp) / (next_xp - previous_xp)
|
||||
bar_width = 620
|
||||
bar_height = 30
|
||||
bar_x = 340
|
||||
bar_y = 240
|
||||
|
||||
# Background bar
|
||||
draw.fill_color = Color('rgba(0, 0, 0, 0.3)')
|
||||
draw.rectangle(bar_x, bar_y, bar_x + bar_width, bar_y + bar_height)
|
||||
|
||||
# Progress bar
|
||||
progress_width = int(bar_width * progress)
|
||||
draw.fill_color = Color('rgb({}, {}, {})'.format(*level_bar_color))
|
||||
if progress_width > 0:
|
||||
draw.rectangle(bar_x, bar_y, bar_x + progress_width, bar_y + bar_height)
|
||||
|
||||
# XP Text
|
||||
draw.font_size = 20
|
||||
draw.fill_color = Color('white')
|
||||
xp_text = f"XP: {humanize_number(current_xp)} / {humanize_number(next_xp)}"
|
||||
draw.text(bar_x + (bar_width - len(xp_text) * 10) / 2, bar_y - 15, xp_text)
|
||||
|
||||
# Progress percentage
|
||||
percent_text = f"{int(progress * 100)}%"
|
||||
draw.text(bar_x + progress_width - 30, bar_y + 5, percent_text)
|
||||
|
||||
# Apply all drawings
|
||||
draw.draw(card)
|
||||
# Add SVG elements
|
||||
with Image(blob=svg_data.encode(), format='svg') as svg:
|
||||
card.composite(svg, 0, 0)
|
||||
|
||||
# Save the result
|
||||
# Add avatar if provided
|
||||
if avatar_bytes:
|
||||
with Image(blob=avatar_bytes) as avatar:
|
||||
# Create circular avatar
|
||||
avatar.resize(200, 200)
|
||||
avatar.virtual_pixel = 'transparent'
|
||||
avatar.format = 'png'
|
||||
avatar.alpha_channel = True
|
||||
|
||||
# Create circular mask
|
||||
with Image(width=200, height=200, background=Color('transparent')) as mask:
|
||||
mask.format = 'png'
|
||||
with Drawing() as draw:
|
||||
draw.fill_color = Color('white')
|
||||
draw.circle((100, 100), (0, 100))
|
||||
draw.draw(mask)
|
||||
avatar.composite_channel('alpha', mask, 'copy_alpha', 0, 0)
|
||||
|
||||
# Add avatar to card
|
||||
card.composite(avatar, 60, 60)
|
||||
|
||||
# Save as WebP
|
||||
card.format = 'webp'
|
||||
card.compression_quality = 95
|
||||
|
||||
|
|
|
@ -10,4 +10,3 @@
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.8.1,<4.0"
|
||||
Wand = "^0.6.11" # ImageMagick Python bindings for modern image processing
|
||||
|
|
Loading…
Add table
Reference in a new issue