fix gif embeds in shuttle mode

This commit is contained in:
rambros 2026-03-10 15:59:12 +05:30
parent 478c025754
commit e9b87042e1
5 changed files with 20 additions and 92 deletions

View file

@ -691,7 +691,6 @@ class BackupReader:
def __init__(self, backup_path: str | Path):
self.backup_path = Path(backup_path)
self.guild: BackupGuild | None = None
self.role_map: Dict[int, str] = {}
# Internal caches populated by start()
self._categories: List[BackupCategory] = []
@ -724,7 +723,6 @@ class BackupReader:
if roles_file.exists():
roles_data = json.loads(roles_file.read_text(encoding="utf-8"))
self._roles = [BackupRole(r) for r in roles_data]
self.role_map = {r.id: r.name for r in self._roles}
logger.info(f"[Backup] Loaded {len(self._roles)} roles")
# 3. Structure -> categories + channels

View file

@ -61,14 +61,6 @@ class DiscordReader:
self.guild: discord.Guild | None = None
self.client: discord.Client | None = None
self.role_map: Dict[int, str] = {}
self.channel_name_map: Dict[int, str] = {}
# Session-level caches to avoid redundant fetch calls
self._roles_cache: list[discord.Role] | None = None
self._channels_cache: list[discord.abc.GuildChannel] | None = None
self._categories_cache: list[discord.CategoryChannel] | None = None
self._emojis_cache: list[discord.Emoji] | None = None
self._stickers_cache: list[discord.GuildSticker] | None = None
def _create_client(self):
intents = discord.Intents.default()
@ -86,6 +78,7 @@ class DiscordReader:
await self.client.login(self.token)
# Use fetch methods specifically to bypass dependency on gateway cache
# fetch_guild initializes the guild object needed for subsequent API calls
try:
logger.info(f"Fetching guild {self.server_id}...")
self.guild = await self.client.fetch_guild(self.server_id)
@ -93,32 +86,9 @@ class DiscordReader:
except discord.Forbidden:
logger.error(f"403 Forbidden: Missing Access to fetch guild {self.server_id}.")
raise
# Pre-fetch roles via API - Handle Forbidden gracefully
try:
roles = await self.guild.fetch_roles()
self.role_map = {r.id: r.name for r in roles}
self._roles_cache = [r for r in roles if not r.is_default()]
except discord.Forbidden:
logger.warning("403 Forbidden: Missing Access to fetch roles. Continuing without role mapping.")
self.role_map = {}
except Exception as e:
logger.error(f"Failed to fetch roles: {e}")
self.role_map = {}
# Pre-fetch channels via API
try:
channels = await self.guild.fetch_channels()
self.channel_name_map = {c.id: c.name for c in channels}
self._channels_cache = [c for c in channels if not isinstance(c, discord.CategoryChannel)]
self._categories_cache = [c for c in channels if isinstance(c, discord.CategoryChannel)]
logger.debug(f"Pre-fetched {len(self.channel_name_map)} channels")
except discord.Forbidden:
logger.warning("403 Forbidden: Missing Access to fetch channels. Continuing without channel name mapping.")
self.channel_name_map = {}
except Exception as e:
logger.error(f"Failed to fetch channels: {e}")
self.channel_name_map = {}
logger.error(f"Failed to fetch guild {self.server_id}: {e}")
raise
async def validate(self) -> Dict[str, Any]:
"""Validates the token, server ID, intents, and permissions."""
@ -180,40 +150,27 @@ class DiscordReader:
async def get_categories(self):
if not self.guild:
return []
if self._categories_cache is not None:
return self._categories_cache
categories = await self.guild.fetch_channels()
self._categories_cache = [c for c in categories if isinstance(c, discord.CategoryChannel)]
return self._categories_cache
return [c for c in categories if isinstance(c, discord.CategoryChannel)]
async def get_roles(self):
"""Returns all roles in the server (excluding @everyone)."""
if not self.guild:
return []
if self._roles_cache is not None:
return self._roles_cache
roles = await self.guild.fetch_roles()
# Filter out default @everyone role which cannot typically be created
self._roles_cache = [r for r in roles if not r.is_default()]
return self._roles_cache
return [r for r in roles if not r.is_default()]
async def get_emojis(self):
"""Returns all custom emojis in the server."""
if not self.guild:
return []
if self._emojis_cache is not None:
return self._emojis_cache
self._emojis_cache = await self.guild.fetch_emojis()
return self._emojis_cache
return await self.guild.fetch_emojis()
async def get_stickers(self):
"""Returns all custom stickers in the server."""
if not self.guild:
return []
if self._stickers_cache is not None:
return self._stickers_cache
self._stickers_cache = await self.guild.fetch_stickers()
return self._stickers_cache
return await self.guild.fetch_stickers()
async def get_members(self):
"""Returns all members in the server."""
@ -230,11 +187,9 @@ class DiscordReader:
if not self.guild:
return []
if self._channels_cache is None:
channels = await self.guild.fetch_channels()
self._channels_cache = [c for c in channels if not isinstance(c, discord.CategoryChannel)]
all_channels = [c for c in channels if not isinstance(c, discord.CategoryChannel)]
all_channels = self._channels_cache
if category_id:
all_channels = [c for c in all_channels if c.category_id == category_id]
return all_channels
@ -284,11 +239,6 @@ class DiscordReader:
client = self.client
self.client = None # Atomic clear
self.guild = None
self._roles_cache = None
self._channels_cache = None
self._categories_cache = None
self._emojis_cache = None
self._stickers_cache = None
if client:
try:
await client.close()

