@@ -3,23 +3,24 @@ import {
33 type Message ,
44 type MessageReaction ,
55 type User ,
6- type TextBasedChannel ,
7- type GuildEmoji ,
8- type ReactionEmoji ,
96 ChannelType ,
107 type Channel ,
118 type Snowflake ,
129 type GuildTextBasedChannel ,
13- type ApplicationEmoji ,
10+ hyperlink ,
1411} from "discord.js" ;
1512import { Temporal } from "@js-temporal/polyfill" ;
1613
1714import type { BotContext , QuoteConfig } from "#context.ts" ;
1815import type { ReactionHandler } from "../ReactionHandler.ts" ;
1916import log from "#log" ;
2017
18+ import * as quoteService from "#service/quote.ts" ;
19+
2120const quoteMessage = "Ihr quoted echt jeden Scheiß, oder?" ;
2221
22+ // TODO: Move some of these functions to the service
23+
2324const isChannelAnonymous = async ( context : BotContext , channel : Channel ) => {
2425 const anonChannels = context . commandConfig . quote . anonymousChannelIds ;
2526
@@ -37,13 +38,6 @@ const isChannelAnonymous = async (context: BotContext, channel: Channel) => {
3738 return false ;
3839} ;
3940
40- const isQuoteEmoji = (
41- quoteConfig : QuoteConfig ,
42- emoji : GuildEmoji | ReactionEmoji | ApplicationEmoji ,
43- ) => {
44- return emoji . name === quoteConfig . emojiName ;
45- } ;
46-
4741const getMessageQuoter = async (
4842 quoteConfig : QuoteConfig ,
4943 message : Message ,
@@ -53,8 +47,8 @@ const getMessageQuoter = async (
5347 throw new Error ( "Guild is null" ) ;
5448 }
5549 const fetchedMessage = await message . fetch ( true ) ;
56- const messageReaction = fetchedMessage . reactions . cache . find ( r =>
57- isQuoteEmoji ( quoteConfig , r . emoji ) ,
50+ const messageReaction = fetchedMessage . reactions . cache . find (
51+ r => r . emoji . name === quoteConfig . emojiName ,
5852 ) ;
5953
6054 if ( messageReaction === undefined ) {
@@ -67,13 +61,6 @@ const getMessageQuoter = async (
6761 . filter ( ( member ) : member is GuildMember => member !== null ) ;
6862} ;
6963
70- const isMessageAlreadyQuoted = (
71- messageQuoter : readonly GuildMember [ ] ,
72- context : BotContext ,
73- ) : boolean => {
74- return messageQuoter . some ( u => u . id === context . client . user . id ) ;
75- } ;
76-
7764const hasMessageEnoughQuotes = (
7865 context : BotContext ,
7966 messageQuoter : readonly GuildMember [ ] ,
@@ -192,78 +179,72 @@ export default {
192179 return ;
193180 }
194181
182+ if ( invoker . id === context . client . user . id ) {
183+ return ;
184+ }
185+
195186 const quoteConfig = context . commandConfig . quote ;
187+ if ( event . emoji . name !== quoteConfig . emojiName ) {
188+ return ;
189+ }
196190
197- if (
198- ! isQuoteEmoji ( quoteConfig , event . emoji ) ||
199- event . message . guildId === null ||
200- invoker . id === context . client . user . id
201- ) {
191+ const message = await event . message . fetch ( true ) ;
192+ if ( ! message . inGuild ( ) ) {
193+ return ;
194+ }
195+
196+ const quotedMember = message . member ;
197+ if ( ! quotedMember ) {
198+ log . error (
199+ "`quotedMember` is null or undefined. Should not happen. Good luck finding the issue." ,
200+ ) ;
201+ return ;
202+ }
203+
204+ const quoter = await context . guild . members . fetch ( invoker . id ) ;
205+ if ( ! quoter ) {
206+ log . error (
207+ "`quoter` is null or undefined. Should not happen. Good luck finding the issue." ,
208+ ) ;
202209 return ;
203210 }
204211
205- const quoter = context . guild . members . cache . get ( invoker . id ) ;
212+ // We could use a proper transaction for that
213+ // But nobody's got time for that
214+ const alreadyQuoted = await quoteService . isMessageAlreadyQuoted ( message ) ;
215+ if ( alreadyQuoted ) {
216+ return ;
217+ }
206218
207- const sourceChannel = event . message . channel as TextBasedChannel ;
208- const quotedMessage = await sourceChannel . messages . fetch ( event . message . id ) ;
209- const messageReference = quotedMessage . reference ;
210- const messageReferenceId = messageReference ?. messageId ;
219+ const messageReferenceId = message . reference ?. messageId ?? undefined ;
211220 const referencedMessage = messageReferenceId
212- ? await sourceChannel . messages . fetch ( messageReferenceId )
221+ ? await message . channel . messages . fetch ( messageReferenceId )
213222 : undefined ;
214223
215- const quotedUser = quotedMessage . member ;
216224 const referencedUser = referencedMessage ?. member ;
217- const quotingMembers = await getMessageQuoter ( quoteConfig , quotedMessage ) ;
225+ const quotingMembers = await getMessageQuoter ( quoteConfig , message ) ;
218226
219227 const quotingMembersAllowed = quotingMembers . filter ( context . roleGuard . isNerd ) ;
220228
221- if ( ! quotedUser || ! quoter ) {
222- log . error (
223- "Something bad happened, there is something missing that shouldn't be missing" ,
224- ) ;
225- return ;
226- }
227-
228229 log . debug (
229- `[Quote] User tried to ${ quoter . displayName } (${ quoter . id } ) quote user ${ quotedUser . displayName } (${ quotedUser . id } ) on message ${ quotedMessage . id } ` ,
230+ `[Quote] User tried to ${ quoter . displayName } (${ quoter . id } ) quote user ${ quotedMember . displayName } (${ quotedMember . id } ) on message ${ message . id } ` ,
230231 ) ;
231232
232233 if (
233234 ! context . roleGuard . isNerd ( quoter ) ||
234- quoteConfig . blacklistedChannelIds . has ( quotedMessage . channelId ) ||
235- isMessageAlreadyQuoted ( quotingMembers , context )
235+ quoteConfig . blacklistedChannelIds . has ( message . channelId )
236236 ) {
237237 await event . users . remove ( quoter ) ;
238238 return ;
239239 }
240240
241- if (
242- quotedMessage . author . id === context . client . user . id &&
243- isQuoterQuotingQuoteMessage ( quotedMessage )
244- ) {
241+ if ( message . author . id === context . client . user . id && isQuoterQuotingQuoteMessage ( message ) ) {
245242 await event . users . remove ( quoter ) ;
246243 return ;
247244 }
248245
249- if ( isQuoterQuotingHimself ( quoter , quotedUser ) ) {
250- await context . textChannels . hauptchat . send ( {
251- embeds : [
252- {
253- color : 0xe83e41 ,
254- author : {
255- name : quoter . displayName ,
256- icon_url : quoter . avatarURL ( { forceStatic : true } ) ?? undefined ,
257- } ,
258- title : `${ quoter . displayName } der Lellek hat gerade versucht sich, selbst zu quoten. Was für ein Opfer!` ,
259- description : `${ quotedMessage . cleanContent } \n\n([link](${ quotedMessage . url } ))` ,
260- } ,
261- ] ,
262- allowedMentions : {
263- users : [ quoter . id ] ,
264- } ,
265- } ) ;
266-
246+ if ( isQuoterQuotingHimself ( quoter , quotedMember ) ) {
247+ await context . textChannels . hauptchat . send ( createSelfQuoteReply ( quoter , message ) ) ;
267248 await event . users . remove ( quoter ) ;
268249 return ;
269250 }
@@ -274,14 +255,14 @@ export default {
274255
275256 const { quote, reference } = await createQuote (
276257 context ,
277- quotedUser ,
258+ quotedMember ,
278259 quotingMembersAllowed . map ( member => member . user ) ,
279260 referencedUser ,
280- quotedMessage ,
261+ message ,
281262 referencedMessage ,
282263 ) ;
283264 const { id : targetChannelId , channel : targetChannel } = getTargetChannel (
284- quotedMessage . channelId ,
265+ message . channelId ,
285266 context ,
286267 ) ;
287268
@@ -305,9 +286,9 @@ export default {
305286 // another quote event could sneak in and performing a quote itself.
306287 // Therefore we're checking again whether the message is already quoted BEFORE
307288 // sending the quote.
308- // This is a really dirty fix - not even a fix at all - but I'm to lazy to
309- // introduce some proper synchronization. Should work good enough for us.
310- if ( isMessageAlreadyQuoted ( quotingMembers , context ) ) {
289+ const wasAdded = await quoteService . addQuoteIfNotPresent ( message ) ;
290+ if ( ! wasAdded ) {
291+ // caught race condition
311292 return ;
312293 }
313294
@@ -318,12 +299,29 @@ export default {
318299 await targetChannel . send ( quote ) ;
319300 }
320301
321- await quotedMessage . react ( event . emoji ) ;
322- if (
323- quotedMessage . channel . isTextBased ( ) &&
324- quotedMessage . channel . type === ChannelType . GuildText
325- ) {
326- await quotedMessage . reply ( quoteMessage ) ;
302+ await message . react ( event . emoji ) ;
303+
304+ if ( message . channel . isTextBased ( ) && message . channel . type === ChannelType . GuildText ) {
305+ await message . reply ( quoteMessage ) ;
327306 }
328307 } ,
329308} satisfies ReactionHandler ;
309+
310+ function createSelfQuoteReply ( quoter : GuildMember , message : Message < true > ) {
311+ return {
312+ embeds : [
313+ {
314+ color : 0xe83e41 ,
315+ author : {
316+ name : quoter . displayName ,
317+ icon_url : quoter . avatarURL ( { forceStatic : true } ) ?? undefined ,
318+ } ,
319+ title : `${ quoter . displayName } der Lellek hat gerade versucht sich, selbst zu quoten. Was für ein Opfer!` ,
320+ description : `${ message . cleanContent } \n\n(${ hyperlink ( "link" , message . url ) } )` ,
321+ } ,
322+ ] ,
323+ allowedMentions : {
324+ users : [ quoter . id ] ,
325+ } ,
326+ } ;
327+ }
0 commit comments