135 lines
4.6 KiB
Python
135 lines
4.6 KiB
Python
|
|
"""Messaging utilities for Superviseur bot."""
|
||
|
|
|
||
|
|
import discord
|
||
|
|
import logging
|
||
|
|
from typing import Optional, List, Dict
|
||
|
|
|
||
|
|
logger = logging.getLogger('Superviseur')
|
||
|
|
|
||
|
|
|
||
|
|
class MessagingManager:
|
||
|
|
"""Manages message sending with rate limiting and chunking."""
|
||
|
|
|
||
|
|
def __init__(self, bot):
|
||
|
|
self.bot = bot
|
||
|
|
|
||
|
|
async def send_message_with_limit(
|
||
|
|
self,
|
||
|
|
channel: discord.abc.Messageable,
|
||
|
|
content: str
|
||
|
|
) -> None:
|
||
|
|
"""Send a message, splitting into multiple if it exceeds 2000 chars."""
|
||
|
|
while len(content) > 2000:
|
||
|
|
split_index = content.rfind(' ', 0, 2000)
|
||
|
|
if split_index == -1:
|
||
|
|
split_index = 2000
|
||
|
|
chunk = content[:split_index]
|
||
|
|
await channel.send(chunk)
|
||
|
|
content = content[split_index:].lstrip()
|
||
|
|
if content:
|
||
|
|
await channel.send(content)
|
||
|
|
|
||
|
|
async def reply_with_limit(
|
||
|
|
self,
|
||
|
|
message: discord.Message,
|
||
|
|
content: str
|
||
|
|
) -> None:
|
||
|
|
"""Reply to a message, splitting if it exceeds 2000 chars."""
|
||
|
|
if not message.channel:
|
||
|
|
logger.warning(f"Cannot send message: no channel")
|
||
|
|
return
|
||
|
|
|
||
|
|
if hasattr(message.channel, 'permissions_for'):
|
||
|
|
perms = message.channel.permissions_for(
|
||
|
|
message.guild.me if message.guild else message.author
|
||
|
|
)
|
||
|
|
if not perms.send_messages:
|
||
|
|
logger.warning(f"Cannot send message in channel {message.channel}")
|
||
|
|
return
|
||
|
|
|
||
|
|
first_chunk = True
|
||
|
|
|
||
|
|
while len(content) > 2000:
|
||
|
|
split_index = content.rfind(' ', 0, 2000)
|
||
|
|
if split_index == -1:
|
||
|
|
split_index = 2000
|
||
|
|
chunk = content[:split_index]
|
||
|
|
|
||
|
|
if first_chunk:
|
||
|
|
try:
|
||
|
|
await message.reply(chunk)
|
||
|
|
except (discord.errors.HTTPException, discord.errors.NotFound):
|
||
|
|
try:
|
||
|
|
await message.channel.send(chunk)
|
||
|
|
except discord.errors.Forbidden:
|
||
|
|
logger.error(f"Forbidden to send in channel {message.channel}")
|
||
|
|
first_chunk = False
|
||
|
|
else:
|
||
|
|
try:
|
||
|
|
await message.channel.send(chunk)
|
||
|
|
except discord.errors.Forbidden:
|
||
|
|
logger.error(f"Forbidden to send in channel {message.channel}")
|
||
|
|
return
|
||
|
|
|
||
|
|
content = content[split_index:].lstrip()
|
||
|
|
|
||
|
|
if content:
|
||
|
|
if first_chunk:
|
||
|
|
try:
|
||
|
|
await message.reply(content)
|
||
|
|
except (discord.errors.HTTPException, discord.errors.NotFound):
|
||
|
|
try:
|
||
|
|
await message.channel.send(content)
|
||
|
|
except discord.errors.Forbidden:
|
||
|
|
logger.error(f"Forbidden to send in channel {message.channel}")
|
||
|
|
else:
|
||
|
|
try:
|
||
|
|
await message.channel.send(content)
|
||
|
|
except discord.errors.Forbidden:
|
||
|
|
logger.error(f"Forbidden to send in channel {message.channel}")
|
||
|
|
|
||
|
|
async def send_embed(
|
||
|
|
self,
|
||
|
|
target: discord.abc.Messageable,
|
||
|
|
title: str,
|
||
|
|
description: str,
|
||
|
|
color: discord.Color = discord.Color.light_grey(),
|
||
|
|
thumbnail_url: Optional[str] = None,
|
||
|
|
is_asset: bool = False,
|
||
|
|
fields: Optional[List[Dict[str, str]]] = None
|
||
|
|
) -> None:
|
||
|
|
"""Send a clinical Samaritan-style embed with support for fields and clickable links."""
|
||
|
|
# Clinical formatting - we avoid the global ``` to allow markdown (links, mentions)
|
||
|
|
display_title = f"◈ {title} ◈"
|
||
|
|
|
||
|
|
embed = discord.Embed(
|
||
|
|
title=display_title,
|
||
|
|
description=description,
|
||
|
|
color=color
|
||
|
|
)
|
||
|
|
|
||
|
|
if fields:
|
||
|
|
for field in fields:
|
||
|
|
embed.add_field(
|
||
|
|
name=field.get("name", "---"),
|
||
|
|
value=field.get("value", "---"),
|
||
|
|
inline=field.get("inline", True)
|
||
|
|
)
|
||
|
|
|
||
|
|
if is_asset:
|
||
|
|
embed.set_author(
|
||
|
|
name="SYSTEM: BÊTA | PRIMARY ASSET LINK",
|
||
|
|
icon_url="https://i.imgur.com/xH3X5rR.png"
|
||
|
|
)
|
||
|
|
|
||
|
|
if thumbnail_url:
|
||
|
|
embed.set_thumbnail(url=thumbnail_url)
|
||
|
|
|
||
|
|
embed.set_footer(text=f"HEURISTIC ANALYSIS COMPLETE | {self.bot.system_name} v2.0")
|
||
|
|
|
||
|
|
try:
|
||
|
|
await target.send(embed=embed)
|
||
|
|
except discord.errors.Forbidden:
|
||
|
|
logger.error(f"Cannot send DM/Message to {target}")
|
||
|
|
|