@@ -367,6 +367,18 @@ class Bot {
367
367
return null ;
368
368
}
369
369
370
+ // compare two strings case-insensitively
371
+ // for discord mention matching
372
+ static caseComp ( str1 , str2 ) {
373
+ return str1 . toUpperCase ( ) === str2 . toUpperCase ( ) ;
374
+ }
375
+
376
+ // check if the first string starts with the second case-insensitively
377
+ // for discord mention matching
378
+ static caseStartsWith ( str1 , str2 ) {
379
+ return str1 . toUpperCase ( ) . startsWith ( str2 . toUpperCase ( ) ) ;
380
+ }
381
+
370
382
sendToDiscord ( author , channel , text ) {
371
383
const discordChannel = this . findDiscordChannel ( channel ) ;
372
384
if ( ! discordChannel ) return ;
@@ -400,29 +412,73 @@ class Bot {
400
412
}
401
413
402
414
const { guild } = discordChannel ;
403
- const withMentions = withFormat . replace ( / @ [ ^ \s ] + \b / g, ( match ) => {
404
- const search = match . substring ( 1 ) ;
405
- const nickUser = guild . members . find ( 'nickname' , search ) ;
406
- if ( nickUser ) {
407
- return nickUser ;
408
- }
415
+ const withMentions = withFormat . replace ( / @ ( [ ^ \s # ] + ) # ( \d + ) / g, ( match , username , discriminator ) => {
416
+ // @username #1234 => mention
417
+ // skips usernames including spaces for ease (they cannot include hashes)
418
+ // checks case insensitively as Discord does
419
+ const user = guild . members . find ( x =>
420
+ Bot . caseComp ( x . user . username , username . toUpperCase ( ) )
421
+ && x . user . discriminator === discriminator ) ;
422
+ if ( user ) return user ;
409
423
410
- const user = this . discord . users . find ( 'username' , search ) ;
411
- if ( user ) {
412
- return user ;
413
- }
424
+ return match ;
425
+ } ) . replace ( / @ ( [ ^ \s ] + ) / g, ( match , reference ) => {
426
+ // this preliminary stuff is ultimately unnecessary
427
+ // but might save time over later more complicated calculations
428
+ // @nickname => mention, case insensitively
429
+ const nickUser = guild . members . find ( x =>
430
+ x . nickname !== null && Bot . caseComp ( x . nickname , reference ) ) ;
431
+ if ( nickUser ) return nickUser ;
432
+
433
+ // @username => mention, case insensitively
434
+ const user = guild . members . find ( x => Bot . caseComp ( x . user . username , reference ) ) ;
435
+ if ( user ) return user ;
436
+
437
+ // @role => mention, case insensitively
438
+ const role = guild . roles . find ( x => x . mentionable && Bot . caseComp ( x . name , reference ) ) ;
439
+ if ( role ) return role ;
440
+
441
+ // No match found checking the whole word. Check for partial matches now instead.
442
+ // @nameextra => [mention]extra, case insensitively, as Discord does
443
+ // uses the longest match, and if there are two, whichever is a match by case
444
+ let matchLength = 0 ;
445
+ let bestMatch = null ;
446
+ let caseMatched = false ;
447
+
448
+ // check if a partial match is found in reference and if so update the match values
449
+ const checkMatch = function ( matchString , matchValue ) {
450
+ // if the matchString is longer than the current best and is a match
451
+ // or if it's the same length but it matches by case unlike the current match
452
+ // set the best match to this matchString and matchValue
453
+ if ( ( matchString . length > matchLength && Bot . caseStartsWith ( reference , matchString ) )
454
+ || ( matchString . length === matchLength && ! caseMatched
455
+ && reference . startsWith ( matchString ) ) ) {
456
+ matchLength = matchString . length ;
457
+ bestMatch = matchValue ;
458
+ caseMatched = reference . startsWith ( matchString ) ;
459
+ }
460
+ } ;
414
461
415
- const role = guild . roles . find ( 'name' , search ) ;
416
- if ( role && role . mentionable ) {
417
- return role ;
418
- }
462
+ // check users by username and nickname
463
+ guild . members . forEach ( ( member ) => {
464
+ checkMatch ( member . user . username , member ) ;
465
+ if ( bestMatch === member || member . nickname === null ) return ;
466
+ checkMatch ( member . nickname , member ) ;
467
+ } ) ;
468
+ // check mentionable roles by visible name
469
+ guild . roles . forEach ( ( member ) => {
470
+ if ( ! member . mentionable ) return ;
471
+ checkMatch ( member . name , member ) ;
472
+ } ) ;
473
+
474
+ // if a partial match was found, return the match and the unmatched trailing characters
475
+ if ( bestMatch ) return bestMatch . toString ( ) + reference . substring ( matchLength ) ;
419
476
420
477
return match ;
421
478
} ) . replace ( / : ( \w + ) : / g, ( match , ident ) => {
479
+ // :emoji: => mention, case sensitively
422
480
const emoji = guild . emojis . find ( x => x . name === ident && x . requiresColons ) ;
423
- if ( emoji ) {
424
- return `<:${ emoji . identifier } >` ; // identifier = name + ":" + id
425
- }
481
+ if ( emoji ) return emoji ;
426
482
427
483
return match ;
428
484
} ) ;
0 commit comments