142 lines
5.3 KiB
Python
142 lines
5.3 KiB
Python
# discord_export_bot_v2.py
|
|
# This bot connects to a Discord server and exports the entire message
|
|
# history from every accessible text channel into separate CSV files.
|
|
# This version uses a more robust task-based approach to prevent hanging.
|
|
|
|
import discord
|
|
import csv
|
|
import os
|
|
import asyncio
|
|
|
|
# --- Configuration ---
|
|
BOT_TOKEN = "___"
|
|
OUTPUT_DIRECTORY = "discord_chat_logs"
|
|
# Optional: If you want to lock the bot to one server
|
|
# ALLOWED_SERVER_ID = 123456789012345678
|
|
# -------------------
|
|
|
|
# --- Bot Setup ---
|
|
# The intents MUST be enabled in the Discord Developer Portal.
|
|
intents = discord.Intents.default()
|
|
intents.guilds = True
|
|
intents.messages = True
|
|
intents.message_content = True # This is the most important one!
|
|
|
|
client = discord.Client(intents=intents)
|
|
|
|
async def export_channel_history(channel):
|
|
"""
|
|
Asynchronously fetches all messages from a given text channel
|
|
and saves them to a CSV file.
|
|
"""
|
|
print(f"-> Starting export for channel: #{channel.name}")
|
|
|
|
sanitized_channel_name = "".join(c if c.isalnum() else '_' for c in channel.name)
|
|
file_path = os.path.join(OUTPUT_DIRECTORY, f"{sanitized_channel_name}.csv")
|
|
|
|
try:
|
|
message_count = 0
|
|
with open(file_path, 'w', newline='', encoding='utf-8') as csvfile:
|
|
header = [
|
|
'message_id', 'timestamp_utc', 'author_id', 'author_name',
|
|
'author_nickname', 'content', 'attachment_urls', 'embeds'
|
|
]
|
|
writer = csv.DictWriter(csvfile, fieldnames=header)
|
|
writer.writeheader()
|
|
|
|
# This is the part that fails without the Message Content Intent
|
|
async for message in channel.history(limit=None):
|
|
message_count += 1
|
|
if message_count % 250 == 0: # Log progress less frequently
|
|
print(f" ... processed {message_count} messages in #{channel.name}")
|
|
|
|
attachment_urls = ", ".join([att.url for att in message.attachments])
|
|
embeds_str = ", ".join([str(embed.to_dict()) for embed in message.embeds])
|
|
|
|
# Handle nickname - only Member objects have nick attribute, not User objects
|
|
author_nickname = getattr(message.author, 'nick', None) or message.author.display_name
|
|
|
|
writer.writerow({
|
|
'message_id': message.id,
|
|
'timestamp_utc': message.created_at,
|
|
'author_id': message.author.id,
|
|
'author_name': message.author.name,
|
|
'author_nickname': author_nickname,
|
|
'content': message.content,
|
|
'attachment_urls': attachment_urls,
|
|
'embeds': embeds_str
|
|
})
|
|
|
|
if message_count > 0:
|
|
print(f"✅ Finished exporting {message_count} messages from #{channel.name}.")
|
|
else:
|
|
print(f"⚠️ Channel #{channel.name} is empty or unreadable. 0 messages exported.")
|
|
return True
|
|
|
|
except discord.errors.Forbidden:
|
|
print(f"❌ ERROR: Permission denied for channel #{channel.name}. Check bot permissions. Skipping.")
|
|
return False
|
|
except Exception as e:
|
|
print(f"❌ An unexpected error occurred for channel #{channel.name}: {e}")
|
|
return False
|
|
|
|
async def main_export_task():
|
|
"""
|
|
The main logic for the bot's export process.
|
|
This is run as a background task to avoid blocking.
|
|
"""
|
|
# Wait until the bot is fully ready before starting
|
|
await client.wait_until_ready()
|
|
|
|
print('------')
|
|
print("Bot is ready. Starting export process...")
|
|
|
|
# Create the output directory if it doesn't exist
|
|
if not os.path.exists(OUTPUT_DIRECTORY):
|
|
os.makedirs(OUTPUT_DIRECTORY)
|
|
print(f"Created output directory: {OUTPUT_DIRECTORY}")
|
|
|
|
# Use the first guild the bot is in. For specific server, use client.get_guild(ALLOWED_SERVER_ID)
|
|
if not client.guilds:
|
|
print("Error: Bot does not appear to be in any server.")
|
|
await client.close()
|
|
return
|
|
|
|
guild = client.guilds[0]
|
|
print(f"Targeting server: {guild.name} (ID: {guild.id})")
|
|
|
|
text_channels = [channel for channel in guild.text_channels]
|
|
print(f"Found {len(text_channels)} text channels to export.")
|
|
|
|
for channel in text_channels:
|
|
await export_channel_history(channel)
|
|
await asyncio.sleep(1)
|
|
|
|
print('------')
|
|
print("All channels have been processed. The bot will now shut down.")
|
|
|
|
# This properly closes the bot's connection.
|
|
await client.close()
|
|
|
|
@client.event
|
|
async def on_ready():
|
|
"""
|
|
This event is triggered once the bot has successfully connected.
|
|
It now only prints a ready message and starts the main task.
|
|
"""
|
|
print(f'Logged in as: {client.user.name} (ID: {client.user.id})')
|
|
# Schedule the main task to run in the background
|
|
client.loop.create_task(main_export_task())
|
|
|
|
# --- Run the Bot ---
|
|
if __name__ == "__main__":
|
|
if BOT_TOKEN == "YOUR_BOT_TOKEN_HERE":
|
|
print("!!! ERROR: Please replace 'YOUR_BOT_TOKEN_HERE' with your actual bot token in the script.")
|
|
else:
|
|
try:
|
|
client.run(BOT_TOKEN)
|
|
except discord.errors.LoginFailure:
|
|
print("!!! ERROR: Login failed. The token is likely invalid or incorrect.")
|
|
except Exception as e:
|
|
print(f"!!! An error occurred while running the bot: {e}")
|