Security News
Supply Chain Attack Detected in Solana's web3.js Library
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
Export Discord chats with your discord.py (or fork) bots!
Join Discord
·
Report Bug
·
Request Feature
To install the library to your virtual environment, for bot usage, run the command:
pip install chat-exporter
To clone the repository locally, run the command:
git clone https://github.com/mahtoid/DiscordChatExporterPy
There are currently 3 methods (functions) to chat-exporter
which you can use to export your chat.
Expand the blocks below to learn the functions, arguments and usages.
.quick_export()
is the simplest way of using chat-exporter.
Using the quick_export function will gather the history of the channel you give, build the transcript then post the file and embed directly to the channel - returning a message object gathered from the message it posted.
This is mostly seen as a demo function, as opposed to a command you should actually use.
Required Argument(s):
channel
: discord.TextChannel
object, whether ctx.channel
or any channel you gather.
Optional Argument(s):
bot
: commands.Bot
object to gather members who are no longer in your guild.
Return Argument:
discord.Message
: The message quick_export will send, containing the embed and exported chat file.
Example:
import discord
import chat_exporter
from discord.ext import commands
intents = discord.Intents.default()
intents.members = True
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)
...
@bot.command()
async def save(ctx: commands.Context):
await chat_exporter.quick_export(ctx.channel)
...
.export()
is the most efficient and flexible method to export a chat using chat-exporter.
Using the export function will generate a transcript using the channel you pass in, along with using any of the custom kwargs passed in to set limits, timezone, 24h formats and more (listed below).
This would be the main function to use within chat-exporter.
Required Argument(s):
channel
: discord.TextChannel
object, whether ctx.channel
or any channel you gather.
Optional Argument(s):
limit
: Integer value to set the limit (amount of messages) the chat exporter gathers when grabbing the history (default=unlimited).
tz_info
: String value of a TZ Database name to set a custom timezone for the exported messages (default=UTC).
guild
: discord.Guild
object which can be passed in to solve bugs for certain forks.
military_time
: Boolean value to set a 24h format for times within your exported chat (default=False | 12h format).
fancy_times
: Boolean value which toggles the 'fancy times' (Today|Yesterday|Day).
before
: datetime.datetime
object which allows to gather messages from before a certain date.
after
: datetime.datetime
object which allows to gather messages from after a certain date.
bot
: commands.Bot
object to gather members who are no longer in your guild.
attachment_handler
: chat_exporter.AttachmentHandler
object to export assets to in order to make them available after the channel
got deleted.
Return Argument:
transcript
: The HTML build-up for you to construct the HTML File with Discord.
Example:
import io
...
@bot.command()
async def save(ctx: commands.Context, limit: int = 100, tz_info: str = "UTC", military_time: bool = True):
transcript = await chat_exporter.export(
ctx.channel,
limit=limit,
tz_info=tz_info,
military_time=military_time,
bot=bot,
)
if transcript is None:
return
transcript_file = discord.File(
io.BytesIO(transcript.encode()),
filename=f"transcript-{ctx.channel.name}.html",
)
await ctx.send(file=transcript_file)
.raw_export()
is for the crazy people who like to do their own thing when using chat-exporter.
Using the raw_export function will generate a transcript using the list of messages you pass in, along with using any of the custom kwargs passed in to set limits, timezone, 24h formats and more (listed below).
This would be for people who want to filter what content to export.
Required Argument(s):
channel
: discord.TextChannel
object, whether ctx.channel
or any channel you gather (this is just for padding the header).
messages
: A list of Message objects which you wish to export to an HTML file.
Optional Argument(s):
tz_info
: String value of a TZ Database name to set a custom timezone for the exported messages (default=UTC)
military_time
: Boolean value to set a 24h format for times within your exported chat (default=False | 12h format)
fancy_times
: Boolean value which toggles the 'fancy times' (Today|Yesterday|Day)
bot
: commands.Bot
object to gather members who are no longer in your guild.
attachment_handler
: chat_exporter.AttachmentHandler
object to export assets to in order to make them available after the channel
got deleted.
Return Argument:
transcript
: The HTML build-up for you to construct the HTML File with Discord.
Example:
import io
...
@bot.command()
async def purge(ctx: commands.Context, tz_info: str, military_time: bool):
deleted_messages = await ctx.channel.purge()
transcript = await chat_exporter.raw_export(
ctx.channel,
messages=deleted_messages,
tz_info=tz_info,
military_time=military_time,
bot=bot,
)
if transcript is None:
return
transcript_file = discord.File(
io.BytesIO(transcript.encode()),
filename=f"transcript-{ctx.channel.name}.html",
)
await ctx.send(file=transcript_file)
Due to Discords newly introduced restrictions on to their CDN, we have introduced an Attachment Handler. This handler will assist you with circumventing the 'broken' and 'dead-assets' which arise when former attachments hosted by Discord reach their expiration date.
The AttachmentHandler
serves as a template for you to implement your own asset handler. Below are two basic examples on
how to use the AttachmentHandler
. One using the example of storing files on a local webserver, with the other being
an example of storing them on Discord (the latter merely just being an example, this will still obviously run in to
the expiration issue).
If you do not specify an attachment handler, chat-exporter will continue to use the (proxy) URLs for the assets.
The concept of implementing such an AttachmentHandler is very easy. In the following a short general procedure is described to write your own AttachmentHandler fitting your storage solution. Here we will assume, that we store the attachments in a cloud storage.
chat_exporter.AttachmentHandler
and implement the __init__
method if needed. This should look
something like this:from chat_exporter import AttachmentHandler
from cloud_wrapper import CloudClient
class MyAttachmentHandler(AttachmentHandler):
def __init__(self, *args, **kwargs):
# Your initialization code here
# in your case we just create the cloud client
self.cloud_client = CloudClient()
process_asset
method is the method that is called for each asset in the chat. Here we have to implement the
upload logic and the generation of the asset url from the uploaded asset.import io
import aiohttp
from chat_exporter import AttachmentHandler
from cloud_wrapper import CloudClient
from discord import Attachment
class MyAttachmentHandler(AttachmentHandler):
def __init__(self, *args, **kwargs):
# Your initialization code here
# in your case we just create the cloud client
self.cloud_client = CloudClient()
async def process_asset(self, attachment: Attachment):
# Your upload logic here, in our example we just upload the asset to the cloud
# first we need to authorize the client
await self.cloud_client.authorize()
# then we fetch the content of the attachment
async with aiohttp.ClientSession() as session:
async with session.get(attachment.url) as res:
if res.status != 200:
res.raise_for_status()
data = io.BytesIO(await res.read())
data.seek(0)
# and upload it to the cloud, back we get some sort of identifier for the uploaded file
asset_id = await self.cloud_client.upload(data)
# now we can generate the asset url from the identifier
asset_url = await self.cloud_client.get_share_url(asset_id, shared_with="everyone")
# and set the proxy url attribute of the attachment to the generated url
attachment.proxy_url = asset_url
return attachment
Note
process_asset
method should return the attachment object with the proxy_url attribute set to the generated url.process_asset
method should be an async method, as it is likely that you have to do some async operations
like fetching the content of the attachment or uploading it to the cloud.process_asset
if you need to do some
operations before or after the upload of the asset. But the process_asset
method is the only method that is
called from chat-exporter.Examples:
Assuming you have a file server running, which serves the content of the folder /usr/share/assets/
under https://example.com/assets/
, you can easily use the AttachmentToLocalFileHostHandler
like this:
import io
import discord
from discord.ext import commands
import chat_exporter
from chat_exporter import AttachmentToLocalFileHostHandler
...
# Establish the file handler
file_handler = AttachmentToLocalFileHostHandler(
base_path="/usr/share/assets",
url_base="https://example.com/assets/",
)
@bot.command()
async def save(ctx: commands.Context):
transcript = await chat_exporter.export(
ctx.channel,
attachment_handler=file_handler,
)
if transcript is None:
return
transcript_file = discord.File(
io.BytesIO(transcript.encode()),
filename=f"transcript-{ctx.channel.name}.html",
)
await ctx.send(file=transcript_file)
Assuming you want to store your attachments in a discord channel, you can use the AttachmentToDiscordChannel
.
Please note that discord recent changes regarding content links will result in the attachments links being broken
after 24 hours. While this is therefor not a recommended way to store your attachments, it should give you a good
idea how to perform asynchronous storing of the attachments.
import io
import discord
from discord.ext import commands
import chat_exporter
from chat_exporter import AttachmentToDiscordChannel
...
# Establish the file handler
channel_handler = AttachmentToDiscordChannel(
channel=bot.get_channel(CHANNEL_ID),
)
@bot.command()
async def save(ctx: commands.Context):
transcript = await chat_exporter.export(
ctx.channel,
attachment_handler=channel_handler,
)
if transcript is None:
return
transcript_file = discord.File(
io.BytesIO(transcript.encode()),
filename=f"transcript-{ctx.channel.name}.html",
)
await ctx.send(file=transcript_file)
Due to these pain, and many requests - I have built a fancy PHP script which will show the transcript file within a browser.
Required Argument(s):
channel
: discord.TextChannel
object, whether ctx.channel
or any channel you gather.
message
: The Discord message containing the transcript file
Return Argument:
discord.Message
: The message quick_link will send, containing the embed.
Example:
import chat_exporter
...
@bot.command()
async def save(ctx: commands.Context):
message = await chat_exporter.quick_export(ctx.channel)
await chat_exporter.quick_link(ctx.channel, message)
Required Argument(s):
message
: The Discord message containing the transcript file
Return Argument:
link
: The link to view the transcript file online
Example:
import io
import chat_exporter
...
@bot.command()
async def save(ctx: commands.Context):
transcript = await chat_exporter.export(ctx.channel)
if transcript is None:
return
transcript_file = discord.File(
io.BytesIO(transcript.encode()),
filename=f"transcript-{ctx.channel.name}.html",
)
message = await ctx.send(file=transcript_file)
link = await chat_exporter.link(message)
await ctx.send("Click this link to view the transcript online: " + link)
Please note that the PHP script does NOT store any information.
It simply makes a request to the given URL and echos (prints) the content for you to be able to view it.
This project borrows CSS and HTML code from Tyrrrz's C# DiscordChatExporter repository.
FAQs
A simple Discord chat exporter for Python Discord bots.
We found that chat-exporter demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
A supply chain attack has been detected in versions 1.95.6 and 1.95.7 of the popular @solana/web3.js library.
Research
Security News
A malicious npm package targets Solana developers, rerouting funds in 2% of transactions to a hardcoded address.
Security News
Research
Socket researchers have discovered malicious npm packages targeting crypto developers, stealing credentials and wallet data using spyware delivered through typosquats of popular cryptographic libraries.