discord-html-transcripts-fix 2.2.0 → 2.3.1
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 +79 -25
- package/dist/generator/index.js +32 -22
- package/dist/generator/renderers/attachment.js +15 -3
- package/dist/generator/renderers/components/DiscordHighlightedCode.js +30 -4
- package/dist/generator/renderers/components/Media Gallery.js +2 -1
- package/dist/generator/renderers/components/Select Menu.js +8 -2
- package/dist/generator/renderers/components/TranscriptHeader.js +22 -2
- package/dist/generator/renderers/components.js +6 -3
- package/dist/generator/renderers/content.js +45 -10
- package/dist/generator/renderers/embed.js +24 -9
- package/dist/generator/renderers/message.js +76 -28
- package/dist/generator/renderers/reply.js +18 -5
- package/dist/generator/renderers/systemMessage.js +20 -5
- package/dist/index.js +7 -0
- package/dist/static/client.js +10 -0
- package/dist/types.d.ts +19 -10
- package/dist/utils/buildProfiles.js +75 -17
- package/dist/utils/selfContained.js +402 -50
- package/dist/utils/utils.js +107 -2
- package/package.json +8 -3
|
@@ -4,6 +4,7 @@ exports.buildProfiles = buildProfiles;
|
|
|
4
4
|
exports.buildExtendedContext = buildExtendedContext;
|
|
5
5
|
exports.buildAllContext = buildAllContext;
|
|
6
6
|
const discord_js_1 = require("discord.js");
|
|
7
|
+
const utils_1 = require("./utils");
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Backwards compatible: returns the profile dict used by <discord-mention> web components.
|
|
@@ -37,7 +38,8 @@ async function buildAllContext(messages, channel) {
|
|
|
37
38
|
? channel.guild
|
|
38
39
|
: null;
|
|
39
40
|
|
|
40
|
-
|
|
41
|
+
// id -> 'member' | 'bare': whether the entry was built from the guild member.
|
|
42
|
+
const userState = new Map();
|
|
41
43
|
const seenRoleIds = new Set();
|
|
42
44
|
const seenChannelIds = new Set();
|
|
43
45
|
|
|
@@ -73,12 +75,48 @@ async function buildAllContext(messages, channel) {
|
|
|
73
75
|
};
|
|
74
76
|
};
|
|
75
77
|
|
|
78
|
+
// Plain-object input may carry dates as strings; discord.js gives Dates.
|
|
79
|
+
const isoDate = (value) => {
|
|
80
|
+
const date = value instanceof Date ? value
|
|
81
|
+
: (typeof value === 'string' || typeof value === 'number') ? new Date(value) : null;
|
|
82
|
+
return date && !Number.isNaN(date.getTime()) ? date.toISOString() : null;
|
|
83
|
+
};
|
|
84
|
+
|
|
76
85
|
const includeUser = (member, author) => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
const id = author?.id;
|
|
87
|
+
if (!id) return;
|
|
88
|
+
// The first sighting must not decide: a user seen first without their
|
|
89
|
+
// member (a mention, a command, a forward) would otherwise keep a profile
|
|
90
|
+
// without nickname, roles and color for the whole transcript.
|
|
91
|
+
const state = userState.get(id);
|
|
92
|
+
if (state === 'member' || (state === 'bare' && !member)) return;
|
|
93
|
+
userState.set(id, member ? 'member' : 'bare');
|
|
94
|
+
try {
|
|
95
|
+
buildUser(id, member, author);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
// One malformed user must not cost every other profile in the transcript.
|
|
100
|
+
console.warn(`[discord-html-transcripts-fix] could not build the profile of user ${id}${member ? ' from their guild member' : ''}:`, err);
|
|
101
|
+
}
|
|
102
|
+
// A broken member — discord.js' role getters throw when the @everyone role
|
|
103
|
+
// is not cached, for one — still leaves the user's own name and avatar.
|
|
104
|
+
if (member && !profiles[id]) {
|
|
105
|
+
try {
|
|
106
|
+
buildUser(id, null, author);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.warn(`[discord-html-transcripts-fix] could not build the profile of user ${id}:`, err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
81
113
|
|
|
114
|
+
// discord.js gives a method; a user serialised with toJSON() carries the URL itself.
|
|
115
|
+
const avatarOf = (subject, size) => typeof subject?.displayAvatarURL === 'function'
|
|
116
|
+
? subject.displayAvatarURL({ size, forceStatic: true })
|
|
117
|
+
: (typeof subject?.displayAvatarURL === 'string' ? subject.displayAvatarURL : undefined);
|
|
118
|
+
|
|
119
|
+
const buildUser = (id, member, author) => {
|
|
82
120
|
// Compute member roles first so we can derive the name color from the
|
|
83
121
|
// highest *listed* role (top of the role list), matching how Discord
|
|
84
122
|
// colors usernames. Falls back to displayHexColor (highest *colored*
|
|
@@ -103,9 +141,9 @@ async function buildAllContext(messages, channel) {
|
|
|
103
141
|
// whose hash still carries the `a_` prefix). The static variant (.webp
|
|
104
142
|
// or .png) always works, so render avatars as static in transcripts.
|
|
105
143
|
// Avatars in a historical archive don't gain much from being animated.
|
|
106
|
-
|
|
144
|
+
const profile = {
|
|
107
145
|
author: member?.nickname ?? author.displayName ?? author.username,
|
|
108
|
-
avatar: member
|
|
146
|
+
avatar: avatarOf(member, 64) ?? avatarOf(author, 64),
|
|
109
147
|
roleColor: effectiveColor,
|
|
110
148
|
roleIcon: member?.roles?.icon?.iconURL?.() ?? undefined,
|
|
111
149
|
roleName: member?.roles?.hoist?.name ?? undefined,
|
|
@@ -125,19 +163,19 @@ async function buildAllContext(messages, channel) {
|
|
|
125
163
|
}
|
|
126
164
|
}
|
|
127
165
|
|
|
128
|
-
|
|
166
|
+
const user = {
|
|
129
167
|
id,
|
|
130
168
|
username: author.username,
|
|
131
169
|
displayName: member?.nickname || author.displayName || author.username,
|
|
132
170
|
globalName: author.globalName || null,
|
|
133
|
-
avatar: member
|
|
171
|
+
avatar: avatarOf(member, 128) || avatarOf(author, 128) || null,
|
|
134
172
|
bannerColor: effectiveColor,
|
|
135
173
|
bot: !!author.bot,
|
|
136
174
|
system: !!author.system,
|
|
137
175
|
verifiedBot: flagNames.includes('VerifiedBot'),
|
|
138
176
|
flags: flagNames,
|
|
139
|
-
joinedAt: member?.joinedAt
|
|
140
|
-
createdAt: author.createdAt
|
|
177
|
+
joinedAt: isoDate(member?.joinedAt),
|
|
178
|
+
createdAt: isoDate(author.createdAt),
|
|
141
179
|
roles: memberRoles.map((r) => ({
|
|
142
180
|
id: r.id,
|
|
143
181
|
name: r.name,
|
|
@@ -148,9 +186,12 @@ async function buildAllContext(messages, channel) {
|
|
|
148
186
|
? { name: memberRoles[0].name, color: memberRoles[0].hexColor }
|
|
149
187
|
: null,
|
|
150
188
|
};
|
|
189
|
+
// Both at once, so a failure above never leaves half an entry behind.
|
|
190
|
+
profiles[id] = profile;
|
|
191
|
+
users[id] = user;
|
|
151
192
|
};
|
|
152
193
|
|
|
153
|
-
|
|
194
|
+
const collect = (message) => {
|
|
154
195
|
if (message.author) includeUser(message.member, message.author);
|
|
155
196
|
if (message.interaction?.user) {
|
|
156
197
|
// FIX: try to find a member object for this user from later messages (interaction user may have authored their own messages elsewhere).
|
|
@@ -174,11 +215,28 @@ async function buildAllContext(messages, channel) {
|
|
|
174
215
|
for (const [cid] of message.mentions.channels) includeChannelFromGuild(cid);
|
|
175
216
|
}
|
|
176
217
|
}
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
218
|
+
// Authors of forwarded snapshots — from plain-object input only, exactly
|
|
219
|
+
// as the renderer names them. Discord hides who wrote a forward, and a
|
|
220
|
+
// discord.js snapshot of a message the bot has cached is that cached
|
|
221
|
+
// original, author included (see renderSnapshots).
|
|
222
|
+
// messageSnapshots is a discord.js Collection, not an array.
|
|
223
|
+
const snaps = message.messageSnapshots;
|
|
224
|
+
const snapList = !snaps ? [] : Array.isArray(snaps) ? snaps : typeof snaps.values === 'function' ? Array.from(snaps.values()) : [];
|
|
225
|
+
for (const snap of snapList) {
|
|
226
|
+
// Raw payloads nest the snapshot under `message`, like the renderer reads it.
|
|
227
|
+
const snapMessage = snap?.message || snap;
|
|
228
|
+
if (!snapMessage || (0, utils_1.isLibraryStructure)(snapMessage)) continue;
|
|
229
|
+
includeUser(null, snapMessage.author);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
for (const message of messages) {
|
|
234
|
+
try {
|
|
235
|
+
collect(message);
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
// A malformed message costs its own entries, not every profile.
|
|
239
|
+
console.warn('[discord-html-transcripts-fix] could not collect the profiles of message', message?.id, err);
|
|
182
240
|
}
|
|
183
241
|
}
|
|
184
242
|
|