import { Response } from 'centra' ; import { get , post , UserLogger } from './requests' ; import fallbackData from './fallbackListData' ; import legacyIdsFallbackData from './legacyIdsFallbackData' ; type listDataType = { [ listname ] : { api_docs : string | null ; api_post : string | null ; api_field : string | null ; api_shard_id : string | null ; api_shard_count : string | null ; api_shards : string | null ; api_get : string | null ; } ; } ; type legacyIdDataType = { [ listname ] : string ; } ; type apiKeysObject = { [ listname ] : string ; } ; /**
 * This is a fallback type
 * to make BLAPI compatible with typescript code that does not use discord.js
 */ class Collection < K , T > extends Map < K , T > { [ key ] : any ; } /**
 * This is a fallback type
 * to make BLAPI compatible with typescript code that does not use discord.js
 */ type DiscordJSClientFallback = { user : { id : string ; [ k ] : any ; } | null ; shard : ( { count : number ; [ k ] : any ; } & ( { ids : number [ ] ; } | { id : number ; } ) ) | null ; guilds : { cache : Collection < string , ( { shardID : number ; } | { shardId : number ; } ) & { [ k ] : any ; } > ; } ; ws : { shards : Collection < number , { id : number ; [ k ] : any ; } > ; [ k ] : any ; } ; [ k ] : any ; } ; type LogOptions = boolean | { extended ? : boolean ; logger ? : UserLogger ; } ; let listData = fallbackData as listDataType ; let legacyIds = legacyIdsFallbackData as legacyIdDataType ; const listAge = new Date ( ) ; let extendedLogging = false ; let useBotblockAPI = true ; /**
 * the userLogger variable will later be defined with the
 *  logger supplied by the user if they supplied any
 */ let userLogger ; const log = { info : ( msg ) => ( userLogger ? userLogger . info ( `BLAPI: ${ msg }` ) : console . info ( `[INFO] BLAPI: ${ msg }` ) ) , warn : ( msg ) => ( userLogger ? userLogger . warn ( `BLAPI: ${ msg }` ) : console . warn ( `[WARN] BLAPI: ${ msg }` ) ) , error : ( err ) => ( userLogger ? userLogger . error ( `BLAPI: ${ err }` ) : console . error ( `[ERROR] BLAPI ${ err }` ) ) , } ; function convertLegacyIds ( apiKeys ) { const newApiKeys = { ... apiKeys } ; Object . entries ( legacyIds ) . forEach ( ( [ list , newlist ] ) => { if ( newApiKeys [ list ] ) { newApiKeys [ newlist ] = newApiKeys [ list ] ; delete newApiKeys [ list ] ; } } ) ; return newApiKeys ; } function buildBotblockData ( apiKeys , bot_id , server_count , shard_id ? , shard_count ? , shards ? ) { return { ... apiKeys , bot_id , server_count , shard_id , shard_count , shards , } ; } param apiKeys async function postToAllLists ( apiKeys , client_id , server_count , shard_id ? , shard_count ? , shards ? ) { const currentDate = new Date ( ) ; if ( ! listData || listAge < currentDate ) { listAge . setDate ( currentDate . getDate ( ) + 1 ) ; try { const tmpListData = await get < listDataType > ( 'https://botblock.org/api/lists?filter=true' , log ) ; if ( tmpListData ) { listData = tmpListData ; log . info ( 'Updated list endpoints.' ) ; } else { log . error ( 'Got empty list of endpoints from botblock.' ) ; } } catch ( e ) { log . error ( e ) ; log . error ( "Something went wrong when contacting BotBlock for the API of the lists, so we're using an older preset. Some lists might not be available because of this." ) ; } try { const tmpLegacyIdsData = await get < legacyIdDataType > ( 'https://botblock.org/api/legacy-ids' , log ) ; if ( tmpLegacyIdsData ) { legacyIds = tmpLegacyIdsData ; log . info ( 'Updated legacy Ids.' ) ; } else { log . error ( 'Got empty list of legacy Ids from botblock.' ) ; } } catch ( e ) { log . error ( e ) ; log . error ( "Something went wrong when contacting BotBlock for legacy Ids, so we're using an older preset. Some lists might not be available because of this." ) ; } } const posts = [ ] ; Object . entries ( listData ) . forEach ( ( [ listname ] ) => { if ( apiKeys [ listname ] && listData [ listname ] . api_post ) { const list = listData [ listname ] ; if ( ! list . api_post || ! list . api_field ) { return ; } const apiPath = list . api_post . replace ( ':id' , client_id ) ; const sendObj = { } ; sendObj [ list . api_field ] = server_count ; if ( shard_id && list . api_shard_id ) { sendObj [ list . api_shard_id ] = shard_id ; } if ( shard_count && list . api_shard_count ) { sendObj [ list . api_shard_count ] = shard_count ; } if ( shards && list . api_shards ) { sendObj [ list . api_shards ] = shards ; } posts . push ( post ( apiPath , apiKeys [ listname ] , sendObj , extendedLogging , log ) ) ; } } ) ; return Promise . all ( posts ) ; } param client param apiKeys param repeatInterval async function handleInternal ( client , apiKeys , repeatInterval ) { setTimeout ( handleInternal . bind ( null , client , apiKeys , repeatInterval ) , 60000 * repeatInterval ) ; if ( client . user ) { const client_id = client . user . id ; let unchanged ; let shard_count ; let shards ; let server_count = 0 ; let shard_id ; if ( client . shard && client . shard . id === 0 ) { shard_count = client . shard . count ; shard_id = client . shard . id ; try { const guildSizes = await client . shard . broadcastEval ( 'this.guilds.cache.size' ) ; const shardCounts = guildSizes . filter ( ( count ) => count !== 0 ) ; if ( shardCounts . length !== client . shard . count ) { return ; } server_count = shardCounts . reduce ( ( prev , val ) => prev + val , 0 ) ; } catch ( e ) { log . error ( e ) ; log . error ( 'Error while fetching shard server counts:' ) ; } } else if ( client . ws . shards . size > 1 ) { shard_count = client . ws . shards . size ; shards = client . ws . shards . map ( ( s ) => client . guilds . cache . filter ( ( g ) => g . shardID === s . id || g . shardId === s . id ) . size ) as Array < number > ; if ( shards . length !== client . ws . shards . size ) { log . info ( "Not all shards are up yet, so we're skipping this run." ) ; return ; } server_count = shards . reduce ( ( prev , val ) => prev + val , 0 ) ; } else if ( ! client . shard ) { server_count = client . guilds . cache . size ; } else { unchanged = true ; } if ( ! unchanged ) { if ( repeatInterval > 2 && useBotblockAPI ) { await post ( 'https://botblock.org/api/count' , 'no key needed for this' , buildBotblockData ( apiKeys , client_id , server_count , shard_id , shard_count , shards ) , extendedLogging , log ) ; if ( apiKeys [ 'top.gg' ] ) { await postToAllLists ( { 'top.gg' : apiKeys [ 'top.gg' ] } , client_id , server_count , shard_id , shard_count , shards ) ; } } else { await postToAllLists ( apiKeys , client_id , server_count , shard_id , shard_count , shards ) ; } } } else { log . error ( `Discord client seems to not be connected yet, so we're skipping this run of the post. We will try again in ${ repeatInterval } minutes.` ) ; } } param discordClient param apiKeys param repeatInterval export function handle ( discordClient , apiKeys , repeatInterval ? ) { return handleInternal ( discordClient , convertLegacyIds ( apiKeys ) , ! repeatInterval || repeatInterval < 1 ? 30 : repeatInterval ) ; } param guildCount param botId param apiKeys param shardId param shardCount param shards export async function manualPost ( guildCount , botID , apiKeys , shard_id ? , shard_count ? , shards ? ) { const updatedApiKeys = convertLegacyIds ( apiKeys ) ; const client_id = botID ; let server_count = guildCount ; if ( shard_id === 0 || ( shard_id && ! shards ) ) { if ( shards ) { if ( shards . length !== shard_count ) { throw new Error ( `BLAPI: Shardcount (${ shard_count }) does not equal the length of the shards array (${ shards . length }).` ) ; } server_count = shards . reduce ( ( prev , val ) => prev + val , 0 ) ; } } const responses = [ ] ; if ( useBotblockAPI ) { responses . push ( await post ( 'https://botblock.org/api/count' , 'no key needed for this' , buildBotblockData ( updatedApiKeys , client_id , server_count , shard_id , shard_count , shards ) , extendedLogging , log ) ) ; if ( updatedApiKeys [ 'top.gg' ] ) { responses . concat ( await postToAllLists ( { 'top.gg' : updatedApiKeys [ 'top.gg' ] } , client_id , server_count , shard_id , shard_count , shards ) ) ; } } else { responses . concat ( await postToAllLists ( updatedApiKeys , client_id , server_count , shard_id , shard_count , shards ) ) ; } return responses ; } export function setLogging ( logOptions ) { if ( typeof logOptions === 'boolean' ) { extendedLogging = logOptions ; } if ( typeof logOptions === 'object' && Object . prototype . hasOwnProperty . call ( logOptions , 'extended' ) && typeof logOptions . extended === 'boolean' ) { extendedLogging = logOptions . extended ; } if ( ! Object . prototype . hasOwnProperty . call ( logOptions , 'logger' ) ) { return ; } const { logger } = logOptions as any ; if ( typeof logger . info !== 'function' || typeof logger . warn !== 'function' || typeof logger . error !== 'function' ) { throw new Error ( 'Your supplied logger does not seem to expose the log levels BLAPI needs to work. Make sure your logger offers the following methods: info() warn() error()' ) ; } userLogger = logOptions . logger ; } export function setBotblock ( useBotblock ) { useBotblockAPI = useBotblock ; }