fix gif embeds in shuttle mode
This commit is contained in:
parent
478c025754
commit
e9b87042e1
5 changed files with 20 additions and 92 deletions
|
|
@ -691,7 +691,6 @@ class BackupReader:
|
||||||
def __init__(self, backup_path: str | Path):
|
def __init__(self, backup_path: str | Path):
|
||||||
self.backup_path = Path(backup_path)
|
self.backup_path = Path(backup_path)
|
||||||
self.guild: BackupGuild | None = None
|
self.guild: BackupGuild | None = None
|
||||||
self.role_map: Dict[int, str] = {}
|
|
||||||
|
|
||||||
# Internal caches populated by start()
|
# Internal caches populated by start()
|
||||||
self._categories: List[BackupCategory] = []
|
self._categories: List[BackupCategory] = []
|
||||||
|
|
@ -724,7 +723,6 @@ class BackupReader:
|
||||||
if roles_file.exists():
|
if roles_file.exists():
|
||||||
roles_data = json.loads(roles_file.read_text(encoding="utf-8"))
|
roles_data = json.loads(roles_file.read_text(encoding="utf-8"))
|
||||||
self._roles = [BackupRole(r) for r in roles_data]
|
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")
|
logger.info(f"[Backup] Loaded {len(self._roles)} roles")
|
||||||
|
|
||||||
# 3. Structure -> categories + channels
|
# 3. Structure -> categories + channels
|
||||||
|
|
|
||||||
|
|
@ -61,14 +61,6 @@ class DiscordReader:
|
||||||
|
|
||||||
self.guild: discord.Guild | None = None
|
self.guild: discord.Guild | None = None
|
||||||
self.client: discord.Client | 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):
|
def _create_client(self):
|
||||||
intents = discord.Intents.default()
|
intents = discord.Intents.default()
|
||||||
|
|
@ -86,6 +78,7 @@ class DiscordReader:
|
||||||
await self.client.login(self.token)
|
await self.client.login(self.token)
|
||||||
|
|
||||||
# Use fetch methods specifically to bypass dependency on gateway cache
|
# Use fetch methods specifically to bypass dependency on gateway cache
|
||||||
|
# fetch_guild initializes the guild object needed for subsequent API calls
|
||||||
try:
|
try:
|
||||||
logger.info(f"Fetching guild {self.server_id}...")
|
logger.info(f"Fetching guild {self.server_id}...")
|
||||||
self.guild = await self.client.fetch_guild(self.server_id)
|
self.guild = await self.client.fetch_guild(self.server_id)
|
||||||
|
|
@ -93,32 +86,9 @@ class DiscordReader:
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
logger.error(f"403 Forbidden: Missing Access to fetch guild {self.server_id}.")
|
logger.error(f"403 Forbidden: Missing Access to fetch guild {self.server_id}.")
|
||||||
raise
|
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:
|
except Exception as e:
|
||||||
logger.error(f"Failed to fetch roles: {e}")
|
logger.error(f"Failed to fetch guild {self.server_id}: {e}")
|
||||||
self.role_map = {}
|
raise
|
||||||
|
|
||||||
# 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 = {}
|
|
||||||
|
|
||||||
async def validate(self) -> Dict[str, Any]:
|
async def validate(self) -> Dict[str, Any]:
|
||||||
"""Validates the token, server ID, intents, and permissions."""
|
"""Validates the token, server ID, intents, and permissions."""
|
||||||
|
|
@ -180,40 +150,27 @@ class DiscordReader:
|
||||||
async def get_categories(self):
|
async def get_categories(self):
|
||||||
if not self.guild:
|
if not self.guild:
|
||||||
return []
|
return []
|
||||||
if self._categories_cache is not None:
|
|
||||||
return self._categories_cache
|
|
||||||
categories = await self.guild.fetch_channels()
|
categories = await self.guild.fetch_channels()
|
||||||
self._categories_cache = [c for c in categories if isinstance(c, discord.CategoryChannel)]
|
return [c for c in categories if isinstance(c, discord.CategoryChannel)]
|
||||||
return self._categories_cache
|
|
||||||
|
|
||||||
async def get_roles(self):
|
async def get_roles(self):
|
||||||
"""Returns all roles in the server (excluding @everyone)."""
|
"""Returns all roles in the server (excluding @everyone)."""
|
||||||
if not self.guild:
|
if not self.guild:
|
||||||
return []
|
return []
|
||||||
if self._roles_cache is not None:
|
|
||||||
return self._roles_cache
|
|
||||||
roles = await self.guild.fetch_roles()
|
roles = await self.guild.fetch_roles()
|
||||||
# Filter out default @everyone role which cannot typically be created
|
return [r for r in roles if not r.is_default()]
|
||||||
self._roles_cache = [r for r in roles if not r.is_default()]
|
|
||||||
return self._roles_cache
|
|
||||||
|
|
||||||
async def get_emojis(self):
|
async def get_emojis(self):
|
||||||
"""Returns all custom emojis in the server."""
|
"""Returns all custom emojis in the server."""
|
||||||
if not self.guild:
|
if not self.guild:
|
||||||
return []
|
return []
|
||||||
if self._emojis_cache is not None:
|
return await self.guild.fetch_emojis()
|
||||||
return self._emojis_cache
|
|
||||||
self._emojis_cache = await self.guild.fetch_emojis()
|
|
||||||
return self._emojis_cache
|
|
||||||
|
|
||||||
async def get_stickers(self):
|
async def get_stickers(self):
|
||||||
"""Returns all custom stickers in the server."""
|
"""Returns all custom stickers in the server."""
|
||||||
if not self.guild:
|
if not self.guild:
|
||||||
return []
|
return []
|
||||||
if self._stickers_cache is not None:
|
return await self.guild.fetch_stickers()
|
||||||
return self._stickers_cache
|
|
||||||
self._stickers_cache = await self.guild.fetch_stickers()
|
|
||||||
return self._stickers_cache
|
|
||||||
|
|
||||||
async def get_members(self):
|
async def get_members(self):
|
||||||
"""Returns all members in the server."""
|
"""Returns all members in the server."""
|
||||||
|
|
@ -230,11 +187,9 @@ class DiscordReader:
|
||||||
if not self.guild:
|
if not self.guild:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if self._channels_cache is None:
|
channels = await self.guild.fetch_channels()
|
||||||
channels = await self.guild.fetch_channels()
|
all_channels = [c for c in channels if not isinstance(c, discord.CategoryChannel)]
|
||||||
self._channels_cache = [c for c in channels if not isinstance(c, discord.CategoryChannel)]
|
|
||||||
|
|
||||||
all_channels = self._channels_cache
|
|
||||||
if category_id:
|
if category_id:
|
||||||
all_channels = [c for c in all_channels if c.category_id == category_id]
|
all_channels = [c for c in all_channels if c.category_id == category_id]
|
||||||
return all_channels
|
return all_channels
|
||||||
|
|
@ -284,11 +239,6 @@ class DiscordReader:
|
||||||
client = self.client
|
client = self.client
|
||||||
self.client = None # Atomic clear
|
self.client = None # Atomic clear
|
||||||
self.guild = None
|
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:
|
if client:
|
||||||
try:
|
try:
|
||||||
await client.close()
|
await client.close()
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from src.core.utils import resolve_discord_links
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
if not content or not guild:
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
@ -47,10 +47,6 @@ def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None,
|
||||||
if role and role.name:
|
if role and role.name:
|
||||||
return f"`@{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)
|
return match.group(0)
|
||||||
|
|
||||||
def replace_channel(match):
|
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)]}>"
|
return f"<#{channel_map[str(cid)]}>"
|
||||||
|
|
||||||
# 2. Fallback to name in backticks
|
# 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)
|
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):
|
def replace_emoji(match):
|
||||||
animated = match.group(1) == "a"
|
animated = match.group(1) == "a"
|
||||||
|
|
@ -196,10 +187,8 @@ async def migrate_messages(
|
||||||
msg.guild,
|
msg.guild,
|
||||||
msg.mentions,
|
msg.mentions,
|
||||||
msg.role_mentions,
|
msg.role_mentions,
|
||||||
context.discord_reader.role_map,
|
|
||||||
context.state.emoji_map,
|
context.state.emoji_map,
|
||||||
context.state.channel_map,
|
context.state.channel_map,
|
||||||
context.discord_reader.channel_name_map,
|
|
||||||
state=context.state,
|
state=context.state,
|
||||||
target_server_id=context.fluxer_writer.community_id
|
target_server_id=context.fluxer_writer.community_id
|
||||||
)
|
)
|
||||||
|
|
@ -229,10 +218,8 @@ async def migrate_messages(
|
||||||
msg.guild,
|
msg.guild,
|
||||||
snapshot.mentions if hasattr(snapshot, 'mentions') else None,
|
snapshot.mentions if hasattr(snapshot, 'mentions') else None,
|
||||||
snapshot.role_mentions if hasattr(snapshot, 'role_mentions') else None,
|
snapshot.role_mentions if hasattr(snapshot, 'role_mentions') else None,
|
||||||
context.discord_reader.role_map,
|
|
||||||
context.state.emoji_map,
|
context.state.emoji_map,
|
||||||
context.state.channel_map,
|
context.state.channel_map,
|
||||||
context.discord_reader.channel_name_map,
|
|
||||||
state=context.state,
|
state=context.state,
|
||||||
target_server_id=context.fluxer_writer.community_id
|
target_server_id=context.fluxer_writer.community_id
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from src.core.utils import resolve_discord_links
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
if not content or not guild:
|
||||||
return content
|
return content
|
||||||
|
|
||||||
|
|
@ -47,10 +47,6 @@ def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None,
|
||||||
if role and role.name:
|
if role and role.name:
|
||||||
return f"`@{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)
|
return match.group(0)
|
||||||
|
|
||||||
def replace_channel(match):
|
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)]}>"
|
return f"<#{channel_map[str(cid)]}>"
|
||||||
|
|
||||||
# 2. Fallback to name in backticks
|
# 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)
|
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):
|
def replace_emoji(match):
|
||||||
animated = match.group(1) == "a"
|
animated = match.group(1) == "a"
|
||||||
|
|
@ -204,10 +195,8 @@ async def migrate_messages(
|
||||||
msg.guild,
|
msg.guild,
|
||||||
msg.mentions,
|
msg.mentions,
|
||||||
msg.role_mentions,
|
msg.role_mentions,
|
||||||
context.discord_reader.role_map,
|
|
||||||
context.state.emoji_map,
|
context.state.emoji_map,
|
||||||
context.state.channel_map,
|
context.state.channel_map,
|
||||||
context.discord_reader.channel_name_map,
|
|
||||||
state=context.state,
|
state=context.state,
|
||||||
target_server_id=context.stoat_writer.community_id
|
target_server_id=context.stoat_writer.community_id
|
||||||
)
|
)
|
||||||
|
|
@ -233,10 +222,8 @@ async def migrate_messages(
|
||||||
msg.guild,
|
msg.guild,
|
||||||
snapshot.mentions if hasattr(snapshot, 'mentions') else None,
|
snapshot.mentions if hasattr(snapshot, 'mentions') else None,
|
||||||
snapshot.role_mentions if hasattr(snapshot, 'role_mentions') else None,
|
snapshot.role_mentions if hasattr(snapshot, 'role_mentions') else None,
|
||||||
context.discord_reader.role_map,
|
|
||||||
context.state.emoji_map,
|
context.state.emoji_map,
|
||||||
context.state.channel_map,
|
context.state.channel_map,
|
||||||
context.discord_reader.channel_name_map,
|
|
||||||
state=context.state,
|
state=context.state,
|
||||||
target_server_id=context.stoat_writer.community_id
|
target_server_id=context.stoat_writer.community_id
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -337,7 +337,13 @@ class StoatWriter:
|
||||||
# Stoat requires SendableEmbed objects, not raw dicts
|
# Stoat requires SendableEmbed objects, not raw dicts
|
||||||
stoat_embeds = []
|
stoat_embeds = []
|
||||||
if 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
|
# Convert integer color to hex string if present
|
||||||
color = None
|
color = None
|
||||||
if e.get("color"):
|
if e.get("color"):
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue