improve thread start & end markers

This commit is contained in:
rambros 2026-03-06 19:01:35 +05:30
parent 0e67e721d9
commit 1835cbcc17
4 changed files with 59 additions and 23 deletions

View file

@ -102,7 +102,9 @@ async def migrate_messages(
target_channel_id: str, target_channel_id: str,
after_message_id: int | None = None, after_message_id: int | None = None,
progress_callback: Callable[[Dict[str, Any]], Awaitable[None]] | None = None, progress_callback: Callable[[Dict[str, Any]], Awaitable[None]] | None = None,
thread_id: str | None = None thread_id: str | None = None,
parent_target_id: str | None = None,
thread_name: str | None = None
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Migrate messages for a specific channel and returns detailed statistics.""" """Migrate messages for a specific channel and returns detailed statistics."""
stats = { stats = {
@ -128,11 +130,7 @@ async def migrate_messages(
# Skip system messages like "pinned a message", etc. # Skip system messages like "pinned a message", etc.
# We treat thread_starter_message (type 21) as our thread marker. if msg.type not in [context.discord_reader.MESSAGE_TYPE_DEFAULT, context.discord_reader.MESSAGE_TYPE_REPLY, context.discord_reader.MESSAGE_TYPE_THREAD_STARTER]:
if msg.type == context.discord_reader.MESSAGE_TYPE_THREAD_STARTER:
channel_name = msg.channel.name if msg.channel else "Unknown Thread"
content = f"> <<< THREAD: **{channel_name}** >>>"
elif msg.type not in [context.discord_reader.MESSAGE_TYPE_DEFAULT, context.discord_reader.MESSAGE_TYPE_REPLY]:
# If we are skipping the parent, we STILL need to check for a thread! # If we are skipping the parent, we STILL need to check for a thread!
if hasattr(msg, 'thread') and msg.thread: if hasattr(msg, 'thread') and msg.thread:
thread = msg.thread thread = msg.thread
@ -146,7 +144,9 @@ async def migrate_messages(
context=context, context=context,
source_channel_id=thread.id, source_channel_id=thread.id,
target_channel_id=target_channel_id, target_channel_id=target_channel_id,
thread_id=str(thread.id) thread_id=str(thread.id),
parent_target_id=None,
thread_name=thread.name
) )
stats["messages"] += thread_stats["messages"] stats["messages"] += thread_stats["messages"]
stats["attachments"] += thread_stats["attachments"] stats["attachments"] += thread_stats["attachments"]
@ -211,6 +211,14 @@ async def migrate_messages(
else: else:
logger.debug(f"Reply target Discord ID {msg.reference.message_id} not found in current session map.") logger.debug(f"Reply target Discord ID {msg.reference.message_id} not found in current session map.")
# If this is the FIRST thread message and we have a parent_target_id, force it as reply to the starter
if not reply_to_fluxer_id and parent_target_id and stats["messages"] == 0:
reply_to_fluxer_id = parent_target_id
# Prepend thread marker to the first message of the thread
if thread_name and stats["messages"] == 0:
content = f"> <<< THREAD: **{thread_name}** >>>\n{content}"
avatar_url = str(msg.author.display_avatar.url) if msg.author.display_avatar.url else None avatar_url = str(msg.author.display_avatar.url) if msg.author.display_avatar.url else None
if avatar_url and not avatar_url.startswith("http"): if avatar_url and not avatar_url.startswith("http"):
avatar_url = None avatar_url = None
@ -262,7 +270,9 @@ async def migrate_messages(
context=context, context=context,
source_channel_id=thread.id, source_channel_id=thread.id,
target_channel_id=target_channel_id, target_channel_id=target_channel_id,
thread_id=str(thread.id) thread_id=str(thread.id),
parent_target_id=fluxer_msg_id,
thread_name=thread.name
) )
stats["messages"] += thread_stats["messages"] stats["messages"] += thread_stats["messages"]
stats["attachments"] += thread_stats["attachments"] stats["attachments"] += thread_stats["attachments"]

View file

@ -299,7 +299,7 @@ class FluxerWriter:
print(err_msg) print(err_msg)
return None return None
async def send_marker(self, channel_id: str, content: str, files: list[dict] | None = None) -> Optional[str]: async def send_marker(self, channel_id: str, content: str, files: list[dict] | None = None, reply_to_message_id: Optional[str] = None) -> Optional[str]:
""" """
Sends a simple marker message (e.g., thread start/end) using the bot directly. Sends a simple marker message (e.g., thread start/end) using the bot directly.
""" """
@ -309,11 +309,16 @@ class FluxerWriter:
if files: if files:
fluxer_files = [File(io.BytesIO(f["data"]), filename=f["filename"]) for f in files] fluxer_files = [File(io.BytesIO(f["data"]), filename=f["filename"]) for f in files]
message_reference = None
if reply_to_message_id:
message_reference = {"message_id": str(reply_to_message_id), "channel_id": str(channel_id)}
try: try:
msg_data = await self.client.send_message( msg_data = await self.client.send_message(
channel_id=channel_id, channel_id=channel_id,
content=content, content=content,
files=fluxer_files files=fluxer_files,
message_reference=message_reference
) )
return str(msg_data["id"]) if msg_data else None return str(msg_data["id"]) if msg_data else None
except Exception as e: except Exception as e:

View file

@ -107,7 +107,9 @@ async def migrate_messages(
target_channel_id: str, target_channel_id: str,
after_message_id: int | None = None, after_message_id: int | None = None,
progress_callback: Callable[[Dict[str, Any]], Awaitable[None]] | None = None, progress_callback: Callable[[Dict[str, Any]], Awaitable[None]] | None = None,
thread_id: str | None = None thread_id: str | None = None,
parent_target_id: str | None = None,
thread_name: str | None = None
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""Migrate messages for a specific channel using Stoat masquerade for author impersonation.""" """Migrate messages for a specific channel using Stoat masquerade for author impersonation."""
stats = { stats = {
@ -133,14 +135,8 @@ async def migrate_messages(
# Skip system messages like "pinned a message", etc. # Skip system messages like "pinned a message", etc.
# We treat thread_starter_message (type 21) as our thread marker.
content = "" # Initialize content content = "" # Initialize content
if msg.type == context.discord_reader.MESSAGE_TYPE_THREAD_STARTER: if msg.type not in [context.discord_reader.MESSAGE_TYPE_DEFAULT, context.discord_reader.MESSAGE_TYPE_REPLY, context.discord_reader.MESSAGE_TYPE_THREAD_STARTER]:
channel_name = msg.channel.name if msg.channel else "Unknown Thread"
content = f"> <<< THREAD: **{channel_name}** >>>"
# If it's a thread starter and we already processed the thread at the top,
# we might be double-posting. But we want it as a marker.
elif msg.type not in [context.discord_reader.MESSAGE_TYPE_DEFAULT, context.discord_reader.MESSAGE_TYPE_REPLY]:
# If we are skipping the parent, we STILL need to check for a thread! # If we are skipping the parent, we STILL need to check for a thread!
if hasattr(msg, 'thread') and msg.thread: if hasattr(msg, 'thread') and msg.thread:
thread = msg.thread thread = msg.thread
@ -149,12 +145,16 @@ async def migrate_messages(
# Track thread entry # Track thread entry
stats["threads"] += 1 stats["threads"] += 1
pass
# Migrate thread messages recursively # Migrate thread messages recursively
thread_stats = await migrate_messages( thread_stats = await migrate_messages(
context=context, context=context,
source_channel_id=thread.id, source_channel_id=thread.id,
target_channel_id=target_channel_id, target_channel_id=target_channel_id,
thread_id=str(thread.id) thread_id=str(thread.id),
parent_target_id=None,
thread_name=thread.name
) )
stats["messages"] += thread_stats["messages"] stats["messages"] += thread_stats["messages"]
stats["attachments"] += thread_stats["attachments"] stats["attachments"] += thread_stats["attachments"]
@ -214,6 +214,14 @@ async def migrate_messages(
else: else:
logger.debug(f"Reply target Discord ID {msg.reference.message_id} not found in current session map.") logger.debug(f"Reply target Discord ID {msg.reference.message_id} not found in current session map.")
# If this is the FIRST thread message and we have a parent_target_id, force it as reply to the starter
if not reply_to_stoat_id and parent_target_id and stats["messages"] == 0:
reply_to_stoat_id = parent_target_id
# Prepend thread marker to the first message of the thread
if thread_name and stats["messages"] == 0:
content = f"> <<< THREAD: **{thread_name}** >>>\n{content}"
avatar_url = str(msg.author.display_avatar.url) if msg.author.display_avatar.url else None avatar_url = str(msg.author.display_avatar.url) if msg.author.display_avatar.url else None
if avatar_url and not avatar_url.startswith("http"): if avatar_url and not avatar_url.startswith("http"):
avatar_url = None avatar_url = None
@ -260,12 +268,16 @@ async def migrate_messages(
# Track thread entry # Track thread entry
stats["threads"] += 1 stats["threads"] += 1
pass
# Migrate thread messages recursively # Migrate thread messages recursively
thread_stats = await migrate_messages( thread_stats = await migrate_messages(
context=context, context=context,
source_channel_id=thread.id, source_channel_id=thread.id,
target_channel_id=target_channel_id, target_channel_id=target_channel_id,
thread_id=str(thread.id) thread_id=str(thread.id),
parent_target_id=stoat_msg_id,
thread_name=thread.name
) )
stats["messages"] += thread_stats["messages"] stats["messages"] += thread_stats["messages"]
stats["attachments"] += thread_stats["attachments"] stats["attachments"] += thread_stats["attachments"]

View file

@ -279,7 +279,7 @@ class StoatWriter:
logger.error(f"Failed to send Stoat message to {channel_id}: {e}") logger.error(f"Failed to send Stoat message to {channel_id}: {e}")
raise # Let caller handle (migration loop will stop for permission errors) raise # Let caller handle (migration loop will stop for permission errors)
async def send_marker(self, channel_id: str, content: str, files: Optional[List[Dict[str, Any]]] = None) -> Optional[str]: async def send_marker(self, channel_id: str, content: str, files: Optional[List[Dict[str, Any]]] = None, reply_to_message_id: Optional[str] = None) -> Optional[str]:
try: try:
channel = await self.client.fetch_channel(channel_id) channel = await self.client.fetch_channel(channel_id)
attachments = None attachments = None
@ -287,7 +287,16 @@ class StoatWriter:
attachments = [] attachments = []
for f in files: for f in files:
attachments.append((f["filename"], f["data"])) attachments.append((f["filename"], f["data"]))
msg = await channel.send(content=content, attachments=attachments)
replies = None
if reply_to_message_id:
replies = [stoat.Reply(id=reply_to_message_id, mention=False)]
msg = await channel.send(
content=content,
attachments=attachments,
replies=replies
)
return str(msg.id) return str(msg.id)
except Exception as e: except Exception as e:
logger.error(f"Failed to send Stoat marker to {channel_id}: {e}") logger.error(f"Failed to send Stoat marker to {channel_id}: {e}")