disco-reaper/src/core/utils.py
2026-03-19 11:21:04 +05:30

91 lines
3.7 KiB
Python

import re
import logging
from src.core.state import MigrationState
logger = logging.getLogger(__name__)
def resolve_discord_links(content: str, state: MigrationState, platform: str, target_server_id: str) -> str:
"""
Finds Discord message/channel links and resolves them to the target platform
if they have been migrated.
"""
if not content:
return content
# Regex for Discord links: https://discord.com/channels/{guild}/{channel}/{message}
# Matches: https://discord.com/channels/123/456 or https://discord.com/channels/123/456/789
discord_link_re = re.compile(r'https?://(?:ptb\.|canary\.)?discord\.com/channels/(\d+)/(\d+)(?:/(\d+))?')
def replace_link(match):
full_url = match.group(0)
# Check if already part of a markdown link: [text](link) or [text](<link>)
# We look backwards for ]( or ](<
start_idx = match.start()
if start_idx > 2:
prev_chars = content[max(0, start_idx-3):start_idx]
if prev_chars.endswith("](") or prev_chars.endswith("](<"):
logger.debug(f"resolve_discord_links: Skipping already-wrapped link: {full_url[:60]}")
return full_url
guild_id = match.group(1)
channel_id = match.group(2)
message_id = match.group(3)
target_cid = state.get_target_channel_id(channel_id) or state.get_target_category_id(channel_id)
logger.debug(f"resolve_discord_links: guild={guild_id} channel={channel_id} msg={message_id} target_cid={target_cid}")
if message_id:
# Message link resolution
t_cid, t_mid = state.find_message_mapping(message_id)
logger.debug(f"resolve_discord_links: find_message_mapping({message_id}) -> t_cid={t_cid}, t_mid={t_mid}")
if t_mid:
# Use found channel ID if available, otherwise fallback to channel_id mapping
final_cid = t_cid or target_cid
if final_cid:
if platform == "stoat":
return f"https://stoat.chat/server/{target_server_id}/channel/{final_cid}/{t_mid}"
else: # Fluxer
return f"https://fluxer.app/channels/{target_server_id}/{final_cid}/{t_mid}"
# Fallback for unmigrated message
return f"[`discord-message`](<{full_url}>)"
else:
# Channel link resolution
if target_cid:
if platform == "stoat":
return f"https://stoat.chat/server/{target_server_id}/channel/{target_cid}"
else: # Fluxer
return f"https://fluxer.app/channels/{target_server_id}/{target_cid}"
# Fallback for unmapped channel
return f"[`discord-channel`](<{full_url}>)"
logger.debug(f"resolve_discord_links: Processing content (len {len(content)}): {content[:100]!r}")
result = discord_link_re.sub(replace_link, content)
if result != content:
logger.debug(f"resolve_discord_links: Content resolved to (len {len(result)}): {result[:100]!r}")
return result
import subprocess
def get_app_version() -> str:
"""Gets the dynamic app version from baked file or git."""
try:
from src.core._baked_version import __version__
return f"Reaper-{__version__}"
except ImportError:
pass
try:
version = subprocess.check_output(
["git", "describe", "--tags", "--always"],
stderr=subprocess.DEVNULL,
universal_newlines=True
).strip()
if not version:
return "Reaper-Unknown"
return f"Reaper-{version}"
except Exception:
return "Reaper-Unknown-git"