discord-html-transcripts-fix 1.8.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -9
- package/dist/downloader/images.d.ts +25 -1
- package/dist/generator/renderers/message.js +9 -1
- package/dist/generator/renderers/systemMessage.js +25 -20
- package/dist/index.js +2 -1
- package/dist/types.d.ts +23 -4
- package/package.json +20 -2
package/README.md
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
# `discord-html-transcripts-fix`
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/discord-html-transcripts-fix)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
3
7
|
A nicely formatted HTML transcript generator for [discord.js](https://discord.js.org/) with full **Components V2** support, an **interactive viewer**, and **hardened security**.
|
|
4
8
|
|
|
5
9
|
Forked from [discord-html-transcripts](https://github.com/ItzDerock/discord-html-transcripts).
|
|
6
10
|
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- **Node.js ≥ 20** — the image downloader uses `undici` v7.
|
|
14
|
+
- **discord.js v14 or v15** — required peer dependency.
|
|
15
|
+
- **[`sharp`](https://sharp.pixelplumbing.com/)** — *optional* peer dependency, only needed if you use `.withCompression()` to compress / convert transcript images to WebP.
|
|
16
|
+
|
|
7
17
|
## Install
|
|
8
18
|
|
|
9
19
|
```bash
|
|
10
20
|
npm install discord-html-transcripts-fix
|
|
11
21
|
```
|
|
12
22
|
|
|
13
|
-
|
|
23
|
+
`discord.js` is the only **required** peer dependency — React, Lit SSR, the markdown parser, etc. are installed automatically. `sharp` is an optional peer (image compression only).
|
|
14
24
|
|
|
15
25
|
## Quick start
|
|
16
26
|
|
|
@@ -18,11 +28,28 @@ Only `discord.js` is a peer dependency; everything else (React, Lit SSR, markdow
|
|
|
18
28
|
const { createTranscript } = require('discord-html-transcripts-fix');
|
|
19
29
|
|
|
20
30
|
const attachment = await createTranscript(channel, {
|
|
21
|
-
limit: -1,
|
|
31
|
+
limit: -1, // fetch every message
|
|
22
32
|
saveImages: false,
|
|
23
33
|
});
|
|
24
34
|
|
|
25
|
-
channel.send({ files: [attachment] });
|
|
35
|
+
await channel.send({ files: [attachment] });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### TypeScript
|
|
39
|
+
|
|
40
|
+
In TypeScript, use the `ExportReturnType` enum for `returnType` (the return value is typed accordingly):
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { createTranscript, ExportReturnType } from 'discord-html-transcripts-fix';
|
|
44
|
+
|
|
45
|
+
const html = await createTranscript(channel, {
|
|
46
|
+
returnType: ExportReturnType.String, // => Promise<string>
|
|
47
|
+
language: 'de',
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const stream = await createTranscript(channel, {
|
|
51
|
+
returnType: ExportReturnType.Stream, // => Promise<Readable>, ideal for huge tickets
|
|
52
|
+
});
|
|
26
53
|
```
|
|
27
54
|
|
|
28
55
|
## Options
|
|
@@ -31,7 +58,7 @@ channel.send({ files: [attachment] });
|
|
|
31
58
|
|---|---|---|---|
|
|
32
59
|
| `limit` | `number` | `-1` | Max messages to fetch. `-1` = recursive (all). |
|
|
33
60
|
| `filter` | `(m) => boolean` | `() => true` | Predicate to filter messages. |
|
|
34
|
-
| `returnType` | `'attachment'` \| `'buffer'` \| `'string'` \| `'stream'` | `'attachment'` | Return value shape. `'stream'` is best for 5k+ message exports. |
|
|
61
|
+
| `returnType` | `'attachment'` \| `'buffer'` \| `'string'` \| `'stream'` | `'attachment'` | Return value shape. In TypeScript pass the `ExportReturnType` enum. `'stream'` returns a Node `Readable` and is best for 5k+ message exports. |
|
|
35
62
|
| `filename` | `string` | `transcript-{channel-id}.html` | Output filename when returning as attachment. |
|
|
36
63
|
| `saveImages` | `boolean` | `false` | Download images and inline them as base64 data URLs. |
|
|
37
64
|
| `favicon` | `'guild'` \| `string` | `'guild'` | Page favicon — `'guild'` uses the server icon, or pass a URL. |
|
|
@@ -39,9 +66,9 @@ channel.send({ files: [attachment] });
|
|
|
39
66
|
| `language` | `'en'` \| `'de'` | `'en'` | UI language for participant labels, filter strings, etc. |
|
|
40
67
|
| `i18n` | `Partial<Record<lang, Record<key,string>>>` | — | Override individual strings per language. |
|
|
41
68
|
| `statsFooter` | `false` \| `{ enabled?, template? }` | `{ enabled: true }` | Bottom stats line. See below. |
|
|
42
|
-
| `footerText` | `string` |
|
|
43
|
-
| `poweredBy` | `boolean` | `false` | Show the original "Powered by discord-html-transcripts" credit link. |
|
|
44
|
-
| `callbacks` | `{ resolveUser, resolveRole, resolveChannel }` | — | Custom resolvers for mentions. |
|
|
69
|
+
| `footerText` | `string` | `Exported {number} message{s}.` | Legacy "Exported X messages" line. Only renders when `statsFooter` is disabled. |
|
|
70
|
+
| `poweredBy` | `boolean` | `false` | Show the original "Powered by discord-html-transcripts" credit link. Only renders when `statsFooter` is disabled. |
|
|
71
|
+
| `callbacks` | `{ resolveUser, resolveRole, resolveChannel, resolveImageSrc }` | — | Custom resolvers for mentions / image URLs. |
|
|
45
72
|
|
|
46
73
|
### Configurable stats footer
|
|
47
74
|
|
|
@@ -58,6 +85,23 @@ await createTranscript(channel, { statsFooter: false });
|
|
|
58
85
|
|
|
59
86
|
Placeholders: `{messages}`, `{participants}`, `{images}`, `{from}`, `{to}`, `{span}`.
|
|
60
87
|
|
|
88
|
+
### Image compression (optional)
|
|
89
|
+
|
|
90
|
+
`saveImages: true` inlines images as base64 without any extra dependency. To additionally
|
|
91
|
+
compress them (and optionally convert to WebP), install `sharp` and build a custom downloader:
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
const { createTranscript, TranscriptImageDownloader } = require('discord-html-transcripts-fix');
|
|
95
|
+
|
|
96
|
+
const resolveImageSrc = new TranscriptImageDownloader()
|
|
97
|
+
.withMaxSize(2048) // KB per image
|
|
98
|
+
.withConcurrency(8) // parallel downloads (default 6)
|
|
99
|
+
.withCompression(80, true) // quality 80, convert to WebP — requires `sharp`
|
|
100
|
+
.build();
|
|
101
|
+
|
|
102
|
+
await createTranscript(channel, { saveImages: true, callbacks: { resolveImageSrc } });
|
|
103
|
+
```
|
|
104
|
+
|
|
61
105
|
### Optional edit history
|
|
62
106
|
|
|
63
107
|
If your bot tracks edits, attach them to the message *before* rendering:
|
|
@@ -130,6 +174,7 @@ In addition to plain text, replies, embeds, and attachments, the viewer supports
|
|
|
130
174
|
- **Fix** `parseDiscordEmoji` no longer throws on deleted reactions with `emoji.name === null`
|
|
131
175
|
- **Fix** `formatBytes(null/undefined/NaN)` no longer returns `NaN undefined`
|
|
132
176
|
- **Fix** `createTranscript` slice uses the resolved limit instead of the raw `limit`
|
|
177
|
+
- **Fix** `statsFooter` (custom template / `false`) is now forwarded end-to-end — it used to be silently ignored by `createTranscript`/`generateFromMessages`
|
|
133
178
|
- **Fix** embed fields render through a proper async component (was an inline `async` arrow inside `.map()`)
|
|
134
179
|
- **Fix** `JoinMessage` text is deterministic per message id — re-rendering the same channel always yields the same join line
|
|
135
180
|
- **Fix** random `console.log` calls in production paths replaced by the `debug` namespace
|
|
@@ -153,8 +198,23 @@ In addition to plain text, replies, embeds, and attachments, the viewer supports
|
|
|
153
198
|
### DX
|
|
154
199
|
|
|
155
200
|
- **`react`, `react-dom`, `debug` moved into regular dependencies** so users don't install them manually (`debug` was actually a missing runtime dep in the original — `images.js` requires it)
|
|
156
|
-
-
|
|
157
|
-
- `
|
|
201
|
+
- **`sharp` declared as an optional peer dependency** — needed only for `.withCompression()`, no longer a hidden requirement
|
|
202
|
+
- **TypeScript declarations match runtime** — `ExportReturnType.Stream`, `language`, `i18n`, `stream`, and `withConcurrency()` are now exposed in the types
|
|
203
|
+
- `discord.js` remains the only **required** peer dependency
|
|
204
|
+
|
|
205
|
+
## API
|
|
206
|
+
|
|
207
|
+
| Export | Description |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `createTranscript(channel, options?)` | Fetch a channel's messages and render a transcript. |
|
|
210
|
+
| `generateFromMessages(messages, channel, options?)` | Render a transcript from a message array/collection you already have. |
|
|
211
|
+
| `ExportReturnType` | Enum: `Attachment` \| `Buffer` \| `String` \| `Stream`. |
|
|
212
|
+
| `TranscriptImageDownloader` | Builder for a custom image-saving callback (`withMaxSize`, `withConcurrency`, `withCompression`, `build`). |
|
|
213
|
+
| `DiscordMessages` | The underlying React component, for advanced/custom rendering. |
|
|
214
|
+
|
|
215
|
+
## License
|
|
216
|
+
|
|
217
|
+
[Apache-2.0](./LICENSE) — same as the original package.
|
|
158
218
|
|
|
159
219
|
## Credits
|
|
160
220
|
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import type { APIAttachment, APIMessage, Awaitable } from 'discord.js';
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Subset of sharp's `WebpOptions`. `sharp` is an OPTIONAL peer dependency — it is
|
|
4
|
+
* only needed for `.withCompression(..., convertToWebP)`. Typing it locally (instead
|
|
5
|
+
* of `import('sharp')`) means consumers who don't use compression are not forced to
|
|
6
|
+
* install sharp and don't get a "Cannot find module 'sharp'" error when type-checking.
|
|
7
|
+
*/
|
|
8
|
+
export interface WebpOptions {
|
|
9
|
+
quality?: number;
|
|
10
|
+
alphaQuality?: number;
|
|
11
|
+
lossless?: boolean;
|
|
12
|
+
nearLossless?: boolean;
|
|
13
|
+
smartSubsample?: boolean;
|
|
14
|
+
effort?: number;
|
|
15
|
+
loop?: number;
|
|
16
|
+
delay?: number | number[];
|
|
17
|
+
minSize?: boolean;
|
|
18
|
+
mixed?: boolean;
|
|
19
|
+
force?: boolean;
|
|
20
|
+
[option: string]: unknown;
|
|
21
|
+
}
|
|
3
22
|
/**
|
|
4
23
|
* Callback used to save an image attachment.
|
|
5
24
|
* The returned string is the URL that will be used in the transcript.
|
|
@@ -22,6 +41,11 @@ export declare class TranscriptImageDownloader {
|
|
|
22
41
|
* @param size The maximum file size in kilobytes
|
|
23
42
|
*/
|
|
24
43
|
withMaxSize(size: number): this;
|
|
44
|
+
/**
|
|
45
|
+
* Sets the maximum number of images downloaded concurrently.
|
|
46
|
+
* @param n Concurrency (default 6)
|
|
47
|
+
*/
|
|
48
|
+
withConcurrency(n: number): this;
|
|
25
49
|
/**
|
|
26
50
|
* Sets the compression quality for each image. This requires `sharp` to be installed.
|
|
27
51
|
* Optionally, images can be converted to WebP format which is smaller in size.
|
|
@@ -138,9 +138,17 @@ async function DiscordMessage({ message, context }) {
|
|
|
138
138
|
// no longer render a separate "/cmd opt:val by user" line.
|
|
139
139
|
const slashData = buildSlashCommandData(message);
|
|
140
140
|
|
|
141
|
+
// FIX: pass timestamp as ISO string. React would otherwise stringify a
|
|
142
|
+
// Date via toString() (locale-dependent like "Tue May 26 2026 …"),
|
|
143
|
+
// which skyra's <discord-message> often fails to parse — falling back
|
|
144
|
+
// to new Date() == transcript creation time. ISO is round-trip safe.
|
|
145
|
+
const createdAtIso = message.createdAt instanceof Date
|
|
146
|
+
? message.createdAt.toISOString()
|
|
147
|
+
: (typeof message.createdAt === 'string' ? message.createdAt : undefined);
|
|
148
|
+
|
|
141
149
|
return ((0, jsx_runtime_1.jsxs)("discord-message", {
|
|
142
150
|
id: `m-${message.id}`,
|
|
143
|
-
timestamp:
|
|
151
|
+
timestamp: createdAtIso,
|
|
144
152
|
edited: message.editedAt !== null,
|
|
145
153
|
server: (isCrossGuildReply || isCrosspost || isCrossposted) ? true : undefined,
|
|
146
154
|
highlight: message.mentions.everyone || pinned,
|
|
@@ -18,11 +18,16 @@ async function SystemMessage({ message, context }) {
|
|
|
18
18
|
const color = message.member?.roles?.color?.hexColor;
|
|
19
19
|
const authorId = author?.id;
|
|
20
20
|
const ctx = context || {};
|
|
21
|
+
// FIX: pass message.createdAt as ISO string so skyra renders the original
|
|
22
|
+
// timestamp instead of falling back to "now" (= transcript creation time).
|
|
23
|
+
const ts = message.createdAt instanceof Date
|
|
24
|
+
? message.createdAt.toISOString()
|
|
25
|
+
: (typeof message.createdAt === 'string' ? message.createdAt : undefined);
|
|
21
26
|
|
|
22
27
|
switch (message.type) {
|
|
23
28
|
case discord_js_1.MessageType.RecipientAdd:
|
|
24
29
|
case discord_js_1.MessageType.UserJoin:
|
|
25
|
-
return ((0, jsx_runtime_1.jsx)("discord-system-message", { id: `m-${message.id}`, type: "join", children: (0, jsx_runtime_1.jsx)(JoinMessage, { member: message.member, fallbackUser: author, seed: message.id, context: ctx }) }, message.id));
|
|
30
|
+
return ((0, jsx_runtime_1.jsx)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "join", children: (0, jsx_runtime_1.jsx)(JoinMessage, { member: message.member, fallbackUser: author, seed: message.id, context: ctx }) }, message.id));
|
|
26
31
|
|
|
27
32
|
case discord_js_1.MessageType.ChannelPinnedMessage: {
|
|
28
33
|
const refId = message.reference?.messageId;
|
|
@@ -36,7 +41,7 @@ async function SystemMessage({ message, context }) {
|
|
|
36
41
|
? (0, jsx_runtime_1.jsx)("i", { "data-goto": refId, className: "dht-jump", role: "button", tabIndex: 0, children: linkLabel })
|
|
37
42
|
: (0, jsx_runtime_1.jsx)("strong", { children: linkLabel });
|
|
38
43
|
const parts = template.includes('{message}') ? template.split('{message}') : null;
|
|
39
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "pin", children: parts
|
|
44
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "pin", children: parts
|
|
40
45
|
? [
|
|
41
46
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
42
47
|
' ',
|
|
@@ -57,7 +62,7 @@ async function SystemMessage({ message, context }) {
|
|
|
57
62
|
case discord_js_1.MessageType.GuildBoostTier1:
|
|
58
63
|
case discord_js_1.MessageType.GuildBoostTier2:
|
|
59
64
|
case discord_js_1.MessageType.GuildBoostTier3:
|
|
60
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: [
|
|
65
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "boost", children: [
|
|
61
66
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
62
67
|
' ',
|
|
63
68
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "boosted", children: t(ctx, 'boosted', 'boosted the server!') }),
|
|
@@ -67,7 +72,7 @@ async function SystemMessage({ message, context }) {
|
|
|
67
72
|
case discord_js_1.MessageType.ThreadCreated: {
|
|
68
73
|
const refId = message.reference?.messageId;
|
|
69
74
|
const label = message.content || 'New thread';
|
|
70
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "thread", children: [
|
|
75
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "thread", children: [
|
|
71
76
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
72
77
|
' ',
|
|
73
78
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "threadStarted", children: t(ctx, 'threadStarted', 'started a thread:') }),
|
|
@@ -79,7 +84,7 @@ async function SystemMessage({ message, context }) {
|
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
case discord_js_1.MessageType.ChannelNameChange:
|
|
82
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [
|
|
87
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "edit", children: [
|
|
83
88
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
84
89
|
' ',
|
|
85
90
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "changedChannelName", children: t(ctx, 'changedChannelName', 'changed the channel name:') }),
|
|
@@ -88,7 +93,7 @@ async function SystemMessage({ message, context }) {
|
|
|
88
93
|
] }, message.id));
|
|
89
94
|
|
|
90
95
|
case discord_js_1.MessageType.ChannelIconChange:
|
|
91
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [
|
|
96
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "edit", children: [
|
|
92
97
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
93
98
|
' ',
|
|
94
99
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "changedChannelIcon", children: t(ctx, 'changedChannelIcon', 'changed the channel icon.') }),
|
|
@@ -97,7 +102,7 @@ async function SystemMessage({ message, context }) {
|
|
|
97
102
|
case discord_js_1.MessageType.ChatInputCommand:
|
|
98
103
|
case discord_js_1.MessageType.ContextMenuCommand: {
|
|
99
104
|
const cmd = message.interaction?.commandName ? '/' + message.interaction.commandName : 'a command';
|
|
100
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [
|
|
105
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "call", children: [
|
|
101
106
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
102
107
|
' ',
|
|
103
108
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "usedCommand", children: t(ctx, 'usedCommand', 'used') }),
|
|
@@ -107,7 +112,7 @@ async function SystemMessage({ message, context }) {
|
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
case discord_js_1.MessageType.Call:
|
|
110
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [
|
|
115
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "call", children: [
|
|
111
116
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
112
117
|
' ',
|
|
113
118
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "startedCall", children: t(ctx, 'startedCall', 'started a call.') }),
|
|
@@ -116,7 +121,7 @@ async function SystemMessage({ message, context }) {
|
|
|
116
121
|
case discord_js_1.MessageType.ChannelFollowAdd: {
|
|
117
122
|
const followText = t(ctx, 'addedFollow', 'added {target} to follow this channel.');
|
|
118
123
|
const parts = followText.split('{target}');
|
|
119
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [
|
|
124
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "edit", children: [
|
|
120
125
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
121
126
|
' ',
|
|
122
127
|
parts[0],
|
|
@@ -126,7 +131,7 @@ async function SystemMessage({ message, context }) {
|
|
|
126
131
|
}
|
|
127
132
|
|
|
128
133
|
case discord_js_1.MessageType.RecipientRemove:
|
|
129
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "leave", children: [
|
|
134
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "leave", children: [
|
|
130
135
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
131
136
|
' ',
|
|
132
137
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "left", children: t(ctx, 'left', 'left.') }),
|
|
@@ -141,7 +146,7 @@ async function SystemMessage({ message, context }) {
|
|
|
141
146
|
const action = fields.find((f) => /action_type|action/i.test(f.name))?.value || '';
|
|
142
147
|
const blocked = e?.description || '';
|
|
143
148
|
const channelId = fields.find((f) => /channel/i.test(f.name))?.value;
|
|
144
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: [
|
|
149
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "boost", children: [
|
|
145
150
|
(0, jsx_runtime_1.jsx)("span", { "data-i18n": "autoModBlocked", children: t(ctx, 'autoModBlocked', 'AutoMod blocked a message.') }),
|
|
146
151
|
ruleName && (0, jsx_runtime_1.jsxs)("span", { className: "dht-automod-rule", children: [' · Rule: ', (0, jsx_runtime_1.jsx)("strong", { children: ruleName })] }),
|
|
147
152
|
action && (0, jsx_runtime_1.jsxs)("span", { className: "dht-automod-rule", children: [' · Action: ', action] }),
|
|
@@ -151,34 +156,34 @@ async function SystemMessage({ message, context }) {
|
|
|
151
156
|
] }, message.id));
|
|
152
157
|
}
|
|
153
158
|
case discord_js_1.MessageType.StageStart:
|
|
154
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [
|
|
159
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "call", children: [
|
|
155
160
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
156
161
|
' started a stage: ',
|
|
157
162
|
(0, jsx_runtime_1.jsx)("strong", { children: message.content || '' })
|
|
158
163
|
] }, message.id));
|
|
159
164
|
case discord_js_1.MessageType.StageEnd:
|
|
160
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "leave", children: [
|
|
165
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "leave", children: [
|
|
161
166
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
162
167
|
' ended the stage.'
|
|
163
168
|
] }, message.id));
|
|
164
169
|
case discord_js_1.MessageType.StageSpeaker:
|
|
165
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [
|
|
170
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "call", children: [
|
|
166
171
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
167
172
|
' is now a speaker.'
|
|
168
173
|
] }, message.id));
|
|
169
174
|
case discord_js_1.MessageType.StageRaiseHand:
|
|
170
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "call", children: [
|
|
175
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "call", children: [
|
|
171
176
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
172
177
|
' requested to speak.'
|
|
173
178
|
] }, message.id));
|
|
174
179
|
case discord_js_1.MessageType.StageTopic:
|
|
175
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: [
|
|
180
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "edit", children: [
|
|
176
181
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
177
182
|
' changed the stage topic: ',
|
|
178
183
|
(0, jsx_runtime_1.jsx)("strong", { children: message.content || '' })
|
|
179
184
|
] }, message.id));
|
|
180
185
|
case discord_js_1.MessageType.GuildApplicationPremiumSubscription:
|
|
181
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: [
|
|
186
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "boost", children: [
|
|
182
187
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
183
188
|
' subscribed ',
|
|
184
189
|
(0, jsx_runtime_1.jsx)("strong", { children: message.applicationId ? `app ${message.applicationId}` : 'an app' }),
|
|
@@ -187,7 +192,7 @@ async function SystemMessage({ message, context }) {
|
|
|
187
192
|
|
|
188
193
|
case discord_js_1.MessageType.RoleSubscriptionPurchase: {
|
|
189
194
|
const roleName = message.roleSubscriptionData?.tierName || message.content || 'a role';
|
|
190
|
-
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: [
|
|
195
|
+
return ((0, jsx_runtime_1.jsxs)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "boost", children: [
|
|
191
196
|
(0, jsx_runtime_1.jsx)(Highlight, { color, userId: authorId, children: authorName }),
|
|
192
197
|
' ',
|
|
193
198
|
t(ctx, 'roleSubPurchased', 'subscribed to {role}!').replace('{role}', roleName),
|
|
@@ -198,10 +203,10 @@ async function SystemMessage({ message, context }) {
|
|
|
198
203
|
case discord_js_1.MessageType.GuildIncidentReportFalseAlarm:
|
|
199
204
|
case discord_js_1.MessageType.GuildIncidentAlertModeEnabled:
|
|
200
205
|
case discord_js_1.MessageType.GuildIncidentAlertModeDisabled:
|
|
201
|
-
return ((0, jsx_runtime_1.jsx)("discord-system-message", { id: `m-${message.id}`, type: "boost", children: (0, jsx_runtime_1.jsx)("span", { "data-i18n": "serverAlert", children: t(ctx, 'serverAlert', 'Server safety alert.') }) }, message.id));
|
|
206
|
+
return ((0, jsx_runtime_1.jsx)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "boost", children: (0, jsx_runtime_1.jsx)("span", { "data-i18n": "serverAlert", children: t(ctx, 'serverAlert', 'Server safety alert.') }) }, message.id));
|
|
202
207
|
|
|
203
208
|
case discord_js_1.MessageType.PollResult:
|
|
204
|
-
return ((0, jsx_runtime_1.jsx)("discord-system-message", { id: `m-${message.id}`, type: "edit", children: (0, jsx_runtime_1.jsx)("span", { "data-i18n": "pollEnded", children: t(ctx, 'pollEnded', 'A poll ended.') }) }, message.id));
|
|
209
|
+
return ((0, jsx_runtime_1.jsx)("discord-system-message", { id: `m-${message.id}`, timestamp: ts, type: "edit", children: (0, jsx_runtime_1.jsx)("span", { "data-i18n": "pollEnded", children: t(ctx, 'pollEnded', 'A poll ended.') }) }, message.id));
|
|
205
210
|
|
|
206
211
|
// These render through the normal pipeline
|
|
207
212
|
case discord_js_1.MessageType.Default:
|
package/dist/index.js
CHANGED
|
@@ -78,8 +78,9 @@ async function generateFromMessages(messages, channel, options = {}) {
|
|
|
78
78
|
channel,
|
|
79
79
|
saveImages: options.saveImages ?? false,
|
|
80
80
|
callbacks,
|
|
81
|
-
poweredBy: options.poweredBy ??
|
|
81
|
+
poweredBy: options.poweredBy ?? false,
|
|
82
82
|
footerText: options.footerText ?? 'Exported {number} message{s}.',
|
|
83
|
+
statsFooter: options.statsFooter,
|
|
83
84
|
favicon: options.favicon ?? 'guild',
|
|
84
85
|
hydrate: options.hydrate ?? false,
|
|
85
86
|
language: options.language ?? 'en',
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AttachmentBuilder, Message } from 'discord.js';
|
|
2
2
|
import type { RenderMessageContext } from './generator';
|
|
3
|
+
import type { Readable } from 'stream';
|
|
3
4
|
export declare enum AttachmentTypes {
|
|
4
5
|
Audio = 0,
|
|
5
6
|
Video = 1,
|
|
@@ -9,9 +10,10 @@ export declare enum AttachmentTypes {
|
|
|
9
10
|
export declare enum ExportReturnType {
|
|
10
11
|
Buffer = "buffer",
|
|
11
12
|
String = "string",
|
|
12
|
-
Attachment = "attachment"
|
|
13
|
+
Attachment = "attachment",
|
|
14
|
+
Stream = "stream"
|
|
13
15
|
}
|
|
14
|
-
export type ObjectType<T extends ExportReturnType> = T extends ExportReturnType.Buffer ? Buffer : T extends ExportReturnType.String ? string : AttachmentBuilder;
|
|
16
|
+
export type ObjectType<T extends ExportReturnType> = T extends ExportReturnType.Buffer ? Buffer : T extends ExportReturnType.String ? string : T extends ExportReturnType.Stream ? Readable : AttachmentBuilder;
|
|
15
17
|
export type GenerateFromMessagesOptions<T extends ExportReturnType> = Partial<{
|
|
16
18
|
/**
|
|
17
19
|
* The type of object to return
|
|
@@ -33,8 +35,9 @@ export type GenerateFromMessagesOptions<T extends ExportReturnType> = Partial<{
|
|
|
33
35
|
*/
|
|
34
36
|
filename: string;
|
|
35
37
|
/**
|
|
36
|
-
* Whether to include the "Powered by discord-html-transcripts"
|
|
37
|
-
*
|
|
38
|
+
* Whether to include the "Powered by discord-html-transcripts" credit link.
|
|
39
|
+
* Only renders when the stats footer is disabled (`statsFooter: false`).
|
|
40
|
+
* @default false
|
|
38
41
|
*/
|
|
39
42
|
poweredBy: boolean;
|
|
40
43
|
/**
|
|
@@ -68,6 +71,22 @@ export type GenerateFromMessagesOptions<T extends ExportReturnType> = Partial<{
|
|
|
68
71
|
enabled?: boolean;
|
|
69
72
|
template?: string;
|
|
70
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* UI language for the built-in strings (participant labels, filter UI, stats footer, …).
|
|
76
|
+
* @default 'en'
|
|
77
|
+
*/
|
|
78
|
+
language: 'en' | 'de';
|
|
79
|
+
/**
|
|
80
|
+
* Override individual built-in strings per language. Merged over the defaults.
|
|
81
|
+
* Keys are the string ids used internally (e.g. `statsMessages`, `statsParticipants`).
|
|
82
|
+
*/
|
|
83
|
+
i18n: Partial<Record<'en' | 'de', Record<string, string>>>;
|
|
84
|
+
/**
|
|
85
|
+
* Return a Node `Readable` stream of the rendered HTML instead of buffering it.
|
|
86
|
+
* Equivalent to `returnType: ExportReturnType.Stream`. Best for very large exports.
|
|
87
|
+
* @default false
|
|
88
|
+
*/
|
|
89
|
+
stream: boolean;
|
|
71
90
|
}>;
|
|
72
91
|
export type CreateTranscriptOptions<T extends ExportReturnType> = Partial<GenerateFromMessagesOptions<T> & {
|
|
73
92
|
/**
|
package/package.json
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discord-html-transcripts-fix",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "A nicely formatted html transcript generator for discord.js. Bugfix fork with support for the latest discord.js and Components v2.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
|
+
"author": "Developer-Sven (https://github.com/Developer-Sven)",
|
|
9
|
+
"homepage": "https://github.com/Developer-Sven/discord-html-transcripts-fix#readme",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/Developer-Sven/discord-html-transcripts-fix.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/Developer-Sven/discord-html-transcripts-fix/issues"
|
|
16
|
+
},
|
|
8
17
|
"keywords": [
|
|
9
18
|
"discord",
|
|
10
19
|
"discord.js",
|
|
@@ -34,7 +43,16 @@
|
|
|
34
43
|
"undici": "^7.16.0"
|
|
35
44
|
},
|
|
36
45
|
"peerDependencies": {
|
|
37
|
-
"discord.js": "^14.0.0 || ^15.0.0"
|
|
46
|
+
"discord.js": "^14.0.0 || ^15.0.0",
|
|
47
|
+
"sharp": ">=0.30.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"sharp": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=20"
|
|
38
56
|
},
|
|
39
57
|
"scripts": {
|
|
40
58
|
"prepublishOnly": "node -e \"require('fs').accessSync('dist/index.js'); require('fs').accessSync('dist/index.d.ts');\" || (echo 'dist/index.js or dist/index.d.ts missing — refusing to publish' && exit 1)"
|