buncord-transcript 1.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.
@@ -0,0 +1,383 @@
1
+ export const css = `
2
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
3
+ @import url('https://fonts.googleapis.com/css2?family=Fira+Code&display=swap');
4
+
5
+ :root {
6
+ --background-primary: #313338;
7
+ --background-secondary: #2b2d31;
8
+ --background-secondary-alt: #242529;
9
+ --background-tertiary: #1e1f22;
10
+ --background-floating: #1e1f22;
11
+ --background-accent: #404249;
12
+ --background-modifier-hover: rgba(78, 80, 88, 0.16);
13
+ --background-modifier-active: rgba(78, 80, 88, 0.24);
14
+ --background-modifier-selected: rgba(78, 80, 88, 0.32);
15
+ --text-normal: #dbdee1;
16
+ --text-muted: #949ba4;
17
+ --text-link: #00a8fc;
18
+ --text-positive: #23a559;
19
+ --text-warning: #f0b232;
20
+ --text-danger: #f23f43;
21
+ --header-primary: #f2f3f5;
22
+ --header-secondary: #b5bac1;
23
+ --interactive-normal: #b5bac1;
24
+ --interactive-hover: #dbdee1;
25
+ --interactive-active: #ffffff;
26
+ --interactive-muted: #4e5058;
27
+ --brand-experiment: #5865f2;
28
+ --brand-experiment-560: #4752c4;
29
+ --status-online: #23a559;
30
+ --status-idle: #f0b232;
31
+ --status-dnd: #f23f43;
32
+ --status-offline: #80848e;
33
+ --button-secondary-background: #4e5058;
34
+ --button-secondary-background-hover: #6d6f78;
35
+ --button-secondary-background-active: #80848e;
36
+ --button-danger-background: #da373c;
37
+ --button-danger-background-hover: #a12828;
38
+ --button-danger-background-active: #892222;
39
+ --button-positive-background: #248046;
40
+ --button-positive-background-hover: #1a6334;
41
+ --button-positive-background-active: #15522b;
42
+ --mention-background: rgba(88, 101, 242, 0.3);
43
+ --mention-foreground: #dee0fc;
44
+ --mention-hover-background: rgba(88, 101, 242, 0.6);
45
+ --embed-background: #2b2d31;
46
+ --embed-background-alternate: #242529;
47
+ --message-margin-horizontal: 16px;
48
+ --message-margin-vertical: 1.0625rem;
49
+ --avatar-size: 40px;
50
+ }
51
+
52
+ * { box-sizing: border-box; }
53
+
54
+ body {
55
+ background-color: var(--background-primary);
56
+ color: var(--text-normal);
57
+ font-family: 'Inter', 'gg sans', 'Whitney', 'Helvetica Neue', Helvetica, Arial, sans-serif;
58
+ font-size: 1rem;
59
+ line-height: 1.375rem;
60
+ margin: 0;
61
+ padding: 0;
62
+ -webkit-font-smoothing: antialiased;
63
+ }
64
+
65
+ a { color: var(--text-link); text-decoration: none; }
66
+ a:hover { text-decoration: underline; }
67
+
68
+ .guild-header {
69
+ background-color: var(--background-primary);
70
+ padding: 0 16px;
71
+ display: flex;
72
+ align-items: center;
73
+ position: sticky;
74
+ top: 0;
75
+ z-index: 100;
76
+ height: 48px;
77
+ box-shadow: 0 1px 0 rgba(0,0,0,0.2), 0 1.5px 0 rgba(0,0,0,0.05), 0 2px 0 rgba(0,0,0,0.05);
78
+ }
79
+
80
+ .guild-icon {
81
+ width: 24px;
82
+ height: 24px;
83
+ border-radius: 50%;
84
+ margin-right: 8px;
85
+ }
86
+
87
+ .guild-info h1 {
88
+ font-size: 1rem;
89
+ font-weight: 600;
90
+ color: var(--header-primary);
91
+ margin: 0;
92
+ }
93
+
94
+ .channel-info {
95
+ font-size: 0.875rem;
96
+ color: var(--header-secondary);
97
+ margin-left: 8px;
98
+ padding-left: 8px;
99
+ border-left: 1px solid var(--background-modifier-active);
100
+ }
101
+
102
+ .chatlog { padding: 0; }
103
+
104
+ .message-group {
105
+ display: flex;
106
+ margin-top: var(--message-margin-vertical);
107
+ padding: 2px 16px;
108
+ position: relative;
109
+ }
110
+
111
+ .message-group:hover {
112
+ background-color: rgba(0,0,0,0.02);
113
+ }
114
+
115
+ .author-avatar {
116
+ width: var(--avatar-size);
117
+ height: var(--avatar-size);
118
+ border-radius: 50%;
119
+ margin-right: 16px;
120
+ margin-top: 2px;
121
+ flex-shrink: 0;
122
+ cursor: pointer;
123
+ }
124
+
125
+ .message-content-wrapper { flex: 1; min-width: 0; }
126
+
127
+ .message-header {
128
+ display: flex;
129
+ align-items: center;
130
+ line-height: 1.375rem;
131
+ }
132
+
133
+ .author-name {
134
+ font-size: 1rem;
135
+ font-weight: 500;
136
+ color: var(--header-primary);
137
+ margin-right: 0.25rem;
138
+ cursor: pointer;
139
+ }
140
+
141
+ .author-name:hover { text-decoration: underline; }
142
+
143
+ .bot-tag {
144
+ background-color: var(--brand-experiment);
145
+ color: #ffffff;
146
+ font-size: 0.625rem;
147
+ text-transform: uppercase;
148
+ height: 0.9375rem;
149
+ padding: 0 0.275rem;
150
+ border-radius: 0.1875rem;
151
+ line-height: normal;
152
+ font-weight: 500;
153
+ margin-left: 0.25rem;
154
+ display: inline-flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ vertical-align: baseline;
158
+ position: relative;
159
+ top: -1px;
160
+ }
161
+
162
+ .bot-tag svg {
163
+ margin-right: 2px;
164
+ width: 14px;
165
+ height: 14px;
166
+ }
167
+
168
+ .bot-tag-text {
169
+ line-height: 0.9375rem;
170
+ }
171
+
172
+ .timestamp {
173
+ font-size: 0.75rem;
174
+ color: var(--text-muted);
175
+ margin-left: 0.5rem;
176
+ font-weight: 400;
177
+ }
178
+
179
+ .message-body {
180
+ font-size: 1rem;
181
+ line-height: 1.375rem;
182
+ color: var(--text-normal);
183
+ white-space: pre-wrap;
184
+ word-wrap: break-word;
185
+ margin-top: 2px;
186
+ }
187
+
188
+ .mention {
189
+ background-color: var(--mention-background);
190
+ color: var(--mention-foreground);
191
+ border-radius: 3px;
192
+ padding: 0 2px;
193
+ font-weight: 500;
194
+ cursor: pointer;
195
+ }
196
+
197
+ .mention:hover {
198
+ background-color: var(--mention-hover-background);
199
+ color: #ffffff;
200
+ }
201
+
202
+ .reply-reference {
203
+ display: flex;
204
+ align-items: center;
205
+ font-size: 0.875rem;
206
+ color: var(--text-muted);
207
+ margin-bottom: 4px;
208
+ margin-left: 56px;
209
+ position: relative;
210
+ }
211
+
212
+ .reply-reference::before {
213
+ content: "";
214
+ display: block;
215
+ position: absolute;
216
+ top: 50%;
217
+ left: -33px;
218
+ bottom: 0;
219
+ border-top: 2px solid #4e5058;
220
+ border-left: 2px solid #4e5058;
221
+ border-top-left-radius: 6px;
222
+ width: 33px;
223
+ height: 10px;
224
+ }
225
+
226
+ .reply-avatar {
227
+ width: 16px;
228
+ height: 16px;
229
+ border-radius: 50%;
230
+ margin-right: 4px;
231
+ }
232
+
233
+ .reply-user {
234
+ font-weight: 500;
235
+ margin-right: 4px;
236
+ cursor: pointer;
237
+ }
238
+
239
+ .reply-user:hover { text-decoration: underline; }
240
+
241
+ .reply-content {
242
+ overflow: hidden;
243
+ text-overflow: ellipsis;
244
+ white-space: nowrap;
245
+ }
246
+
247
+ .embed {
248
+ display: flex;
249
+ max-width: 520px;
250
+ background-color: var(--background-secondary);
251
+ border-radius: 4px;
252
+ border-left: 4px solid var(--background-tertiary);
253
+ margin-top: 8px;
254
+ padding: 8px 16px 16px 12px;
255
+ position: relative;
256
+ }
257
+
258
+ .embed-content {
259
+ display: flex;
260
+ flex-direction: column;
261
+ flex: 1;
262
+ }
263
+
264
+ .embed-author {
265
+ display: flex;
266
+ align-items: center;
267
+ margin-top: 8px;
268
+ margin-bottom: 8px;
269
+ }
270
+
271
+ .embed-author-icon {
272
+ width: 24px;
273
+ height: 24px;
274
+ border-radius: 50%;
275
+ margin-right: 8px;
276
+ }
277
+
278
+ .embed-author-name {
279
+ font-size: 0.875rem;
280
+ font-weight: 600;
281
+ color: var(--header-primary);
282
+ }
283
+
284
+ .embed-title {
285
+ font-size: 1rem;
286
+ font-weight: 600;
287
+ color: var(--text-link);
288
+ margin-bottom: 4px;
289
+ }
290
+
291
+ .embed-description {
292
+ font-size: 0.875rem;
293
+ line-height: 1.125rem;
294
+ color: var(--text-normal);
295
+ white-space: pre-wrap;
296
+ }
297
+
298
+ .embed-fields {
299
+ display: grid;
300
+ grid-gap: 8px;
301
+ margin-top: 8px;
302
+ }
303
+
304
+ .embed-field-name {
305
+ font-size: 0.875rem;
306
+ font-weight: 600;
307
+ color: var(--header-primary);
308
+ margin-bottom: 2px;
309
+ }
310
+
311
+ .embed-field-value {
312
+ font-size: 0.875rem;
313
+ color: var(--text-normal);
314
+ white-space: pre-wrap;
315
+ }
316
+
317
+ .embed-footer {
318
+ display: flex;
319
+ align-items: center;
320
+ margin-top: 8px;
321
+ }
322
+
323
+ .embed-footer-icon {
324
+ width: 20px;
325
+ height: 20px;
326
+ border-radius: 50%;
327
+ margin-right: 8px;
328
+ }
329
+
330
+ .embed-footer-text {
331
+ font-size: 0.75rem;
332
+ color: var(--text-muted);
333
+ }
334
+
335
+ .message-component-group {
336
+ display: flex;
337
+ flex-wrap: wrap;
338
+ gap: 8px;
339
+ margin-top: 8px;
340
+ }
341
+
342
+ .discord-button {
343
+ height: 32px;
344
+ min-width: 60px;
345
+ padding: 2px 16px;
346
+ border-radius: 3px;
347
+ font-size: 14px;
348
+ font-weight: 500;
349
+ color: #ffffff;
350
+ border: none;
351
+ cursor: pointer;
352
+ display: flex;
353
+ align-items: center;
354
+ justify-content: center;
355
+ transition: background-color 0.17s ease;
356
+ }
357
+
358
+ .discord-button-primary { background-color: var(--brand-experiment); }
359
+ .discord-button-primary:hover { background-color: var(--brand-experiment-560); }
360
+ .discord-button-secondary { background-color: var(--button-secondary-background); }
361
+ .discord-button-secondary:hover { background-color: var(--button-secondary-background-hover); }
362
+ .discord-button-success { background-color: var(--button-positive-background); }
363
+ .discord-button-success:hover { background-color: var(--button-positive-background-hover); }
364
+ .discord-button-destructive { background-color: var(--button-danger-background); }
365
+ .discord-button-destructive:hover { background-color: var(--button-danger-background-hover); }
366
+
367
+ .discord-container {
368
+ background-color: var(--background-secondary);
369
+ border: 1px solid rgba(255,255,255,0.05);
370
+ border-radius: 8px;
371
+ padding: 12px;
372
+ margin-top: 8px;
373
+ max-width: 520px;
374
+ }
375
+
376
+ .discord-separator {
377
+ height: 1px;
378
+ background-color: rgba(255,255,255,0.05);
379
+ margin: 8px 0;
380
+ }
381
+ `;
382
+
383
+ export const htmlTemplate = "<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'><meta name='viewport' content='width=device-width, initial-scale=1.0'><title>Transcript - {{channel.name}}</title><style>{{css}}</style></head><body><header class='guild-header'>{{#channel.guildIconUrl}}<img src='{{channel.guildIconUrl}}' alt='Guild Icon' class='guild-icon'>{{/channel.guildIconUrl}}<div class='guild-info'><h1>{{channel.name}}</h1></div>{{#channel.topic}}<div class='channel-info'>{{channel.topic}}</div>{{/channel.topic}}</header><div class='chatlog'>{{#messages}}<div class='message-group' id='message-{{id}}'>{{#replyTo}}<div class='reply-reference'><img src='{{replyTo.author.avatarURL}}' class='reply-avatar'><span class='reply-user' style='color: {{replyTo.author.color}};'>{{replyTo.author.username}}</span><span class='reply-content'>{{replyTo.contentSnippet}}</span></div>{{/replyTo}}<img src='{{author.avatarURL}}' class='author-avatar' alt='{{author.username}}'><div class='message-content-wrapper'><div class='message-header'><span class='author-name' {{#author.color}}style='color:{{author.color}}'{{/author.color}}>{{author.username}}</span>{{#author.bot}}<span class='bot-tag'><svg aria-hidden='true' role='img' xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' viewBox='0 0 24 24'><path fill='white' fill-rule='evenodd' d='M19.06 6.94a1.5 1.5 0 0 1 0 2.12l-8 8a1.5 1.5 0 0 1-2.12 0l-4-4a1.5 1.5 0 0 1 2.12-2.12L10 13.88l6.94-6.94a1.5 1.5 0 0 1 2.12 0Z' clip-rule='evenodd'></path></svg><span class='bot-tag-text'>APP</span></span>{{/author.bot}}<span class='timestamp'>{{timestamp}}</span></div><div class='message-body'>{{{content}}}</div>{{#attachments}}<div class='attachment attachment-image'><a href='{{url}}' target='_blank'><img src='{{url}}' alt='{{name}}' style='max-width: 100%; max-height: 350px; border-radius: 4px; margin-top: 8px;'></a></div>{{/attachments}}{{#containers}}<div class='discord-container'>{{{content}}}</div>{{/containers}}{{#components}}<div class='message-component-group'>{{#components}}{{#isButton}}<button class='discord-button discord-button-{{styleClass}}' type='button' {{#disabled}}disabled{{/disabled}}>{{#emoji.id}}<img src='https://cdn.discordapp.com/emojis/{{emoji.id}}.webp?size=44&quality=lossless' alt='{{emoji.name}}' style='width: 16px; height: 16px; margin-right: 8px;'>{{/emoji.id}}{{#emoji.name}}{{^emoji.id}}<span style='margin-right: 8px;'>{{emoji.name}}</span>{{/emoji.id}}{{/emoji.name}}<span>{{label}}</span></button>{{/isButton}}{{#isSelectMenu}}<div class='discord-select-menu' style='background: var(--background-tertiary); padding: 8px 12px; border-radius: 4px; color: var(--text-muted); cursor: pointer; display: flex; justify-content: space-between; align-items: center; min-width: 200px; margin-top: 4px;'><span>{{placeholder}}</span><svg width='24' height='24' viewBox='0 0 24 24'><path fill='currentColor' d='M7 10L12 15L17 10H7Z' /></svg></div>{{/isSelectMenu}}{{/components}}</div>{{/components}}{{#embeds}}<div class='embed' {{#hexColor}}style='border-left: 4px solid {{hexColor}};'{{/hexColor}}><div class='embed-content'>{{#author}}<div class='embed-author'>{{#iconURL}}<img src='{{iconURL}}' class='embed-author-icon'>{{/iconURL}}<span class='embed-author-name'>{{name}}</span></div>{{/author}}{{#title}}<div class='embed-title'>{{title}}</div>{{/title}}{{#description}}<div class='embed-description'>{{{description}}}</div>{{/description}}{{#fields.length}}<div class='embed-fields'>{{#fields}}<div class='embed-field'><div class='embed-field-name'>{{name}}</div><div class='embed-field-value'>{{{value}}}</div></div>{{/fields}}</div>{{/fields.length}}{{#image}}<img src='{{url}}' style='max-width: 100%; border-radius: 4px; margin-top: 8px;'>{{/image}}{{#footer}}<div class='embed-footer'>{{#iconURL}}<img src='{{iconURL}}' class='embed-footer-icon'>{{/iconURL}}<span class='embed-footer-text'>{{text}}</span></div>{{/footer}}</div></div>{{/embeds}}</div></div>{{/messages}}</div></body></html>";
package/src/types.ts ADDED
@@ -0,0 +1,173 @@
1
+ export interface Attachment {
2
+ id: string;
3
+ url: string;
4
+ name: string;
5
+ contentType?: string;
6
+ }
7
+
8
+ export interface EmbedAuthor {
9
+ name: string;
10
+ url?: string;
11
+ iconURL?: string;
12
+ }
13
+
14
+ export interface EmbedFooter {
15
+ text: string;
16
+ iconURL?: string;
17
+ }
18
+
19
+ export interface EmbedField {
20
+ name: string;
21
+ value: string;
22
+ inline?: boolean;
23
+ }
24
+
25
+ export interface Embed {
26
+ title?: string;
27
+ url?: string;
28
+ description?: string;
29
+ author?: EmbedAuthor;
30
+ color?: number; // Integer color
31
+ footer?: EmbedFooter;
32
+ image?: { url: string };
33
+ thumbnail?: { url: string };
34
+ timestamp?: string; // ISO string
35
+ fields?: EmbedField[];
36
+ }
37
+
38
+ export interface Reaction {
39
+ emoji: {
40
+ id?: string | null;
41
+ name: string | null;
42
+ url?: string | null;
43
+ };
44
+ count: number;
45
+ }
46
+
47
+ export interface Container {
48
+ content: string; // Markdown content inside the container
49
+ }
50
+
51
+
52
+ export interface Button {
53
+ type: 2;
54
+ style: 1 | 2 | 3 | 4 | 5; // 1: Primary (Blurple), 2: Secondary (Grey), 3: Success (Green), 4: Danger (Red), 5: Link (Grey/Url)
55
+ label?: string;
56
+ customId?: string;
57
+ url?: string;
58
+ disabled?: boolean;
59
+ emoji?: {
60
+ id?: string;
61
+ name: string;
62
+ animated?: boolean;
63
+ };
64
+ }
65
+
66
+ export interface SelectMenuOption {
67
+ label: string;
68
+ value: string;
69
+ description?: string;
70
+ emoji?: {
71
+ id?: string;
72
+ name: string;
73
+ animated?: boolean;
74
+ };
75
+ default?: boolean;
76
+ }
77
+
78
+ export interface SelectMenu {
79
+ type: 3 | 5 | 6 | 7 | 8; // 3: String, 5: User, 6: Role, 7: Mentionable, 8: Channel
80
+ customId: string;
81
+ options?: SelectMenuOption[]; // Only for String Select (type 3)
82
+ placeholder?: string;
83
+ minValues?: number;
84
+ maxValues?: number;
85
+ disabled?: boolean;
86
+ }
87
+
88
+ export interface MediaGalleryItem {
89
+ media: {
90
+ url: string;
91
+ width?: number;
92
+ height?: number;
93
+ };
94
+ description?: string;
95
+ }
96
+
97
+ export interface MediaGallery {
98
+ items: MediaGalleryItem[];
99
+ }
100
+
101
+ export interface Separator {
102
+ divider: boolean;
103
+ spacing: 1 | 2 | 3; // Small, Medium, Large
104
+ }
105
+
106
+ export type ActionRowComponent = Button | SelectMenu;
107
+
108
+ export interface ActionRow {
109
+ type: 1;
110
+ components: ActionRowComponent[];
111
+ }
112
+
113
+ export interface TextDisplayComponent {
114
+ type: 10;
115
+ content: string;
116
+ }
117
+
118
+ export interface SeparatorComponent {
119
+ type: 14;
120
+ divider?: boolean;
121
+ spacing?: 1 | 2 | 3; // Small, Medium, Large
122
+ }
123
+
124
+ export interface ContainerComponent {
125
+ type: 17;
126
+ components: (ActionRow | TextDisplayComponent | SeparatorComponent)[];
127
+ accentColor?: number;
128
+ spoiler?: boolean;
129
+ }
130
+
131
+ export type AnyComponent = ActionRow | Button | SelectMenu | TextDisplayComponent | SeparatorComponent | ContainerComponent;
132
+
133
+ export interface Message {
134
+ id: string;
135
+ content: string;
136
+ author: {
137
+ id: string;
138
+ username: string;
139
+ discriminator?: string; // Legacy support
140
+ avatarURL: string;
141
+ bot?: boolean;
142
+ color?: string; // Hex color for username
143
+ };
144
+ timestamp: string; // ISO string
145
+ editedTimestamp?: string | null;
146
+ attachments: Attachment[];
147
+ embeds: Embed[];
148
+ containers?: Container[]; // For rendered HTML content
149
+ components?: AnyComponent[]; // Updated to allow all component types including top-level Containers
150
+ mediaGalleries?: MediaGallery[]; // V2 Media Gallery
151
+ separators?: Separator[]; // V2 Spacing/Dividers
152
+ reactions: Reaction[];
153
+ reference?: {
154
+ messageId: string;
155
+ };
156
+ replyTo?: Message; // Populated during processing if reference exists
157
+ }
158
+
159
+ export interface ChannelInfo {
160
+ name: string;
161
+ topic?: string;
162
+ id: string;
163
+ guildName?: string;
164
+ guildIconUrl?: string;
165
+ }
166
+
167
+ export interface TranscriptOptions {
168
+ returnType?: 'string' | 'buffer' | 'file'; // Default buffer
169
+ fileName?: string; // For 'file' type output
170
+ minify?: boolean; // Default true
171
+ saveImages?: boolean; // TODO: Future feature to download images?
172
+ poweredBy?: boolean; // Show "Powered by..." footer
173
+ }