View file

@ -8,7 +8,7 @@ from src.core.utils import resolve_discord_links
logger = logging.getLogger(__name__)
def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None, role_map=None, emoji_map=None, channel_map=None, discord_channel_map=None, state=None, target_server_id=None) -> str:
def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None, emoji_map=None, channel_map=None, state=None, target_server_id=None) -> str:
if not content or not guild:
return content
@ -47,10 +47,6 @@ def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None,
if role and role.name:
return f"`@{role.name}`"
# 4. Try provided role map
if role_map and rid in role_map:
return f"`@{role_map[rid]}`"
return match.group(0)
def replace_channel(match):
@ -61,13 +57,8 @@ def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None,
return f"<#{channel_map[str(cid)]}>"
# 2. Fallback to name in backticks
# Try metadata map first (robust)
if discord_channel_map and cid in discord_channel_map:
return f"`#{discord_channel_map[cid]}`"
# Try cache
channel = guild.get_channel(cid)
return f"`{channel.name}`" if channel else f"<#{cid}>"
return f"`#{channel.name}`" if channel else f"<#{cid}>"
def replace_emoji(match):
animated = match.group(1) == "a"
@ -196,10 +187,8 @@ async def migrate_messages(
msg.guild,
msg.mentions,
msg.role_mentions,
context.discord_reader.role_map,
context.state.emoji_map,
context.state.channel_map,
context.discord_reader.channel_name_map,
state=context.state,
target_server_id=context.fluxer_writer.community_id
)
@ -229,10 +218,8 @@ async def migrate_messages(
msg.guild,
snapshot.mentions if hasattr(snapshot, 'mentions') else None,
snapshot.role_mentions if hasattr(snapshot, 'role_mentions') else None,
context.discord_reader.role_map,
context.state.emoji_map,
context.state.channel_map,
context.discord_reader.channel_name_map,
state=context.state,
target_server_id=context.fluxer_writer.community_id
)

View file

@ -8,7 +8,7 @@ from src.core.utils import resolve_discord_links
logger = logging.getLogger(__name__)
def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None, role_map=None, emoji_map=None, channel_map=None, discord_channel_map=None, state=None, target_server_id=None) -> str:
def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None, emoji_map=None, channel_map=None, state=None, target_server_id=None) -> str:
if not content or not guild:
return content
@ -47,10 +47,6 @@ def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None,
if role and role.name:
return f"`@{role.name}`"
# 4. Try provided role map
if role_map and rid in role_map:
return f"`@{role_map[rid]}`"
return match.group(0)
def replace_channel(match):
@ -61,13 +57,8 @@ def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None,
return f"<#{channel_map[str(cid)]}>"
# 2. Fallback to name in backticks
# Try metadata map first (robust)
if discord_channel_map and cid in discord_channel_map:
return f"`#{discord_channel_map[cid]}`"
# Try cache
channel = guild.get_channel(cid)
return f"`{channel.name}`" if channel else f"<#{cid}>"
return f"`#{channel.name}`" if channel else f"<#{cid}>"
def replace_emoji(match):
animated = match.group(1) == "a"
@ -204,10 +195,8 @@ async def migrate_messages(
msg.guild,
msg.mentions,
msg.role_mentions,
context.discord_reader.role_map,
context.state.emoji_map,
context.state.channel_map,
context.discord_reader.channel_name_map,
state=context.state,
target_server_id=context.stoat_writer.community_id
)
@ -233,10 +222,8 @@ async def migrate_messages(
msg.guild,
snapshot.mentions if hasattr(snapshot, 'mentions') else None,
snapshot.role_mentions if hasattr(snapshot, 'role_mentions') else None,
context.discord_reader.role_map,
context.state.emoji_map,
context.state.channel_map,
context.discord_reader.channel_name_map,
state=context.state,
target_server_id=context.stoat_writer.community_id
)

View file

@ -337,7 +337,13 @@ class StoatWriter:
# Stoat requires SendableEmbed objects, not raw dicts
stoat_embeds = []
if embeds:
for e in embeds:
for raw_embed in embeds:
# Normalize embed to dict (handles both live discord.Embed and backup dicts)
if hasattr(raw_embed, "to_dict"):
e = raw_embed.to_dict()
else:
e = raw_embed
# Convert integer color to hex string if present
color = None
if e.get("color"):