Bot Py
Bot Py
py
# Actualizado: fix para que pants no usen detección de zapatos por defecto,
# mejora de detect_shoes_mask, tile_texture offset ajustado (texturas de pants
bajadas 25%),
# eliminación verde aplicada en todos los pasos, preservación de la mayoría del
código original.
import os
import sys
import signal
import discord
from discord.ext import commands
from PIL import Image, ImageChops, ImageFilter, ImageOps, ImageEnhance
import aiohttp, asyncio, io, random, logging, traceback
from collections import Counter
from datetime import datetime, timezone
import typing
if os.path.exists(PIDFILE):
try:
with open(PIDFILE, "r") as f:
old_pid = int(f.read().strip())
if _is_process_running(old_pid):
print(f"Ya hay una instancia corriendo con PID {old_pid}. Aborto para
evitar duplicados.")
sys.exit(1)
else:
try:
os.remove(PIDFILE)
except Exception:
pass
except Exception:
try:
os.remove(PIDFILE)
except Exception:
pass
try:
with open(PIDFILE, "w") as f:
f.write(str(os.getpid()))
except Exception as e:
print("No pude crear pidfile:", e)
def _cleanup_and_exit(*_):
try:
if os.path.exists(PIDFILE):
os.remove(PIDFILE)
except Exception:
pass
sys.exit(0)
signal.signal(signal.SIGINT, _cleanup_and_exit)
signal.signal(signal.SIGTERM, _cleanup_and_exit)
# ------------------------------------------
# No cooldowns
CD_SHIRT = 0.50
CD_PANTS = 0.5
CD_CONJUNTO = 0.5
intents = discord.Intents.default()
intents.message_content = True
intents.guilds = True
intents.messages = True
intents.members = True
# runtime storages
user_last_result_msg: dict[int, discord.Message] = {}
TEST_MODE: dict[int, bool] = {}
try:
cleaned = remove_green_from_pil_image(base_img, green_min=green_min,
tol=tol)
out = io.BytesIO()
cleaned.save(out, "PNG")
out.seek(0)
return out.getvalue()
except Exception:
return b
base_rgb = base.convert("RGB")
tex_rgb = tex_tiled.convert("RGB")
try:
multiplied = ImageChops.multiply(base_rgb, tex_rgb)
except Exception:
multiplied = ImageChops.blend(base_rgb, tex_rgb, alpha=0.6)
multiplied_rgba = multiplied.convert("RGBA")
multiplied_rgba.putalpha(Image.new("L", base.size, 255))
result = Image.new("RGBA", base.size, (0, 0, 0, 0))
result = Image.composite(multiplied_rgba, result, opaque_mask)
if translucent_mask.getbbox() is not None:
blended = Image.composite(multiplied_rgba, base.convert("RGBA"),
translucent_mask)
result = Image.composite(blended, result, translucent_mask)
combined_mask = ImageChops.lighter(opaque_mask, translucent_mask)
inverse = ImageChops.invert(combined_mask)
result = Image.composite(base.convert("RGBA"), result, inverse)
return result
# state
self.texture_bytes: bytes | None = initial_texture_bytes
self.texture_url: str | None = None
self.seen_textures: set[str] = set()
self.base_seen_urls: list[set[str]] = [set() for _ in base_bytes_list]
self.using_realistic_channel = False
self.goth = False
self.reset_mode = False
self.shades_mode = False
# zapat (exclusion)
self.zapat_active = False
self.zapat_masks_per_base: list[Image.Image | None] = [None for _ in
base_bytes_list]
def get_block_text(self):
bot_name = (bot.user.name if bot.user and getattr(bot.user, "name", None)
else "botuser")
bot_name_short = f"{bot_name} bot"
ext = self.base_ext or ".png"
return (
"{\n"
"Name Tag:\n"
f"{self.name_tag}.\n\n"
"Extensión:\n"
f"{ext}\n\n"
"Versión:\n"
f"{bot_name_short} \n\n"
"Developer:\n"
"Dizzy\n"
"}"
)
if self.shades_mode:
try:
alpha = base_img.split()[3]
gray = ImageOps.grayscale(base_img)
base_img = gray.convert("RGBA")
base_img.putalpha(alpha)
except Exception:
pass
zapat_mask_for_this = None
if self.zapat_active and idx < len(self.zapat_masks_per_base):
if self.zapat_masks_per_base[idx] is None:
try:
gen = generate_zapat_mask_for_base(base_img)
self.zapat_masks_per_base[idx] = gen
except Exception:
self.zapat_masks_per_base[idx] = None
zapat_mask_for_this = self.zapat_masks_per_base[idx]
base_for_texture = base_img
if self.texture_bytes:
try:
alpha = base_img.split()[3]
gray = ImageOps.grayscale(base_img).convert("RGBA")
gray.putalpha(alpha)
base_for_texture = gray
except Exception:
base_for_texture = base_img
composed = None
if self.texture_bytes:
try:
tex_img =
Image.open(io.BytesIO(self.texture_bytes)).convert("RGBA")
except Exception:
tex_img = None
if tex_img:
# for pants: offset down 25%
offset = 0
exclude_shoes_flag = False
if len(self.base_bytes_list) > 1 and idx == 1:
offset = int(base_for_texture.size[1] * 0.25) # shift DOWN
25%
# default: do NOT exclude shoes; only exclude if
zapat_active true or explicitly asked
exclude_shoes_flag = False
else:
offset = 0
exclude_shoes_flag = False
composed = await
asyncio.to_thread(apply_texture_preserving_translucency_with_offset,
base_for_texture, tex_img,
offset, zapat_mask=zapat_mask_for_this, exclude_shoes=exclude_shoes_flag)
else:
full_mask, _, _ = detect_mask_from_base(base_for_texture)
tmp = base_for_texture.convert("RGB").convert("RGBA")
tmp.putalpha(full_mask)
composed = tmp
else:
full_mask, _, _ = detect_mask_from_base(base_img)
tmp = base_img.convert("RGB").convert("RGBA")
tmp.putalpha(full_mask)
composed = tmp
if composed is None:
return None
if self.goth:
try:
# goth: adjust brightness/contrast by factor
rgb = composed.convert("RGB")
enhancer = ImageEnhance.Color(rgb)
rgb_en = enhancer.enhance(1.0)
contrast = ImageEnhance.Contrast(rgb_en)
rgb_en = contrast.enhance(1.15)
bright = ImageEnhance.Brightness(rgb_en)
rgb_en = bright.enhance(1.05)
rgb_final = rgb_en.convert("RGBA")
alpha = composed.split()[3]
rgb_final.putalpha(alpha)
composed = rgb_final
except Exception:
pass
try:
out = io.BytesIO()
composed.save(out, "PNG")
out.seek(0)
final_clean = remove_green_from_bytes(out.getvalue())
results.append(final_clean)
except Exception:
return None
return results
try:
palette_msg = await
interaction.channel.send(f"{interaction.user.mention} Elige un color:",
view=palette)
palette_msg_holder["msg"] = palette_msg
try:
await interaction.response.defer()
except Exception:
pass
except Exception:
try:
await interaction.response.send_message("No pude mostrar la
paleta.", ephemeral=True)
except Exception:
pass
# Shades button
@discord.ui.button(label="Shades", style=discord.ButtonStyle.primary, row=0)
async def shades_button(self, interaction: discord.Interaction, button:
discord.ui.Button):
if interaction.user.id != self.author_id:
try:
await interaction.response.send_message("Solo el autor puede usar
estos botones.", ephemeral=True)
except Exception:
pass
return
self.shades_mode = not self.shades_mode
try:
await interaction.response.defer()
except Exception:
pass
channel = interaction.channel
loading = None
try:
msg_text = "Activando Shades..." if self.shades_mode else "Desactivando
Shades..."
loading = await channel.send(f"{interaction.user.mention} {msg_text}")
except Exception:
loading = None
try:
await delete_last_result_message(interaction.user.id)
except Exception:
pass
originals = []
for i, b in enumerate(self.base_bytes_list):
originals.append((f"orig_base_{i+1}.png", b))
if self.texture_bytes:
originals.append(("orig_texture.png", self.texture_bytes))
await upload_originals_to_trash_simple(interaction.user, originals)
new_list = await self.build_image_bytes_from_state()
if loading:
try:
await loading.delete()
except Exception:
pass
if not new_list:
try:
await channel.send(f"{interaction.user.mention} Error aplicando
Shades.")
except Exception:
pass
return
files = []
for idx, nb in enumerate(new_list):
fn = f"result{self.base_ext}" if len(new_list) == 1 else
f"result_{idx+1}{self.base_ext}"
files.append((fn, nb))
new_view = ImageEditView(self.author_id, self.base_bytes_list,
self.base_ext, self.origin_channel_id, initial_texture_bytes=self.texture_bytes,
has_shirt=self.has_shirt, has_pants=self.has_pants)
new_view.texture_bytes = self.texture_bytes
new_view.texture_url = self.texture_url
new_view.seen_textures = self.seen_textures
new_view.base_seen_urls = self.base_seen_urls
new_view.using_realistic_channel = self.using_realistic_channel
new_view.goth = self.goth
new_view.shades_mode = self.shades_mode
new_view.zapat_active = self.zapat_active
new_view.zapat_masks_per_base = self.zapat_masks_per_base
new_view.name_tag = self.name_tag
try:
await interaction.message.delete()
except Exception:
pass
await send_to_channel_and_store(channel, interaction.user,
new_view.get_block_text(), files, new_view)
# Randomize
@discord.ui.button(label="Randomize", style=discord.ButtonStyle.primary, row=0)
async def randomize_button(self, interaction: discord.Interaction, button:
discord.ui.Button):
if interaction.user.id != self.author_id:
try:
await interaction.response.send_message("Solo el autor puede usar
estos botones.", ephemeral=True)
except Exception:
pass
return
try:
await interaction.response.defer()
except Exception:
pass
channel = interaction.channel
loading = None
try:
loading = await channel.send(f"{interaction.user.mention} Randomizando
base(s) y textura...")
except Exception:
loading = None
new_bases = []
for idx, chid in enumerate((CHANNEL_BASES_SHIRT, CHANNEL_BASES_PANTS)
[:len(self.base_bytes_list)]):
b, url = await fetch_random_attachment_bytes_nonrepeat(chid,
exclude_urls=self.base_seen_urls[idx], limit=HISTORY_LIMIT)
if b:
b = remove_green_from_bytes(b)
new_bases.append((idx, b, url))
else:
new_bases.append((idx, self.base_bytes_list[idx], None))
try:
await delete_last_result_message(interaction.user.id)
except Exception:
pass
originals = []
for i, b in enumerate(self.base_bytes_list):
originals.append((f"orig_base_{i+1}.png", b))
if self.texture_bytes:
originals.append(("orig_texture.png", self.texture_bytes))
await upload_originals_to_trash_simple(interaction.user, originals)
# Shirt
@discord.ui.button(label="Shirt", style=discord.ButtonStyle.primary, row=1)
async def shirt_button(self, interaction: discord.Interaction, button:
discord.ui.Button):
if interaction.user.id != self.author_id:
try:
await interaction.response.send_message("Solo el autor puede usar
estos botones.", ephemeral=True)
except Exception:
pass
return
if not self.has_shirt:
try:
await interaction.response.send_message("No hay base de shirt en
este mensaje.", ephemeral=True)
except Exception:
pass
return
try:
await interaction.response.defer()
except Exception:
pass
b, url = await fetch_random_attachment_bytes_nonrepeat(CHANNEL_BASES_SHIRT,
exclude_urls=self.base_seen_urls[0], limit=HISTORY_LIMIT)
if not b:
try:
await interaction.followup.send("No encontré una nueva base de
shirt.", ephemeral=True)
except Exception:
pass
return
if url:
self.base_seen_urls[0].add(url)
b = remove_green_from_bytes(b)
self.base_bytes_list[0] = b
channel = interaction.channel
loading = None
try:
loading = await channel.send(f"{interaction.user.mention} Cargando
nueva shirt...")
except Exception:
loading = None
try:
await delete_last_result_message(interaction.user.id)
except Exception:
pass
originals = []
for i, bb in enumerate(self.base_bytes_list):
originals.append((f"orig_base_{i+1}.png", bb))
if self.texture_bytes:
originals.append(("orig_texture.png", self.texture_bytes))
await upload_originals_to_trash_simple(interaction.user, originals)
new_list = await self.build_image_bytes_from_state()
if loading:
try:
await loading.delete()
except Exception:
pass
if not new_list:
return
files = []
for idx, nb in enumerate(new_list):
fn = f"result{self.base_ext}" if len(new_list) == 1 else
f"result_{idx+1}{self.base_ext}"
files.append((fn, nb))
new_view = ImageEditView(self.author_id, self.base_bytes_list,
self.base_ext, self.origin_channel_id, initial_texture_bytes=self.texture_bytes,
has_shirt=self.has_shirt, has_pants=self.has_pants)
new_view.texture_bytes = self.texture_bytes
new_view.texture_url = self.texture_url
new_view.seen_textures = self.seen_textures
new_view.base_seen_urls = self.base_seen_urls
new_view.using_realistic_channel = self.using_realistic_channel
new_view.goth = self.goth
new_view.shades_mode = self.shades_mode
new_view.zapat_active = self.zapat_active
new_view.zapat_masks_per_base = self.zapat_masks_per_base
new_view.name_tag = self.name_tag
try:
await interaction.message.delete()
except Exception:
pass
await send_to_channel_and_store(channel, interaction.user,
new_view.get_block_text(), files, new_view)
# Pants
@discord.ui.button(label="Pants", style=discord.ButtonStyle.primary, row=1)
async def pants_button(self, interaction: discord.Interaction, button:
discord.ui.Button):
if interaction.user.id != self.author_id:
try:
await interaction.response.send_message("Solo el autor puede usar
estos botones.", ephemeral=True)
except Exception:
pass
return
if not self.has_pants:
try:
await interaction.response.send_message("No hay base de pants en
este mensaje.", ephemeral=True)
except Exception:
pass
return
try:
await interaction.response.defer()
except Exception:
pass
if len(self.base_bytes_list) < 2:
try:
await interaction.followup.send("No hay base de pants en este
mensaje.", ephemeral=True)
except Exception:
pass
return
b, url = await fetch_random_attachment_bytes_nonrepeat(CHANNEL_BASES_PANTS,
exclude_urls=self.base_seen_urls[1], limit=HISTORY_LIMIT)
if not b:
try:
await interaction.followup.send("No encontré una nueva base de
pants.", ephemeral=True)
except Exception:
pass
return
if url:
self.base_seen_urls[1].add(url)
b = remove_green_from_bytes(b)
self.base_bytes_list[1] = b
channel = interaction.channel
loading = None
try:
loading = await channel.send(f"{interaction.user.mention} Cargando
nueva pants...")
except Exception:
loading = None
try:
await delete_last_result_message(interaction.user.id)
except Exception:
pass
originals = []
for i, bb in enumerate(self.base_bytes_list):
originals.append((f"orig_base_{i+1}.png", bb))
if self.texture_bytes:
originals.append(("orig_texture.png", self.texture_bytes))
await upload_originals_to_trash_simple(interaction.user, originals)
new_list = await self.build_image_bytes_from_state()
if loading:
try:
await loading.delete()
except Exception:
pass
if not new_list:
return
files = []
for idx, nb in enumerate(new_list):
fn = f"result{self.base_ext}" if len(new_list) == 1 else
f"result_{idx+1}{self.base_ext}"
files.append((fn, nb))
new_view = ImageEditView(self.author_id, self.base_bytes_list,
self.base_ext, self.origin_channel_id, initial_texture_bytes=self.texture_bytes,
has_shirt=self.has_shirt, has_pants=self.has_pants)
new_view.texture_bytes = self.texture_bytes
new_view.texture_url = self.texture_url
new_view.seen_textures = self.seen_textures
new_view.base_seen_urls = self.base_seen_urls
new_view.using_realistic_channel = self.using_realistic_channel
new_view.goth = self.goth
new_view.shades_mode = self.shades_mode
new_view.zapat_active = self.zapat_active
new_view.zapat_masks_per_base = self.zapat_masks_per_base
new_view.name_tag = self.name_tag
try:
await interaction.message.delete()
except Exception:
pass
await send_to_channel_and_store(channel, interaction.user,
new_view.get_block_text(), files, new_view)
# Tela (realistic)
@discord.ui.button(label="Tela", style=discord.ButtonStyle.primary, row=2)
async def tela_button(self, interaction: discord.Interaction, button:
discord.ui.Button):
if interaction.user.id != self.author_id:
try:
await interaction.response.send_message("Solo el autor puede usar
estos botones.", ephemeral=True)
except Exception:
pass
return
try:
await interaction.response.defer()
except Exception:
pass
tex_b, tex_url = await
fetch_random_attachment_bytes_nonrepeat(CHANNEL_TEXTURES_REALISTIC,
exclude_urls=self.seen_textures, limit=HISTORY_LIMIT)
if not tex_b:
try:
await interaction.followup.send("No pude obtener una textura
Tela.", ephemeral=True)
except Exception:
pass
return
if tex_url:
self.seen_textures.add(tex_url)
self.texture_bytes = tex_b
self.texture_url = tex_url
self.using_realistic_channel = True
channel = interaction.channel
loading = None
try:
loading = await channel.send(f"{interaction.user.mention} Cargando
Tela...")
except Exception:
loading = None
try:
await delete_last_result_message(interaction.user.id)
except Exception:
pass
originals = []
for i, b in enumerate(self.base_bytes_list):
originals.append((f"orig_base_{i+1}.png", b))
if self.texture_bytes:
originals.append(("orig_texture.png", self.texture_bytes))
await upload_originals_to_trash_simple(interaction.user, originals)
new_list = await self.build_image_bytes_from_state()
if loading:
try:
await loading.delete()
except Exception:
pass
if not new_list:
return
files = []
for idx, nb in enumerate(new_list):
fn = f"result{self.base_ext}" if len(new_list) == 1 else
f"result_{idx+1}{self.base_ext}"
files.append((fn, nb))
new_view = ImageEditView(self.author_id, self.base_bytes_list,
self.base_ext, self.origin_channel_id, initial_texture_bytes=self.texture_bytes,
has_shirt=self.has_shirt, has_pants=self.has_pants)
new_view.texture_bytes = self.texture_bytes
new_view.texture_url = self.texture_url
new_view.seen_textures = self.seen_textures
new_view.base_seen_urls = self.base_seen_urls
new_view.using_realistic_channel = self.using_realistic_channel
new_view.goth = self.goth
new_view.shades_mode = self.shades_mode
new_view.zapat_active = self.zapat_active
new_view.zapat_masks_per_base = self.zapat_masks_per_base
new_view.name_tag = self.name_tag
try:
await interaction.message.delete()
except Exception:
pass
await send_to_channel_and_store(channel, interaction.user,
new_view.get_block_text(), files, new_view)
# Goth
@discord.ui.button(label="Goth", style=discord.ButtonStyle.secondary, row=2)
async def goth_button(self, interaction: discord.Interaction, button:
discord.ui.Button):
if interaction.user.id != self.author_id:
try:
await interaction.response.send_message("Solo el autor puede usar
estos botones.", ephemeral=True)
except Exception:
pass
return
try:
await interaction.response.defer()
except Exception:
pass
self.goth = not self.goth
channel = interaction.channel
loading = None
try:
loading = await channel.send(f"{interaction.user.mention} Aplicando
Goth...")
except Exception:
loading = None
try:
await delete_last_result_message(interaction.user.id)
except Exception:
pass
originals = []
for i, b in enumerate(self.base_bytes_list):
originals.append((f"orig_base_{i+1}.png", b))
if self.texture_bytes:
originals.append(("orig_texture.png", self.texture_bytes))
await upload_originals_to_trash_simple(interaction.user, originals)
new_list = await self.build_image_bytes_from_state()
if loading:
try:
await loading.delete()
except Exception:
pass
if not new_list:
return
files = []
for idx, nb in enumerate(new_list):
fn = f"result{self.base_ext}" if len(new_list) == 1 else
f"result_{idx+1}{self.base_ext}"
files.append((fn, nb))
new_view = ImageEditView(self.author_id, self.base_bytes_list,
self.base_ext, self.origin_channel_id, initial_texture_bytes=self.texture_bytes,
has_shirt=self.has_shirt, has_pants=self.has_pants)
new_view.texture_bytes = self.texture_bytes
new_view.texture_url = self.texture_url
new_view.seen_textures = self.seen_textures
new_view.base_seen_urls = self.base_seen_urls
new_view.using_realistic_channel = self.using_realistic_channel
new_view.goth = self.goth
new_view.shades_mode = self.shades_mode
new_view.zapat_active = self.zapat_active
new_view.zapat_masks_per_base = self.zapat_masks_per_base
new_view.name_tag = self.name_tag
try:
await interaction.message.delete()
except Exception:
pass
await send_to_channel_and_store(channel, interaction.user,
new_view.get_block_text(), files, new_view)
# Reset
@discord.ui.button(label="Reset", style=discord.ButtonStyle.secondary, row=3)
async def reset_button(self, interaction: discord.Interaction, button:
discord.ui.Button):
if interaction.user.id != self.author_id:
try:
await interaction.response.send_message("Solo el autor puede usar
estos buttons.", ephemeral=True)
except Exception:
pass
return
try:
await interaction.response.defer()
except Exception:
pass
channel = interaction.channel
loading = None
try:
loading = await channel.send(f"{interaction.user.mention} Reseteando...
(ADVERTENCIA: no se recomienda subir imágenes base sin textura).")
except Exception:
loading = None
self.texture_bytes = None
self.texture_url = None
self.seen_textures.clear()
self.goth = False
self.shades_mode = False
self.reset_mode = True
self.zapat_active = False
self.zapat_masks_per_base = [None for _ in self.base_bytes_list]
try:
await delete_last_result_message(interaction.user.id)
except Exception:
pass
originals = []
for i, b in enumerate(self.base_bytes_list):
originals.append((f"orig_base_{i+1}.png", b))
await upload_originals_to_trash_simple(interaction.user, originals)
new_list = []
for b in self.base_bytes_list:
nb = await overlay_bytes_on_bytes(b)
new_list.append(nb)
if loading:
try:
await loading.delete()
except Exception:
pass
content = self.get_block_text() + "\n\n**ADVERTENCIA:** Estas son las
imágenes base sin textura. No se recomienda subirlas ya que pueden tener
consecuencias."
files = []
for idx, nb in enumerate(new_list):
fn = f"base{self.base_ext}" if len(new_list) == 1 else f"base_{idx+1}
{self.base_ext}"
files.append((fn, nb))
new_view = ImageEditView(self.author_id, self.base_bytes_list,
self.base_ext, self.origin_channel_id, initial_texture_bytes=None,
has_shirt=self.has_shirt, has_pants=self.has_pants)
new_view.name_tag = self.name_tag
try:
await interaction.message.delete()
except Exception:
pass
await send_to_channel_and_store(channel, interaction.user, content, files,
new_view)
# base-original
@discord.ui.button(label="base-original", style=discord.ButtonStyle.danger,
row=3)
async def base_original_button(self, interaction: discord.Interaction, button:
discord.ui.Button):
if interaction.user.id != self.author_id:
try:
await interaction.response.send_message("Solo el autor puede usar
estos buttons.", ephemeral=True)
except Exception:
pass
return
try:
await interaction.response.defer()
except Exception:
pass
channel = interaction.channel
try:
await delete_last_result_message(interaction.user.id)
except Exception:
pass
originals = []
for i, b in enumerate(self.base_bytes_list):
originals.append((f"orig_base_{i+1}.png", b))
await upload_originals_to_trash_simple(interaction.user, originals)
files = []
for idx, b in enumerate(self.base_bytes_list):
clean = remove_green_from_bytes(b)
fn = f"base{self.base_ext}" if len(self.base_bytes_list) == 1 else
f"base_{idx+1}{self.base_ext}"
files.append((fn, clean))
content = self.get_block_text() + "\n\n**ADVERTENCIA:** Estas son las
imágenes base sin textura. No se recomienda subirlas ya que pueden tener
consecuencias."
new_view = ImageEditView(self.author_id, self.base_bytes_list,
self.base_ext, self.origin_channel_id, initial_texture_bytes=self.texture_bytes,
has_shirt=self.has_shirt, has_pants=self.has_pants)
new_view.name_tag = self.name_tag
try:
await interaction.message.delete()
except Exception:
pass
await send_to_channel_and_store(channel, interaction.user, content, files,
new_view)
# Zapat / Unzapat
@discord.ui.button(label="Zapat", style=discord.ButtonStyle.secondary, row=2)
async def zapat_button(self, interaction: discord.Interaction, button:
discord.ui.Button):
if interaction.user.id != self.author_id:
try:
await interaction.response.send_message("Solo el autor puede usar
estos buttons.", ephemeral=True)
except Exception:
pass
return
if not self.has_pants or len(self.base_bytes_list) < 2:
try:
await interaction.response.send_message("Zapat solo aplica a
mensajes con pants.", ephemeral=True)
except Exception:
pass
return
try:
await interaction.response.defer()
except Exception:
pass
# toggle
self.zapat_active = not self.zapat_active
if self.zapat_active:
try:
base_b = self.base_bytes_list[1]
base_img = Image.open(io.BytesIO(base_b)).convert("RGBA")
mask = generate_zapat_mask_for_base(base_img)
self.zapat_masks_per_base[1] = mask
except Exception:
self.zapat_masks_per_base[1] = None
else:
self.zapat_masks_per_base[1] = None
channel = interaction.channel
loading = None
try:
loading = await channel.send(f"{interaction.user.mention} {'Aplicando
Zapat...' if self.zapat_active else 'Reaplicando textura completa...'}")
except Exception:
loading = None
try:
await delete_last_result_message(interaction.user.id)
except Exception:
pass
originals = []
for i, b in enumerate(self.base_bytes_list):
originals.append((f"orig_base_{i+1}.png", b))
if self.texture_bytes:
originals.append(("orig_texture.png", self.texture_bytes))
await upload_originals_to_trash_simple(interaction.user, originals)
new_list = await self.build_image_bytes_from_state()
if loading:
try:
await loading.delete()
except Exception:
pass
if not new_list:
try:
await channel.send(f"{interaction.user.mention} Error aplicando
Zapat.")
except Exception:
pass
return
files = []
for idx, nb in enumerate(new_list):
fn = f"result{self.base_ext}" if len(new_list) == 1 else
f"result_{idx+1}{self.base_ext}"
files.append((fn, nb))
new_view = ImageEditView(self.author_id, self.base_bytes_list,
self.base_ext, self.origin_channel_id, initial_texture_bytes=self.texture_bytes,
has_shirt=self.has_shirt, has_pants=self.has_pants)
new_view.texture_bytes = self.texture_bytes
new_view.texture_url = self.texture_url
new_view.seen_textures = self.seen_textures
new_view.base_seen_urls = self.base_seen_urls
new_view.using_realistic_channel = self.using_realistic_channel
new_view.goth = self.goth
new_view.shades_mode = self.shades_mode
new_view.zapat_active = self.zapat_active
new_view.zapat_masks_per_base = self.zapat_masks_per_base
new_view.name_tag = self.name_tag
for it in new_view.children:
if isinstance(it, discord.ui.Button) and it.label in ("Zapat",
"Unzapat"):
it.label = "Unzapat" if new_view.zapat_active else "Zapat"
try:
await interaction.message.delete()
except Exception:
pass
await send_to_channel_and_store(channel, interaction.user,
new_view.get_block_text(), files, new_view)
processed_bases = []
for b in base_bytes_list:
try:
nb = remove_green_from_bytes(b)
processed_bases.append(nb)
except Exception:
processed_bases.append(b)
ext = detect_extension_from_bytes(processed_bases[0])
has_shirt = len(processed_bases) >= 1 and (is_shirt_cmd or not is_pants_cmd)
has_pants = len(processed_bases) >= 2 or (is_pants_cmd and len(processed_bases)
>= 1)
view = ImageEditView(ctx.author.id, processed_bases, ext, ctx.channel.id,
initial_texture_bytes=tex_b, has_shirt=has_shirt, has_pants=has_pants)
if tex_url:
view.texture_url = tex_url
view.seen_textures.add(tex_url)
if tex_b:
new_list = await view.build_image_bytes_from_state()
if not new_list:
base_with_overlay_list = []
for b in processed_bases:
nb = await overlay_bytes_on_bytes(b)
base_with_overlay_list.append(nb)
files = []
for idx, nb in enumerate(base_with_overlay_list):
fn = f"base{ext}" if len(base_with_overlay_list) == 1 else
f"base_{idx+1}{ext}"
files.append((fn, nb))
originals = []
for idx, b in enumerate(processed_bases):
originals.append((f"orig_base_{idx+1}.png", b))
if tex_b:
originals.append(("orig_texture.png", tex_b))
await upload_originals_to_trash_simple(ctx.author, originals)
sent = await send_to_channel_and_store(ctx.channel, ctx.author,
view.get_block_text(), files, view)
return sent
else:
files = []
for idx, nb in enumerate(new_list):
fn = f"result{ext}" if len(new_list) == 1 else f"result_{idx+1}
{ext}"
files.append((fn, nb))
originals = []
for idx, b in enumerate(processed_bases):
originals.append((f"orig_base_{idx+1}.png", b))
if tex_b:
originals.append(("orig_texture.png", tex_b))
await upload_originals_to_trash_simple(ctx.author, originals)
sent = await send_to_channel_and_store(ctx.channel, ctx.author,
view.get_block_text(), files, view)
return sent
else:
base_with_overlay_list = []
for b in processed_bases:
nb = await overlay_bytes_on_bytes(b)
base_with_overlay_list.append(nb)
files = []
for idx, nb in enumerate(base_with_overlay_list):
fn = f"base{ext}" if len(base_with_overlay_list) == 1 else
f"base_{idx+1}{ext}"
files.append((fn, nb))
originals = []
for idx, b in enumerate(processed_bases):
originals.append((f"orig_base_{idx+1}.png", b))
await upload_originals_to_trash_simple(ctx.author, originals)
sent = await send_to_channel_and_store(ctx.channel, ctx.author,
view.get_block_text(), files, view)
return sent
@bot.command(name="shirt")
async def shirt_cmd(ctx: commands.Context):
if ctx.guild is None:
return
base_b, base_url = await
fetch_random_attachment_bytes_nonrepeat(CHANNEL_BASES_SHIRT, exclude_urls=set(),
limit=HISTORY_LIMIT)
if not base_b:
return
await send_base_with_interactive_view(ctx, [base_b], is_shirt_cmd=True,
is_pants_cmd=False)
@bot.command(name="pants")
async def pants_cmd(ctx: commands.Context):
if ctx.guild is None:
return
base_b, base_url = await
fetch_random_attachment_bytes_nonrepeat(CHANNEL_BASES_PANTS, exclude_urls=set(),
limit=HISTORY_LIMIT)
if not base_b:
return
await send_base_with_interactive_view(ctx, [base_b], is_shirt_cmd=False,
is_pants_cmd=True)
@bot.command(name="conjunto")
async def conjunto_cmd(ctx: commands.Context):
if ctx.guild is None:
return
shirt_b, _ = await fetch_random_attachment_bytes_nonrepeat(CHANNEL_BASES_SHIRT,
exclude_urls=set(), limit=HISTORY_LIMIT)
pants_b, _ = await fetch_random_attachment_bytes_nonrepeat(CHANNEL_BASES_PANTS,
exclude_urls=set(), limit=HISTORY_LIMIT)
bases = []
if shirt_b:
bases.append(shirt_b)
if pants_b:
bases.append(pants_b)
if not bases:
return
await send_base_with_interactive_view(ctx, bases, is_shirt_cmd=False,
is_pants_cmd=False)
@bot.command(name="untest")
async def untest_cmd(ctx: commands.Context):
if ctx.guild is None:
return
TEST_MODE.pop(ctx.author.id, None)
try:
await ctx.message.delete()
except Exception:
pass
@bot.event
async def on_ready():
log.info(f"Bot conectado como {bot.user} (ID: {bot.user.id})")