import { URL } from 'url' ; import * as moment from 'moment' ; import { Collections , Command , Interaction , Structures } from 'detritus-client' ; import { DiscordAbortCodes , InteractionCallbackTypes , MessageEmbedTypes , Permissions , StickerFormats } from 'detritus-client/lib/constants' ; import { Embed , Markup , PermissionTools , intToHex } from 'detritus-client/lib/utils' ; import { Response , replacePathParameters } from 'detritus-rest' ; import { Timers } from 'detritus-utils' ; import { Endpoints } from '../api' ; import { DateMomentLogFormat , EmbedColors , GoogleLocalesText , LanguageCodesText , Mimetypes , Timezones , MIMETYPES_SAFE_EMBED , TRUSTED_URLS , } from '../constants' ; import ChannelMembersStore , { ChannelMembersStored } from '../stores/channelmembers' ; import GuildMembersStore , { GuildMembersStored } from '../stores/guildmembers' ; import GuildSettingsStore from '../stores/guildsettings' ; export function createColorUrl ( color ) { return replacePathParameters ( Endpoints . Api . URL_PUBLIC + Endpoints . Api . PATH + Endpoints . Api . IMAGE_CREATE_COLOR_HEX , { format : 'png' , height : 2 , hex : intToHex ( color ) , width : 2 , } ) ; } export function createTimestampMoment ( timestamp , timezone = Timezones . EST ) { return moment ( timestamp ) . tz ( timezone ) ; } export function createTimestampMomentFromGuild ( timestamp , guildId ? ) { let timezone = Timezones . EST ; if ( guildId ) { const settings = GuildSettingsStore . get ( guildId ) ; if ( settings && settings . timezone ) { timezone = settings . timezone ; } } return createTimestampMoment ( timestamp , timezone ) ; } export function createTimestampStringFromGuild ( timestamp , guildId ? ) { return createTimestampMomentFromGuild ( timestamp , guildId ) . format ( DateMomentLogFormat ) ; } export function createUserEmbed ( user , embed = new Embed ( ) ) { embed . setAuthor ( ( user . bot ) ? `${ user } (BOT)` : user . toString ( ) , user . avatarUrlFormat ( null , { size : 1024 } ) , user . jumpLink ) ; return embed ; } export function createUserString ( userId = '1' , user ? , name = 'Unknown?' ) { return `<@!${ userId }> ${ Markup . spoiler ( `(${ Markup . escape . all ( String ( user || name ) ) })` ) }` ; } export function editOrReply ( context , options = { } ) { if ( typeof ( options ) === 'string' ) { options = { content : options } ; } if ( context instanceof Interaction . InteractionContext ) { return context . editOrRespond ( { ... options , allowedMentions : { parse : [ ] , ... options . allowedMentions } , } ) ; } return context . editOrReply ( { reference : true , ... options , allowedMentions : { parse : [ ] , repliedUser : false , ... options . allowedMentions } , } ) ; } export async function fetchMemberOrUserById ( context , userId , memberOnly = false ) { if ( context . user . id === userId ) { if ( memberOnly ) { if ( context . member ) { return context . member ; } } else { return context . user ; } } if ( context instanceof Command . Context ) { const mention = context . message . mentions . get ( userId ) ; if ( mention ) { if ( memberOnly ) { if ( mention instanceof Structures . Member ) { return mention ; } } else { return mention ; } } } try { const { guild } = context ; if ( guild ) { const member = guild . members . get ( userId ) ; if ( member ) { if ( member . isPartial ) { return await guild . fetchMember ( userId ) ; } return member ; } return await guild . fetchMember ( userId ) ; } if ( memberOnly ) { return null ; } if ( context . users . has ( userId ) ) { return context . users . get ( userId ) as Structures . User ; } return await context . rest . fetchUser ( userId ) ; } catch ( error ) { switch ( error . code ) { case DiscordAbortCodes . UNKNOWN_MEMBER : { if ( memberOnly ) { return null ; } return await context . rest . fetchUser ( userId ) ; } ; case DiscordAbortCodes . UNKNOWN_USER : { return null ; } ; default : { throw error ; } ; } } return null ; } export function findImageUrlInAttachment ( attachment ) { if ( attachment . proxyUrl && ( attachment . height || attachment . width ) ) { if ( attachment . isImage ) { if ( attachment . url ) { const url = new URL ( attachment . url ) ; if ( TRUSTED_URLS . includes ( url . host ) ) { return attachment . url ; } } return attachment . proxyUrl ; } else if ( attachment . isVideo ) { return attachment . proxyUrl + '?format=png' ; } } return null ; } export function findImageUrlInEmbed ( embed , ignoreGIFV = false ) { if ( ! ignoreGIFV && embed . type === MessageEmbedTypes . GIFV ) { const url = findImageUrlInEmbed ( embed , true ) ; if ( url && url . endsWith ( '.gif' ) ) { return url ; } if ( embed . url ) { return embed . url ; } return null ; } const { image } = embed ; if ( image && image . proxyUrl && ( image . height || image . width ) ) { if ( image . url ) { const url = new URL ( image . url ) ; if ( TRUSTED_URLS . includes ( url . host ) ) { return image . url ; } } return image . proxyUrl ; } const { thumbnail } = embed ; if ( thumbnail && thumbnail . proxyUrl && ( thumbnail . height || thumbnail . width ) ) { if ( thumbnail . url ) { const url = new URL ( thumbnail . url ) ; if ( TRUSTED_URLS . includes ( url . host ) ) { return thumbnail . url ; } } return thumbnail . proxyUrl ; } const { video } = embed ; if ( video && video . proxyUrl && ( video . height || video . width ) ) { return video . proxyUrl + '?format=png' ; } return null ; } export function findImageUrlInMessage ( message , url ? ) { if ( url ) { for ( let [ embedId , embed ] of message . embeds ) { if ( embed . url === url ) { return findImageUrlInEmbed ( embed ) ; } } } for ( let [ attachmentId , attachment ] of message . attachments ) { const url = findImageUrlInAttachment ( attachment ) ; if ( url ) { return url ; } } for ( let [ embedId , embed ] of message . embeds ) { const url = findImageUrlInEmbed ( embed ) ; if ( url ) { return url ; } } for ( let [ stickerId , sticker ] of message . stickerItems ) { return sticker . assetUrl ; } return null ; } export function findImageUrlInMessages ( messages ) { for ( const message of messages . values ( ) ) { const url = findImageUrlInMessage ( message ) ; if ( url ) { return url ; } } return null ; } export function findImageUrlsInMessage ( message ) { const urls = new Set < string > ( ) ; for ( let [ attachmentId , attachment ] of message . attachments ) { const url = findImageUrlInAttachment ( attachment ) ; if ( url ) { urls . add ( url ) ; } } for ( let [ embedId , embed ] of message . embeds ) { const url = findImageUrlInEmbed ( embed ) ; if ( url ) { urls . add ( url ) ; } } for ( let [ stickerId , sticker ] of message . stickerItems ) { urls . add ( sticker . assetUrl ) ; } return ( urls . size ) ? Array . from ( urls ) : [ ] ; } export function findImageUrlsInMessages ( messages ) { const urls = new Set < string > ( ) ; for ( const message of messages . values ( ) ) { const urlsFound = findImageUrlsInMessage ( message ) ; for ( let url of urlsFound ) { urls . add ( url ) ; } } return ( urls . size ) ? Array . from ( urls ) : [ ] ; } export function findMediaUrlInAttachment ( attachment ) { if ( attachment . proxyUrl && ( attachment . isAudio || ( ( attachment . isImage || attachment . isVideo ) && ( attachment . height || attachment . width ) ) ) ) { if ( attachment . url ) { const url = new URL ( attachment . url ) ; if ( TRUSTED_URLS . includes ( url . host ) ) { return attachment . url ; } } return attachment . proxyUrl ; } return null ; } export function findMediaUrlInEmbed ( embed , ignoreGIFV = false ) { if ( ! ignoreGIFV && embed . type === MessageEmbedTypes . GIFV ) { const url = findImageUrlInEmbed ( embed , true ) ; if ( url && url . endsWith ( '.gif' ) ) { return url ; } if ( embed . url ) { return embed . url ; } return null ; } const { image } = embed ; if ( image && image . proxyUrl && ( image . height || image . width ) ) { if ( image . url ) { const url = new URL ( image . url ) ; if ( TRUSTED_URLS . includes ( url . host ) ) { return image . url ; } } return image . proxyUrl ; } const { thumbnail } = embed ; if ( thumbnail && thumbnail . proxyUrl && ( thumbnail . height || thumbnail . width ) ) { if ( thumbnail . url ) { const url = new URL ( thumbnail . url ) ; if ( TRUSTED_URLS . includes ( url . host ) ) { return thumbnail . url ; } } return thumbnail . proxyUrl ; } const { video } = embed ; if ( video && video . proxyUrl && ( video . height || video . width ) ) { if ( video . url ) { const url = new URL ( video . url ) ; if ( TRUSTED_URLS . includes ( url . host ) ) { return video . url ; } } return video . proxyUrl ; } return null ; } export function findMediaUrlInMessage ( message , url ? ) { if ( url ) { for ( let [ embedId , embed ] of message . embeds ) { if ( embed . url === url ) { return findMediaUrlInEmbed ( embed ) ; } } } for ( let [ attachmentId , attachment ] of message . attachments ) { const url = findMediaUrlInAttachment ( attachment ) ; if ( url ) { return url ; } } for ( let [ embedId , embed ] of message . embeds ) { const url = findMediaUrlInEmbed ( embed ) ; if ( url ) { return url ; } } for ( let [ stickerId , sticker ] of message . stickerItems ) { return sticker . assetUrl ; } return null ; } export function findMediaUrlInMessages ( messages ) { for ( const message of messages . values ( ) ) { const url = findMediaUrlInMessage ( message ) ; if ( url ) { return url ; } } return null ; } export function findMediaUrlsInMessage ( message ) { const urls = new Set < string > ( ) ; for ( let [ attachmentId , attachment ] of message . attachments ) { const url = findMediaUrlInAttachment ( attachment ) ; if ( url ) { urls . add ( url ) ; } } for ( let [ embedId , embed ] of message . embeds ) { const url = findMediaUrlInEmbed ( embed ) ; if ( url ) { urls . add ( url ) ; } } for ( let [ stickerId , sticker ] of message . stickerItems ) { urls . add ( sticker . assetUrl ) ; } return ( urls . size ) ? Array . from ( urls ) : [ ] ; } export function findMediaUrlsInMessages ( messages ) { const urls = new Set < string > ( ) ; for ( const message of messages . values ( ) ) { const urlsFound = findMediaUrlsInMessage ( message ) ; for ( let url of urlsFound ) { urls . add ( url ) ; } } return ( urls . size ) ? Array . from ( urls ) : [ ] ; } /** Member Chunking */ export async function findMemberByChunk ( context , username , discriminator ? ) { const voiceChannel = context . voiceChannel ; if ( voiceChannel ) { const members = voiceChannel . members ; if ( members ) { const found = findMemberByUsername ( members , username , discriminator ) ; if ( found ) { return found ; } } } const guild = context . guild ; if ( guild && ! guild . isReady ) { await guild . requestMembers ( { limit : 0 , presences : true , query : '' , timeout : 10000 , } ) ; } const { channel } = context ; if ( channel ) { const { messages } = channel ; if ( messages ) { for ( let [ messageId , message ] of messages ) { { const members = [ message . member , message . author ] . filter ( ( v ) => v ) ; const found = findMemberByUsername ( members , username , discriminator ) ; if ( found ) { return found ; } } { const members = message . mentions ; const found = findMemberByUsername ( members , username , discriminator ) ; if ( found ) { return found ; } } } } { const found = findMemberByUsername ( channel . recipients , username , discriminator ) ; if ( found ) { return found ; } } { if ( guild && guild . memberCount < ChannelMembersStore . MAX_AMOUNT ) { let channelMembers ; if ( ChannelMembersStore . has ( channel . id ) ) { channelMembers = ChannelMembersStore . get ( channel . id ) as ChannelMembersStored ; } else { channelMembers = channel . members ; if ( ChannelMembersStore . MIN_AMOUNT <= channelMembers . length ) { ChannelMembersStore . set ( channel . id , channelMembers ) ; } } const members = findMembersByUsername ( channelMembers , username , discriminator ) as Array < Structures . Member > ; if ( members . length ) { const sorted = members . sort ( ( x , y ) => { if ( x . hoistedRole && y . hoistedRole ) { return y . hoistedRole . position - x . hoistedRole . position ; } else if ( x . hoistedRole ) { return - 1 ; } else if ( y . hoistedRole ) { return 1 ; } return 0 ; } ) ; return sorted [ 0 ] ; } } } } if ( guild ) { const members = findMembersByUsername ( guild . members , username , discriminator ) as Array < Structures . Member > ; if ( members . length ) { const sorted = members . sort ( ( x , y ) => { if ( x . hoistedRole && y . hoistedRole ) { return y . hoistedRole . position - x . hoistedRole . position ; } else if ( x . hoistedRole ) { return - 1 ; } else if ( y . hoistedRole ) { return 1 ; } return 0 ; } ) ; return sorted [ 0 ] ; } } else { { const found = findMemberByUsername ( [ context . user , context . client . user || undefined ] , username , discriminator ) ; if ( found ) { return found ; } } } return null ; } export async function findMemberByChunkText ( context , text ) { const [ username , discriminator ] = splitTextToDiscordHandle ( text ) ; return await findMemberByChunk ( context , username , discriminator ) ; } export async function findMembersByChunk ( context , username , discriminator ? ) { const guild = context . guild ; if ( guild ) { if ( ! guild . isReady ) { await guild . requestMembers ( { limit : 0 , presences : true , query : '' , timeout : 10000 , } ) ; } const found = findMembersByUsername ( guild . members , username , discriminator ) ; if ( found . length ) { return found ; } } else { const found = findMembersByUsername ( [ context . user , context . client . user || undefined ] , username , discriminator ) ; if ( found . length ) { return found ; } } return [ ] ; } export async function findMembersByChunkText ( context , text ) { const [ username , discriminator ] = splitTextToDiscordHandle ( text ) ; return await findMembersByChunk ( context , username , discriminator ) ; } /** Member Cache Filtering */ export interface FindMemberByUsernameCache { values ( ) : IterableIterator < Structures . Member | Structures . User | undefined > ; } export function findMemberByUsername ( members , username , discriminator ? ) { for ( const memberOrUser of members . values ( ) ) { if ( memberOrUser ) { if ( discriminator ) { if ( memberOrUser . username . toLowerCase ( ) . startsWith ( username ) && memberOrUser . discriminator === discriminator ) { return memberOrUser ; } } else { const nameMatches = memberOrUser . names . some ( ( n ) => n . toLowerCase ( ) . startsWith ( username ) ) ; if ( nameMatches ) { return memberOrUser ; } } } } } export function findMembersByUsername ( members , username , discriminator ? ) { const found = [ ] ; for ( const memberOrUser of members . values ( ) ) { if ( memberOrUser ) { if ( discriminator ) { if ( memberOrUser . username . toLowerCase ( ) . startsWith ( username ) && memberOrUser . discriminator === discriminator ) { found . push ( memberOrUser ) ; } } else { const nameMatches = memberOrUser . names . some ( ( n ) => n . toLowerCase ( ) . startsWith ( username ) ) ; if ( nameMatches ) { found . push ( memberOrUser ) ; } } } } return found ; } export function getMemberJoinPosition ( guild , userId ) { let members ; if ( GuildMembersStore . has ( guild . id ) ) { members = GuildMembersStore . get ( guild . id ) as GuildMembersStored ; } else { members = guild . members . sort ( ( x , y ) => x . joinedAtUnix - y . joinedAtUnix ) ; if ( GuildMembersStore . MIN_AMOUNT <= guild . members . length ) { GuildMembersStore . set ( guild . id , members ) ; } } const joinPosition = members . findIndex ( ( m ) => m . id === userId ) + 1 ; return [ joinPosition , guild . members . length ] ; } /** Formatting Text */ export function formatMemory ( bytes , decimals = 0 ) { const divideBy = 1024 ; const amount = Math . floor ( Math . log ( bytes ) / Math . log ( divideBy ) ) ; const type = ( [ 'B' , 'KB' , 'MB' , 'GB' , 'TB' ] ) [ amount ] ; return ( bytes / Math . pow ( divideBy , amount ) ) . toFixed ( decimals ) + ' ' + type ; } export interface FormatPercentageAsBarOptions { bars ? : number ; total ? : number ; } export function formatPercentageAsBar ( percentage , options = { } ) { const bars = options . bars || 30 ; const total = options . total || 100 ; const fraction = Math . round ( percentage ) / total ; const amount = Math . floor ( fraction * bars ) ; const bar = '#' . repeat ( amount ) ; return ( bar + ' ' . repeat ( Math . max ( bars - amount , 0 ) ) ) . slice ( 0 , bars ) ; } export interface FormatTimeOptions { day ? : boolean ; ms ? : boolean ; } export function formatTime ( ms , options = { } ) { const showDays = options . day || options . day === undefined ; const showMs = ! ! options . ms ; let seconds = Math . floor ( ms / 1000 ) ; let minutes = Math . floor ( seconds / 60 ) ; let hours = Math . floor ( minutes / 60 ) ; let days = Math . floor ( hours / 24 ) ; let milliseconds = ms % 1000 ; seconds %= 60 ; minutes %= 60 ; hours %= 24 ; const daysStr = ( days ) ? `${ days }d` : '' ; const hoursStr = ( `0${ hours }` ) . slice ( - 2 ) ; const minutesStr = ( `0${ minutes }` ) . slice ( - 2 ) ; const secondsStr = ( `0${ seconds }` ) . slice ( - 2 ) ; const millisecondsStr = ( `00${ milliseconds }` ) . slice ( - 3 ) ; let time = `${ minutesStr }:${ secondsStr }` ; if ( hours ) { time = `${ hoursStr }:${ time }` ; } if ( showMs ) { time = `${ time }.${ millisecondsStr }` ; } if ( showDays && days ) { time = `${ daysStr } ${ time }` ; } return time ; } export function htmlDecode ( value ) { return value . replace ( /&amp;/g , '&' ) . replace ( /&lt;/g , '<' ) . replace ( /&gt;/g , '>' ) . replace ( /&#39;/g , "'" ) . replace ( /&quot;/g , '"' ) ; } export async function imageReply ( context , response , options = { } ) { if ( typeof ( options ) === 'string' ) { options = { filename : options } ; } const embed = new Embed ( ) ; if ( response . headers . has ( 'x-args' ) ) { const args = JSON . parse ( Buffer . from ( response . headers . get ( 'x-args' ) || '' , 'base64' ) . toString ( ) ) ; const description = [ ] ; for ( let key in args ) { const title = toTitleCase ( key ) ; const value = args [ key ] ; let text ; if ( typeof ( value ) === 'boolean' ) { text = ( value ) ? 'Yes' : 'No' ; } else if ( typeof ( value ) === 'number' ) { text = value . toLocaleString ( ) ; } else { text = String ( value ) ; } description . push ( `${ title }: ${ text }` ) ; } embed . setDescription ( description . join ( ' | ' ) ) ; } const [ width , height ] = JSON . parse ( response . headers . get ( 'x-dimensions' ) || '[0, 0]' ) ; const [ framesOld , framesNew ] = JSON . parse ( response . headers . get ( 'x-frames' ) || '[0, 0]' ) ; return imageReplyFromOptions ( context , await response . buffer ( ) , { content : options . content , embed , extension : response . headers . get ( 'x-file-extension' ) || undefined , filename : options . filename , framesNew , framesOld , height , mimetype : response . headers . get ( 'content-type' ) || undefined , size : + ( response . headers . get ( 'x-file-size' ) || 0 ) , took : + ( response . headers . get ( 'x-took' ) || 0 ) , width , } ) ; } export async function imageReplyFromOptions ( context , value , options ) { let filename = '' ; if ( options . filename ) { filename = options . filename ; } else { if ( context . command ) { filename = context . command . name . replace ( ' ' , '-' ) ; } else { filename = 'edited-image' ; } } filename = `${ filename }.${ options . extension || 'png' }` ; let embed ; if ( options . embed ) { embed = options . embed ; } else { embed = new Embed ( ) ; } embed . setColor ( EmbedColors . DARK_MESSAGE_BACKGROUND ) ; if ( ! options . mimetype || MIMETYPES_SAFE_EMBED . includes ( options . mimetype as Mimetypes ) ) { embed . setImage ( `attachment://${ filename }` ) ; } let footer = `${ options . width }x${ options . height }` ; if ( options . mimetype === Mimetypes . IMAGE_GIF && options . framesNew ) { footer = `${ footer }, ${ options . framesNew . toLocaleString ( ) } frames` ; } footer = `${ footer }, ${ formatMemory ( options . size , 2 ) }` ; if ( options . took && 2000 <= options . took ) { const seconds = ( options . took / 1000 ) . toFixed ( 1 ) ; footer = `${ footer }, took ${ seconds } seconds` ; } embed . setFooter ( footer ) ; return editOrReply ( context , { content : options . content || '' , embed , file : { contentType : options . mimetype , filename , value } , } ) ; } export function isSnowflake ( value ) { if ( 16 <= value . length && value . length <= 21 ) { return ! ! parseInt ( value ) ; } return false ; } export function languageCodeToText ( code ) { if ( code in GoogleLocalesText ) { return ( GoogleLocalesText as any ) [ code ] ; } if ( code in LanguageCodesText ) { return ( LanguageCodesText as any ) [ code ] ; } return code ; } export function padCodeBlockFromColumns ( strings , options = { } ) { const padding = ( options . padding === undefined ) ? ' ' : options . padding ; const padFunc = ( options . padFunc === undefined ) ? String . prototype . padStart : options . padFunc ; const join = ( options . join === undefined ) ? ' ' : options . join ; const columns = [ ] ; const largestColumn = strings . reduce ( ( x , column ) => Math . max ( x , column . length ) , 0 ) ; for ( const column of strings ) { const formatted = [ ] ; let max = 0 ; for ( const row of column ) { max = Math . max ( max , row . length ) ; } for ( const row of column ) { formatted . push ( padFunc . call ( row , max , padding ) ) ; } columns . push ( formatted ) ; } const rows = [ ] ; for ( let i = 0 ; i < largestColumn ; i ++ ) { const row = [ ] ; for ( const column of columns ) { if ( i in column ) { row . push ( column [ i ] ) ; } } rows . push ( row . join ( join ) ) ; } return rows ; } export function padCodeBlockFromRows ( strings , options = { } ) { const padding = ( options . padding === undefined ) ? ' ' : options . padding ; const padFunc = ( options . padFunc === undefined ) ? String . prototype . padStart : options . padFunc ; const join = ( options . join === undefined ) ? ' ' : options . join ; const columns = [ ] ; const columnsAmount = strings . reduce ( ( x , row ) => Math . max ( x , row . length ) , 0 ) ; for ( let i = 0 ; i < columnsAmount ; i ++ ) { const column = [ ] ; let max = 0 ; for ( const row of strings ) { if ( i in row ) { max = Math . max ( max , row [ i ] . length ) ; } } for ( const row of strings ) { if ( i in row ) { column . push ( padFunc . call ( row [ i ] , max , padding ) ) ; } } columns . push ( column ) ; } const rows = [ ] ; for ( let i = 0 ; i < strings . length ; i ++ ) { const row = [ ] ; for ( const column of columns ) { if ( i in column ) { row . push ( column [ i ] ) ; } } rows . push ( row . join ( join ) ) ; } return rows ; } export function permissionsToObject ( permissions ) { const result = { } ; for ( let check of Object . values ( Permissions ) ) { if ( check === Permissions . NONE ) { continue ; } result [ String ( check ) ] = PermissionTools . checkPermissions ( permissions , check ) ; } return result ; } export function splitTextByAmount ( text , amount , character = '\n' ) { const parts = [ ] ; if ( character ) { const split = text . split ( character ) ; if ( split . length === 1 ) { return split ; } while ( split . length ) { let newText = '' ; while ( newText . length < amount && split . length ) { const part = split . shift ( ) ! ; if ( part ) { if ( amount < newText . length + part . length + 2 ) { split . unshift ( part ) ; break ; } newText += part + '\n' ; } } parts . push ( newText ) ; } } else { while ( text . length ) { parts . push ( text . slice ( 0 , amount ) ) ; text = text . slice ( amount ) ; } } return parts ; } export function splitTextToDiscordHandle ( text ) { const parts = text . split ( '#' ) ; const username = ( parts . shift ( ) as string ) . slice ( 0 , 32 ) . toLowerCase ( ) ; let discriminator = null ; if ( parts . length ) { discriminator = ( parts . shift ( ) as string ) . padStart ( 4 , '0' ) ; } return [ username , discriminator ] ; } export function toCodePoint ( unicodeSurrogates , separator = '-' ) { const r = [ ] ; let c = 0 ; let p = 0 ; let i = 0 ; while ( i < unicodeSurrogates . length ) { c = unicodeSurrogates . charCodeAt ( i ++ ) ; if ( p ) { r . push ( ( 0x10000 + ( ( p - 0xD800 ) << 10 ) + ( c - 0xDC00 ) ) . toString ( 16 ) ) ; p = 0 ; } else if ( 0xD800 <= c && c <= 0xDBFF ) { p = c ; } else { r . push ( c . toString ( 16 ) ) ; } } return r . join ( separator ) ; } export function toTitleCase ( value ) { return value . replace ( /_/g , ' ' ) . split ( ' ' ) . map ( ( word ) => { return word . charAt ( 0 ) . toUpperCase ( ) + word . substr ( 1 ) . toLowerCase ( ) ; } ) . join ( ' ' ) ; } export function bigIntGenerateBetween ( value1 , value2 ) { const lowBigInt = bigIntMin ( value1 , value2 ) ; const highBigInt = bigIntMax ( value1 , value2 ) ; const difference = highBigInt - lowBigInt + 1n ; const differenceLength = difference . toString ( ) . length ; let multiplier = '' ; while ( multiplier . length < differenceLength ) { multiplier += Math . random ( ) . toString ( ) . split ( '.' ) [ 1 ] ; } multiplier = multiplier . slice ( 0 , differenceLength ) ; const divisor = '1' + '0' . repeat ( differenceLength ) ; const randomDifference = ( difference * BigInt ( multiplier ) ) / BigInt ( divisor ) ; return lowBigInt + randomDifference ; } export function bigIntMax ( ... args ) { let value = args [ 0 ] ; for ( let x of args ) { if ( value < x ) { value = x ; } } return value ; } export function bigIntMin ( ... args ) { let value = args [ 0 ] ; for ( let x of args ) { if ( x < value ) { value = x ; } } return value ; } export function chunkArray < T > ( array , amount ) { const pages = [ ] ; for ( let i = 0 ; i < array . length ; i += amount ) { const page = [ ] ; for ( let disabled of array . slice ( i , i + amount ) ) { page . push ( disabled ) ; } if ( page . length ) { pages . push ( page ) ; } } return pages ; } export function randomFromArray < T > ( array ) { return array [ Math . floor ( Math . random ( ) * array . length ) ] ; } export function randomFromIterator < T > ( size , iterator ) { const choice = Math . floor ( Math . random ( ) * size ) ; let i = 0 ; for ( let value of iterator ) { if ( i ++ === choice ) { return value ; } if ( i === size ) { return value ; } } return iterator . next ( ) . value ; } export function shuffleArray < T > ( array ) { for ( let i = array . length - 1 ; 0 < i ; i -- ) { let x = Math . floor ( Math . random ( ) * ( i + 1 ) ) ; const current = array [ i ] ; array [ i ] = array [ x ] ; array [ x ] = current ; } return array ; } export function splitArray < T > ( array , amount ) { const pages = [ ] ; const chunkAmount = Math . max ( Math . round ( array . length / amount ) , 1 ) ; for ( let i = 0 ; i < amount ; i ++ ) { const position = i * chunkAmount ; pages . push ( array . slice ( position , position + chunkAmount ) ) ; } return pages ; } export function splitString ( value , splitter = '|' ) { const splits = [ ] ; if ( value . length ) { let position = 0 ; let argStart = position ; while ( position !== value . length ) { let nextSplitter = value . indexOf ( splitter , position ) ; if ( nextSplitter === - 1 ) { position = value . length ; splits . push ( value . slice ( argStart , position ) ) ; } else { position = nextSplitter + 1 ; if ( value [ nextSplitter - 1 ] !== '\\' ) { splits . push ( value . slice ( argStart , nextSplitter ) ) ; argStart = position ; } else if ( position === value . length ) { splits . push ( value . slice ( argStart , position ) ) ; } } } } else { splits . push ( value ) ; } return splits ; } export function stringOccurances ( search , value ) { let i = 0 ; let position = 0 ; while ( position !== - 1 ) { position = value . indexOf ( search , position ) ; if ( position !== - 1 ) { i ++ ; } } return i ; } export function validateUrl ( value ) { return /^(?:(?:(?:https?):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})))(?::\d{2,5})?(?:[/?#]\S*)?$/i . test ( value ) ; }