Skip to content

Commit e36d8f7

Browse files
committed
[INSTALL] Support quoting forwards, remove Quote#exec
1 parent cafdec7 commit e36d8f7

File tree

8 files changed

+230
-274
lines changed

8 files changed

+230
-274
lines changed

lib/extensions/message.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PermissionFlagsBits } from "discord-api-types/v9";
99
import {
1010
Channel,
1111
Collection,
12+
Constants,
1213
DMChannel,
1314
DiscordAPIError,
1415
EmojiIdentifierResolvable,
@@ -409,6 +410,24 @@ export class FireMessage extends Message {
409410
webhook?: WebhookClient,
410411
debug?: string[]
411412
) {
413+
// check for forwarded message from guilds on current cluster
414+
if (
415+
this.reference?.type == Constants.MessageReferenceType.FORWARD &&
416+
this.client.guilds.cache.has(this.reference.guildId)
417+
) {
418+
const guild = this.client.guilds.cache.get(this.reference.guildId);
419+
if (!guild) return;
420+
const channel = guild.channels.cache.get(this.reference.channelId);
421+
if (!channel || !("messages" in channel)) return;
422+
const message = (await channel.messages.fetch(
423+
this.reference.messageId
424+
)) as FireMessage;
425+
if (message)
426+
return await message.quote(destination, quoter, webhook, debug);
427+
else return;
428+
} else if (this.reference?.type == Constants.MessageReferenceType.FORWARD)
429+
return; // not cached and nothing to quote so just ignore it
430+
412431
// check for quoteable content first
413432
if (
414433
!this.content &&

lib/util/clientutil.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,11 +1057,9 @@ export class Util extends ClientUtil {
10571057

10581058
async getQuoteWebhookURL(destination: GuildTextChannel | ThreadChannel) {
10591059
let thread: ThreadChannel;
1060-
if (destination instanceof ThreadChannel) {
1061-
// we can't assign thread to destination since we're reassigning it
1062-
thread = this.client.channels.cache.get(destination.id) as ThreadChannel;
1060+
if (destination instanceof ThreadChannel)
10631061
destination = destination.parent as GuildTextChannel;
1064-
} else if (typeof destination.fetchWebhooks != "function") return;
1062+
else if (typeof destination.fetchWebhooks != "function") return;
10651063
if (
10661064
!destination
10671065
.permissionsFor(destination.guild.members.me)
@@ -1071,7 +1069,7 @@ export class Util extends ClientUtil {
10711069
const hooks = await destination.fetchWebhooks().catch(() => {});
10721070
let hook: Webhook;
10731071
if (hooks) hook = hooks.filter((hook) => !!hook.token).first();
1074-
if (!hook) {
1072+
if (!hook)
10751073
hook = await destination
10761074
.createWebhook(`Fire Quotes #${destination.name}`.slice(0, 80), {
10771075
avatar: this.client.user.displayAvatarURL({
@@ -1083,10 +1081,13 @@ export class Util extends ClientUtil {
10831081
) as string,
10841082
})
10851083
.catch(() => null);
1086-
}
1087-
return thread && hook?.url
1088-
? `${hook?.url}?thread_id=${thread.id}`
1089-
: hook?.url;
1084+
return (
1085+
hook && {
1086+
id: hook.id,
1087+
token: hook.token,
1088+
threadId: thread?.id,
1089+
}
1090+
);
10901091
}
10911092

10921093
makeImageUrl(root: string, { format = "webp", size = 512 } = {}) {

lib/util/command.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,10 @@ export class Command extends AkairoCommand {
257257
this.slashIds = {};
258258
}
259259

260+
get console() {
261+
return this.client.getLogger(`Command:${this.constructor.name}`);
262+
}
263+
260264
async init(): Promise<any> {}
261265

262266
async unload(): Promise<any> {}

src/commands/Moderation/lockdown.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export default class Lockdown extends Command {
149149
.catch(() => {});
150150
})
151151
.catch((e) => {
152-
this.client.console.debug(
152+
this.console.debug(
153153
`Lockdown permission overwrite failed for ${channel.name} (${channel.id})\n${e.stack}`
154154
);
155155
failed.push(channel.toString());

src/commands/Utilities/quote.ts

Lines changed: 129 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,27 @@
1-
import { ApplicationCommandMessage } from "@fire/lib/extensions/appcommandmessage";
21
import { ContextCommandMessage } from "@fire/lib/extensions/contextcommandmessage";
3-
import { FireMember } from "@fire/lib/extensions/guildmember";
2+
import { FireGuild } from "@fire/lib/extensions/guild";
43
import { FireMessage } from "@fire/lib/extensions/message";
54
import { MessageContextMenuInteraction } from "@fire/lib/extensions/messagecontextmenuinteraction";
6-
import { FireUser } from "@fire/lib/extensions/user";
7-
import { PartialQuoteDestination } from "@fire/lib/interfaces/messages";
5+
import {
6+
MessageLinkMatch,
7+
PartialQuoteDestination,
8+
} from "@fire/lib/interfaces/messages";
89
import { Command } from "@fire/lib/util/command";
910
import { GuildTextChannel, constants } from "@fire/lib/util/constants";
11+
import { messageConverter } from "@fire/lib/util/converters";
1012
import { Language } from "@fire/lib/util/language";
11-
import { ThreadhookClient } from "@fire/lib/util/threadhookclient";
13+
import { Message } from "@fire/lib/ws/Message";
14+
import { EventType } from "@fire/lib/ws/util/constants";
15+
import { MessageUtil } from "@fire/lib/ws/util/MessageUtil";
1216
import { Snowflake } from "discord-api-types/globals";
1317
import { PermissionFlagsBits } from "discord-api-types/v9";
1418
import {
1519
Collection,
1620
Constants,
1721
GuildChannelResolvable,
22+
GuildTextBasedChannel,
1823
MessageActionRow,
1924
MessageButton,
20-
MessageEmbed,
2125
} from "discord.js";
2226

2327
const { regexes } = constants;
@@ -47,6 +51,8 @@ export default class Quote extends Command {
4751
context: ["save to quote"],
4852
restrictTo: "all", // required for context cmd
4953
ephemeral: true, // context only
54+
slashOnly: true,
55+
hidden: true,
5056
});
5157

5258
this.savedQuotes = new Collection();
@@ -57,71 +63,128 @@ export default class Quote extends Command {
5763
}, 30000);
5864
}
5965

60-
async exec(
61-
message?: FireMessage,
62-
args?: {
63-
destination?: PartialQuoteDestination;
64-
quoter?: FireMember | FireUser;
65-
quote?: FireMessage;
66-
webhook?: string;
67-
debug?: boolean;
68-
}
66+
async handleLocalQuote(
67+
message: FireMessage,
68+
match: MessageLinkMatch,
69+
debug = false // TODO: reimplement debug messages
6970
) {
70-
if (
71-
message &&
72-
!message.guild &&
73-
!message.author.hasExperiment(3069732134, 1)
74-
)
75-
return await message.error("COMMAND_GUILD_ONLY", {
76-
invite: this.client.config.inviteLink,
77-
});
78-
if (!args?.quote) return;
79-
let debugMessages: string[];
80-
if (args.debug) debugMessages = [];
81-
let webhook: ThreadhookClient;
82-
if (args.webhook) {
83-
const match = regexes.discord.webhook.exec(args.webhook);
84-
regexes.discord.webhook.lastIndex = 0;
85-
if (!match?.groups.id || !match?.groups.token) return;
86-
webhook = new ThreadhookClient(
87-
{ id: match.groups.id as Snowflake, token: match.groups.token },
88-
{ threadId: match.groups.threadId as Snowflake }
71+
const convertedMessage = await messageConverter(
72+
message,
73+
null,
74+
debug,
75+
match
76+
).catch(() => {});
77+
if (!convertedMessage) return;
78+
else if (
79+
convertedMessage.reference?.type == Constants.MessageReferenceType.FORWARD
80+
) {
81+
const { reference } = convertedMessage;
82+
const shard = this.client.util.getShard(reference.guildId);
83+
if (!(this.client.options.shards as number[]).includes(shard))
84+
return this.forwardCrossClusterQuote(message, {
85+
guild_id: reference.guildId,
86+
channel_id: reference.channelId,
87+
message_id: reference.messageId,
88+
});
89+
const guild = this.client.guilds.cache.get(
90+
reference.guildId
91+
) as FireGuild;
92+
if (!guild) return;
93+
const channel = guild.channels.cache.get(
94+
reference.channelId
95+
) as GuildTextBasedChannel;
96+
if (!channel) return;
97+
const referencedMessage = (await channel.messages
98+
.fetch(reference.messageId)
99+
.catch(() => {})) as FireMessage;
100+
if (!referencedMessage) return;
101+
else
102+
return await referencedMessage.quote(
103+
channel,
104+
message.member ?? message.author
105+
);
106+
} else {
107+
await convertedMessage.quote(
108+
message.channel as GuildTextBasedChannel,
109+
message.member ?? message.author
89110
);
90-
return await args.quote
91-
.quote(args.destination, args.quoter, webhook, debugMessages)
92-
.catch(() => {});
111+
if (match.iteratedMessages?.length)
112+
for (const iterated of match.iteratedMessages)
113+
await iterated.quote(
114+
message.channel as GuildTextBasedChannel,
115+
message.member ?? message.author
116+
);
93117
}
94-
if (!message) return; // we shouldn't get here without one
95-
if (args.quote.content.length > 2000)
96-
return await message.error(QUOTE_PREMIUM_INCREASED_LENGTH);
97-
const quoted = await args.quote
98-
.quote(
99-
message instanceof ApplicationCommandMessage
100-
? (message.realChannel as GuildTextChannel)
101-
: (message.channel as GuildTextChannel),
102-
message.member ?? message.author,
103-
webhook,
104-
debugMessages
118+
}
119+
120+
async forwardCrossClusterQuote(
121+
message: FireMessage,
122+
quote: MessageLinkMatch
123+
) {
124+
const shard = this.client.util.getShard(quote.guild_id);
125+
const webhook = await this.client.util.getQuoteWebhookURL(
126+
message.channel as GuildTextChannel
127+
);
128+
if (!webhook?.token) return;
129+
this.console.log("Forwarding cross-cluster quote", {
130+
user: `${message.author} (${message.author.id})`,
131+
guild: `${message.guild} (${message.guild.id})`,
132+
source: `${quote.guild_id}/${quote.channel_id}/${quote.message_id}`,
133+
destination: `${message.guild.id}/${message.channelId}`,
134+
shard,
135+
});
136+
this.client.manager.ws.send(
137+
MessageUtil.encode(
138+
new Message(EventType.CROSS_CLUSTER_QUOTE, {
139+
shard,
140+
quoter: message.author.id,
141+
webhook,
142+
message: quote,
143+
destination: {
144+
nsfw: (message.channel as GuildTextChannel)?.nsfw || false,
145+
permissions: message.guild
146+
? message.member?.permissions.bitfield.toString() || "0"
147+
: "0",
148+
guild_id: message.guild?.id,
149+
id: message.channelId,
150+
} as PartialQuoteDestination,
151+
})
105152
)
106-
.catch((e: Error) =>
107-
(args.quoter ?? message.author).isSuperuser() ? e.stack : e.message
108-
);
109-
if (quoted == QUOTE_PREMIUM_INCREASED_LENGTH)
110-
return await message.error(quoted);
111-
else if (quoted == "nsfw") return await message.error("QUOTE_NSFW_TO_SFW");
112-
else if (quoted == "empty")
113-
return await message.error("QUOTE_MISSING_CONTENT");
114-
if (typeof quoted == "string" && debugMessages) debugMessages.push(quoted);
115-
if (args.debug) {
116-
if (!debugMessages.length) return;
117-
const content = debugMessages.join("\n");
118-
if (content.length > 2000 && content.length < 4096)
119-
return await message.channel.send({
120-
embeds: [new MessageEmbed().setDescription(content)],
121-
});
122-
else if (content.length <= 2000)
123-
return await message.channel.send(content);
153+
);
154+
}
155+
156+
returnCrossClusterQuote(
157+
destination: PartialQuoteDestination,
158+
quote: MessageLinkMatch,
159+
quoter: Snowflake,
160+
webhook: {
161+
id: Snowflake;
162+
token: string;
163+
threadId?: Snowflake;
124164
}
165+
) {
166+
if (!webhook?.token) return;
167+
168+
const shard = this.client.util.getShard(destination.guild_id);
169+
this.console.log(
170+
"Returning cross cluster quote due to forwarded message on another cluster",
171+
{
172+
quoter,
173+
quote,
174+
shard,
175+
}
176+
);
177+
this.client.manager.ws.send(
178+
MessageUtil.encode(
179+
new Message(EventType.CROSS_CLUSTER_QUOTE, {
180+
shard,
181+
quoter,
182+
webhook,
183+
message: quote,
184+
destination,
185+
})
186+
)
187+
);
125188
}
126189

127190
// Slash & Context commands will always try Command#run first

0 commit comments

Comments
 (0)