225 lines
8.1 KiB
Python
225 lines
8.1 KiB
Python
import discord
|
||
from discord.ext import commands
|
||
from discord import app_commands
|
||
import asyncio
|
||
import logging
|
||
from datetime import datetime
|
||
from typing import Optional
|
||
import config
|
||
from utils.database import FunchosaDatabase
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
def build_funchosa_embed(message_data: dict) -> discord.Embed:
|
||
embed = discord.Embed(
|
||
description=message_data['content'] or "*[без текста]*",
|
||
color=discord.Color.blue(),
|
||
timestamp=datetime.fromisoformat(message_data['timestamp'])
|
||
)
|
||
embed.set_author(name='random фунчоза of the day')
|
||
embed.add_field(
|
||
name="info",
|
||
value=(
|
||
f"автор: <@{message_data['author_id']}>\n"
|
||
f"дата: {message_data['timestamp'].replace('T', ' ')[:19]}\n"
|
||
f"номер в базе: {message_data['id']}"
|
||
),
|
||
inline=False
|
||
)
|
||
|
||
if message_data.get('attachments'):
|
||
links = [
|
||
f"[вложение {i}]({att['url']})"
|
||
for i, att in enumerate(message_data['attachments'][:3], 1)
|
||
]
|
||
embed.add_field(name="вложения", value="\n".join(links), inline=False)
|
||
|
||
return embed
|
||
|
||
|
||
def build_funchosa_view() -> discord.ui.View:
|
||
view = discord.ui.View()
|
||
view.add_item(discord.ui.Button(
|
||
label="подавай еще, раб",
|
||
custom_id="another_random",
|
||
style=discord.ButtonStyle.secondary
|
||
))
|
||
return view
|
||
|
||
|
||
class FunchosaView(discord.ui.View):
|
||
def __init__(self, db: FunchosaDatabase, message_url: str):
|
||
super().__init__(timeout=None)
|
||
self.db = db
|
||
self.add_item(discord.ui.Button(
|
||
label="перейти к сообщению",
|
||
url=message_url,
|
||
style=discord.ButtonStyle.link
|
||
))
|
||
|
||
@discord.ui.button(label="подавай еще, раб", custom_id="another_random", style=discord.ButtonStyle.secondary)
|
||
async def another_random(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||
await interaction.response.defer()
|
||
|
||
message_data = await self.db.get_random_message()
|
||
if not message_data:
|
||
await interaction.followup.send("помоему чет поломалось. меня пингани", ephemeral=True)
|
||
return
|
||
|
||
embed = build_funchosa_embed(message_data)
|
||
view = FunchosaView(self.db, message_data['message_url'])
|
||
await interaction.followup.send(embed=embed, view=view)
|
||
|
||
|
||
class FunchosaParser(commands.Cog):
|
||
def __init__(self, bot):
|
||
self.bot = bot
|
||
self.db = FunchosaDatabase()
|
||
self.target_channel_id = config.FUNCHOSA_CHANNEL_ID
|
||
self.is_parsing = False
|
||
self.parsed_count = 0
|
||
|
||
async def cog_load(self):
|
||
await self.db.init_db()
|
||
logger.info("FunchosaParser initialized")
|
||
asyncio.ensure_future(self._startup())
|
||
|
||
async def _startup(self):
|
||
await self.bot.wait_until_ready()
|
||
await self.auto_parse_on_startup()
|
||
|
||
async def auto_parse_on_startup(self):
|
||
if self.is_parsing:
|
||
logger.warning("Parsing already in progress, skipping startup parse")
|
||
return
|
||
|
||
channel = self.bot.get_channel(self.target_channel_id)
|
||
if not channel:
|
||
logger.warning("Channel with id %s not found", self.target_channel_id)
|
||
return
|
||
|
||
try:
|
||
status = await self.db.get_parsing_status()
|
||
is_first = not status['first_parse_done']
|
||
limit = None if is_first else 250
|
||
logger.info("Starting %s parse", "full" if is_first else "incremental")
|
||
|
||
count = await self._parse_history(channel, limit=limit)
|
||
|
||
last_id = await self.db.get_last_message_in_db()
|
||
await self.db.update_parsing_status(first_parse_done=True, last_parsed_message_id=last_id)
|
||
logger.info("Parsing finished, %d new messages", count)
|
||
|
||
except Exception as e:
|
||
logger.error("Error during startup parse: %s", e, exc_info=True)
|
||
|
||
@commands.Cog.listener()
|
||
async def on_message(self, message):
|
||
if message.author.bot:
|
||
return
|
||
if self.target_channel_id and message.channel.id == self.target_channel_id:
|
||
await self._save_message(message)
|
||
|
||
async def _save_message(self, message):
|
||
try:
|
||
if await self.db.message_exists(message.id):
|
||
return
|
||
|
||
attachments_data = [
|
||
{'url': a.url, 'filename': a.filename}
|
||
for a in message.attachments
|
||
if a.url.lower().endswith(('png', 'jpg', 'jpeg', 'gif', 'webp'))
|
||
]
|
||
|
||
message_data = {
|
||
'message_id': message.id,
|
||
'channel_id': message.channel.id,
|
||
'author_id': message.author.id,
|
||
'author_name': str(message.author),
|
||
'content': message.content,
|
||
'timestamp': message.created_at.isoformat(),
|
||
'message_url': message.jump_url,
|
||
'has_attachments': bool(message.attachments),
|
||
'attachments': attachments_data,
|
||
}
|
||
|
||
saved = await self.db.save_message(message_data)
|
||
if saved:
|
||
self.parsed_count += 1
|
||
if self.parsed_count % 50 == 0:
|
||
logger.info("Saved %d messages so far", self.parsed_count)
|
||
|
||
except Exception as e:
|
||
logger.error("Error saving message: %s", e)
|
||
|
||
async def _parse_history(self, channel, limit=None):
|
||
self.is_parsing = True
|
||
count = 0
|
||
skipped = 0
|
||
|
||
try:
|
||
logger.info("Parsing history of #%s (limit=%s)", channel.name, limit)
|
||
|
||
async for message in channel.history(limit=limit, oldest_first=True):
|
||
if message.author.bot:
|
||
continue
|
||
if await self.db.message_exists(message.id):
|
||
skipped += 1
|
||
continue
|
||
|
||
await self._save_message(message)
|
||
count += 1
|
||
|
||
if (count + skipped) % 100 == 0:
|
||
logger.info("Progress: +%d new, -%d skipped", count, skipped)
|
||
|
||
logger.info("Parse done: %d new, %d skipped", count, skipped)
|
||
return count
|
||
|
||
except Exception as e:
|
||
logger.error("Error parsing history: %s", e, exc_info=True)
|
||
return 0
|
||
finally:
|
||
self.is_parsing = False
|
||
|
||
@commands.hybrid_command()
|
||
@app_commands.describe(number="номер сообщения из базы; optional")
|
||
async def funchosarand(self, ctx, number: Optional[int] = None):
|
||
await ctx.defer()
|
||
|
||
if number:
|
||
message_data = await self.db.get_message_by_number(number)
|
||
if not message_data:
|
||
await ctx.send(f"сообщение с номером {number} не найдено в базе")
|
||
return
|
||
else:
|
||
message_data = await self.db.get_random_message()
|
||
if not message_data:
|
||
await ctx.send("помоему чет поломалось. меня пингани")
|
||
return
|
||
|
||
embed = build_funchosa_embed(message_data)
|
||
view = FunchosaView(self.db, message_data['message_url'])
|
||
await ctx.send(embed=embed, view=view)
|
||
|
||
@commands.hybrid_command()
|
||
async def funchosainfo(self, ctx):
|
||
total = await self.db.get_total_count()
|
||
status = await self.db.get_parsing_status()
|
||
|
||
embed = discord.Embed(title="фунчоза.статы", color=discord.Color.green())
|
||
embed.add_field(name="сообщений в базе", value=f"**{total}**", inline=True)
|
||
|
||
if status['last_parsed_message_id']:
|
||
embed.add_field(
|
||
name="последнее сообщение",
|
||
value=f"id: `{status['last_parsed_message_id']}`",
|
||
inline=False
|
||
)
|
||
|
||
await ctx.send(embed=embed)
|
||
|
||
|
||
async def setup(bot):
|
||
await bot.add_cog(FunchosaParser(bot)) |