discord-html-transcripts-fix
Version:
A nicely formatted html transcript generator for discord.js. Bugfix fork with support for the latest discord.js and Components v2.
135 lines (122 loc) • 6.32 kB
JavaScript
;
/// <reference path="./discord-components.d.ts" />
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p))
Object.defineProperty(exports, p, { enumerable: true, get: function() { return m[p]; } });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TranscriptImageDownloader = exports.DiscordMessages = void 0;
exports.generateFromMessages = generateFromMessages;
exports.createTranscript = createTranscript;
const debug_1 = __importDefault(require("debug"));
const discord_js_1 = require("discord.js");
const generator_1 = __importDefault(require("./generator"));
const types_1 = require("./types");
const images_1 = require("./downloader/images");
const log = (0, debug_1.default)('discord-html-transcripts-fix');
var transcript_1 = require("./generator/transcript");
Object.defineProperty(exports, "DiscordMessages", { enumerable: true, get: function () { return __importDefault(transcript_1).default; } });
var images_2 = require("./downloader/images");
Object.defineProperty(exports, "TranscriptImageDownloader", { enumerable: true, get: function () { return images_2.TranscriptImageDownloader; } });
// Version check — warn only. NEVER kill host process.
const versionPrefix = discord_js_1.version.split('.')[0];
if (versionPrefix !== '14' && versionPrefix !== '15') {
console.warn(`[discord-html-transcripts-fix] Compatible with discord.js v14/v15, you are using v${discord_js_1.version}. Continuing anyway.`);
}
async function generateFromMessages(messages, channel, options = {}) {
const transformedMessages = messages instanceof discord_js_1.Collection ? Array.from(messages.values()) : messages;
// Image-src resolution
let resolveImageSrc = options.callbacks?.resolveImageSrc ?? ((attachment) => attachment.url);
if (options.saveImages) {
if (options.callbacks?.resolveImageSrc) {
log('saveImages + resolveImageSrc both set — resolveImageSrc wins');
} else {
resolveImageSrc = new images_1.TranscriptImageDownloader().build();
log('using default image downloader');
}
}
// Pre-build resolution maps from messages so mentions resolve to a name even
// when the user/role/channel isn't in client cache and can't be fetched.
const userIndex = new Map();
const roleIndex = new Map();
const channelIndex = new Map();
for (const m of transformedMessages) {
if (m.author) userIndex.set(m.author.id, m.author);
if (m.member?.user) userIndex.set(m.member.user.id, m.member.user);
if (m.interaction?.user) userIndex.set(m.interaction.user.id, m.interaction.user);
if (m.mentions) {
if (m.mentions.users) for (const [id, u] of m.mentions.users) userIndex.set(id, u);
if (m.mentions.roles) for (const [id, r] of m.mentions.roles) roleIndex.set(id, r);
if (m.mentions.channels) for (const [id, c] of m.mentions.channels) channelIndex.set(id, c);
}
}
if (!channel.isDMBased() && channel.guild) {
// pre-warm every role we can see (cheap — cache only)
for (const [id, r] of channel.guild.roles.cache) roleIndex.set(id, r);
}
const callbacks = Object.assign({
resolveImageSrc,
resolveChannel: async (id) => channelIndex.get(id) || channel.client.channels.fetch(id).catch(() => null),
resolveUser: async (id) => userIndex.get(id) || channel.client.users.fetch(id).catch(() => null),
resolveRole: channel.isDMBased()
? () => null
: async (id) => roleIndex.get(id) || channel.guild?.roles.fetch(id).catch(() => null),
}, options.callbacks ?? {});
const rendered = await (0, generator_1.default)({
messages: transformedMessages,
channel,
saveImages: options.saveImages ?? false,
callbacks,
poweredBy: options.poweredBy ?? false,
footerText: options.footerText ?? 'Exported {number} message{s}.',
statsFooter: options.statsFooter,
favicon: options.favicon ?? 'guild',
hydrate: options.hydrate ?? false,
language: options.language ?? 'en',
i18n: options.i18n ?? undefined,
stream: options.returnType === 'stream' || options.stream === true,
returnType: options.returnType,
});
// Stream pass-through
if (options.returnType === 'stream' || options.stream === true) {
return rendered; // Node readable stream
}
if (options.returnType === types_1.ExportReturnType.Buffer) {
return Buffer.from(rendered);
}
if (options.returnType === types_1.ExportReturnType.String) {
return rendered;
}
return new discord_js_1.AttachmentBuilder(Buffer.from(rendered), {
name: options.filename ?? `transcript-${channel.id}.html`,
});
}
async function createTranscript(channel, options = {}) {
if (!channel.isTextBased()) {
throw new TypeError(`Provided channel must be text-based, received ${channel.type}`);
}
let allMessages = [];
let lastMessageId;
const { limit, filter } = options;
const resolvedLimit = typeof limit === 'undefined' || limit === -1 ? Infinity : limit;
while (true) {
const fetchLimitOptions = { limit: 100, before: lastMessageId };
if (!lastMessageId) delete fetchLimitOptions.before;
const messages = await channel.messages.fetch(fetchLimitOptions);
const filteredMessages = typeof filter === 'function' ? messages.filter(filter) : messages;
for (const m of filteredMessages.values()) allMessages.push(m);
lastMessageId = messages.lastKey();
if (messages.size < 100) break;
if (allMessages.length >= resolvedLimit) break;
}
if (resolvedLimit < allMessages.length) {
allMessages = allMessages.slice(0, resolvedLimit);
}
return generateFromMessages(allMessages.reverse(), channel, options);
}
exports.default = { createTranscript, generateFromMessages };
__exportStar(require("./types"), exports);
//# sourceMappingURL=index.js.map