diff --git a/src/core/discord_reader.py b/src/core/discord_reader.py index b29eaff..fcda75a 100644 --- a/src/core/discord_reader.py +++ b/src/core/discord_reader.py @@ -62,6 +62,7 @@ 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] = {} def _create_client(self): intents = discord.Intents.default() @@ -98,6 +99,18 @@ class DiscordReader: 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} + 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]: """Validates the token, server ID, intents, and permissions.""" results = { diff --git a/src/fluxer/migrate_message.py b/src/fluxer/migrate_message.py index e6fb0c1..716299b 100644 --- a/src/fluxer/migrate_message.py +++ b/src/fluxer/migrate_message.py @@ -7,7 +7,7 @@ from src.core.base import MigrationContext logger = logging.getLogger(__name__) -def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None, role_map=None, emoji_map=None) -> str: +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) -> str: if not content or not guild: return content @@ -54,8 +54,19 @@ def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None, def replace_channel(match): cid = int(match.group(1)) + + # 1. Check if channel is mapped in state + if channel_map and str(cid) in channel_map: + 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 match.group(0) + return f"`{channel.name}`" if channel else f"<#{cid}>" def replace_emoji(match): animated = match.group(1) == "a" @@ -180,7 +191,9 @@ async def migrate_messages( msg.mentions, msg.role_mentions, context.discord_reader.role_map, - context.state.emoji_map + context.state.emoji_map, + context.state.channel_map, + context.discord_reader.channel_name_map ) # Process attachments @@ -209,7 +222,9 @@ async def migrate_messages( 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.emoji_map, + context.state.channel_map, + context.discord_reader.channel_name_map ) # Add snapshot attachments to the list to process attachments_to_process.extend(snapshot.attachments) diff --git a/src/stoat/migrate_message.py b/src/stoat/migrate_message.py index f7db20f..4d535d7 100644 --- a/src/stoat/migrate_message.py +++ b/src/stoat/migrate_message.py @@ -7,7 +7,7 @@ from src.core.base import MigrationContext logger = logging.getLogger(__name__) -def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None, role_map=None, emoji_map=None) -> str: +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) -> str: if not content or not guild: return content @@ -54,8 +54,19 @@ def clean_mentions(content: str, guild, user_mentions=None, role_mentions=None, def replace_channel(match): cid = int(match.group(1)) + + # 1. Check if channel is mapped in state + if channel_map and str(cid) in channel_map: + 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 match.group(0) + return f"`{channel.name}`" if channel else f"<#{cid}>" def replace_emoji(match): animated = match.group(1) == "a" @@ -188,7 +199,9 @@ async def migrate_messages( msg.mentions, msg.role_mentions, context.discord_reader.role_map, - context.state.emoji_map + context.state.emoji_map, + context.state.channel_map, + context.discord_reader.channel_name_map ) # Process attachments @@ -213,7 +226,9 @@ async def migrate_messages( 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.emoji_map, + context.state.channel_map, + context.discord_reader.channel_name_map ) attachments_to_process.extend(snapshot.attachments) logger.debug(f"Found forwarded snapshot content: {content[:50]}... and {len(snapshot.attachments)} attachments")