import Axios , { AxiosInstance , AxiosRequestConfig } from "axios" ; import EventEmitter from "eventemitter3" ; import defaultsDeep from "lodash.defaultsdeep" ; import { action , makeObservable , observable } from "mobx" ; import type { Session } from "revolt-api/types/Auth" ; import type { Attachment , AttachmentMetadata , SizeOptions } from "revolt-api/types/Autumn" ; import type { RevoltConfiguration } from "revolt-api/types/Core" ; import { MemberCompositeKey , Role } from "revolt-api/types/Servers" ; import { Route , RoutePath , RouteMethod } from "./api/routes" ; import Bots from "./maps/Bots" ; import Channels , { Channel } from "./maps/Channels" ; import Members , { Member } from "./maps/Members" ; import Messages , { Message } from "./maps/Messages" ; import Servers , { Server } from "./maps/Servers" ; import Users , { User } from "./maps/Users" ; import { WebSocketClient } from "./websocket/client" ; import { ClientboundNotification } from "./websocket/notifications" ; import { defaultConfig } from "./config" ; import { Nullable } from "./util/null" ; import Unreads from "./util/Unreads" ; /**
 * Client options object
 */ export interface ClientOptions { apiURL : string ; debug : boolean ; cache : boolean ; unreads : boolean ; heartbeat : number ; autoReconnect : boolean ; /**
     * Automatically reconnect the client if no
     * `pong` is received after X seconds of sending a `ping`.
     * This is a temporary fix for an issue where the client
     * would randomly stop receiving websocket messages.
     */ pongTimeout ? : number ; /**
     * If `pongTimeout` is set, this decides what to do when
     * the timeout is triggered. Default is `RECONNECT`.
     */ onPongTimeout ? : "EXIT" | "RECONNECT" ; ackRateLimiter : boolean ; } export declare interface Client { on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; on ( event , listener ) : this ; } /**
 * Regular expression for mentions.
 */ export const RE_MENTIONS = /<@([A-z0-9]{26})>/g ; /**
 * Regular expression for spoilers.
 */ export const RE_SPOILER = /!!.+!!/g ; export type FileArgs = [ options ? : SizeOptions , allowAnimation ? : boolean , fallback ? : string ] ; export class Client extends EventEmitter { heartbeat : number ; session ? : Session | string ; user : Nullable < User > = null ; options : ClientOptions ; websocket : WebSocketClient ; private Axios : AxiosInstance ; configuration ? : RevoltConfiguration ; users : Users ; channels : Channels ; servers : Servers ; members : Members ; messages : Messages ; bots : Bots ; unreads ? : Unreads ; constructor ( options = { } ) { super ( ) ; this . users = new Users ( this ) ; this . channels = new Channels ( this ) ; this . servers = new Servers ( this ) ; this . members = new Members ( this ) ; this . messages = new Messages ( this ) ; this . bots = new Bots ( this ) ; makeObservable ( this , { user : observable , users : observable , channels : observable , servers : observable , members : observable , messages : observable , reset : action , } , { proxy : false , } ) ; this . options = defaultsDeep ( options , defaultConfig ) ; if ( this . options . cache ) throw "Cache is not supported yet." ; if ( this . options . unreads ) { this . unreads = new Unreads ( this ) ; } this . Axios = Axios . create ( { baseURL : this . apiURL } ) ; this . websocket = new WebSocketClient ( this ) ; this . heartbeat = this . options . heartbeat ; if ( options . debug ) { this . Axios . interceptors . request . use ( ( request ) => { console . debug ( "[<]" , request . method ?. toUpperCase ( ) , request . url ) ; return request ; } ) ; this . Axios . interceptors . response . use ( ( response ) => { console . debug ( "[>] (" + response . status + ":" , response . statusText + ")" , JSON . stringify ( response . data ) ) ; return response ; } ) ; } } /**
     * ? Configuration.
     */ get apiURL ( ) { return this . options . apiURL ; } get debug ( ) { return this . options . debug ; } get autoReconnect ( ) { return this . options . autoReconnect ; } /**
     * ? Axios request wrapper.
     */ req < M extends RouteMethod , T extends RoutePath > ( method , url ) ; req < M extends RouteMethod , T extends RoutePath > ( method , url , data ) ; param method param url param data returns async req < M extends RouteMethod , T extends RoutePath > ( method , url , data ? ) { const res = await this . Axios . request ( { method , data , url , } ) ; return res . data ; } param method param url param data returns async request < M extends RouteMethod , T extends RoutePath > ( method , url , data ) { const res = await this . Axios . request ( { ... data , method , url , } ) ; return res . data ; } /**
     * ? Authentication and connection.
     */ remarks async connect ( ) { this . configuration = await this . req ( "GET" , "/" ) ; } /**
     * Fetches the configuration of the server if it has not been already fetched.
     */ async fetchConfiguration ( ) { if ( ! this . configuration ) await this . connect ( ) ; } private $generateHeaders ( session = this . session ) { if ( typeof session === "string" ) { return { "x-bot-token" : session , } ; } else { return { "x-session-token" : session ?. token , } ; } } private $updateHeaders ( ) { this . Axios . defaults . headers = this . $generateHeaders ( ) ; } param details returns async login ( details ) { await this . fetchConfiguration ( ) ; this . session = await this . req ( "POST" , "/auth/session/login" , details ) ; return await this . $connect ( ) ; } param session returns async useExistingSession ( session ) { await this . fetchConfiguration ( ) ; this . session = session ; return await this . $connect ( ) ; } param token async loginBot ( token ) { await this . fetchConfiguration ( ) ; this . session = token ; this . $updateHeaders ( ) ; return await this . websocket . connect ( ) ; } returns private async $connect ( ) { this . $updateHeaders ( ) ; const { onboarding } = await this . req ( "GET" , "/onboard/hello" ) ; if ( onboarding ) { return ( username , loginAfterSuccess ? ) => this . completeOnboarding ( { username } , loginAfterSuccess ) ; } await this . websocket . connect ( ) ; } param data param loginAfterSuccess async completeOnboarding ( data , loginAfterSuccess ? ) { await this . req ( "POST" , "/onboard/complete" , data ) ; if ( loginAfterSuccess ) { await this . websocket . connect ( ) ; } } /**
     * ? Miscellaneous API routes.
     */ param code returns async fetchInvite ( code ) { return await this . req ( "GET" , `/invites/${ code }` as "/invites/id" ) ; } param code returns async joinInvite ( code ) { return await this . req ( "POST" , `/invites/${ code }` as "/invites/id" ) ; } param code async deleteInvite ( code ) { await this . req ( "DELETE" , `/invites/${ code }` as "/invites/id" ) ; } param keys returns async syncFetchSettings ( keys ) { return await this . req ( "POST" , "/sync/settings/fetch" , { keys } ) ; } param data param timestamp async syncSetSettings ( data , timestamp ? ) { const requestData = { } ; for ( const key of Object . keys ( data ) ) { const value = data [ key ] ; requestData [ key ] = typeof value === "string" ? value : JSON . stringify ( value ) ; } const query = timestamp ? `?timestamp=${ timestamp }` : "" ; await this . req ( "POST" , `/sync/settings/set${ query }` as "/sync/settings/set" , requestData ) ; } returns async syncFetchUnreads ( ) { return await this . req ( "GET" , "/sync/unreads" ) ; } /**
     * ? Utility functions.
     */ /**
     * Log out of Revolt. Disconnect the WebSocket, request a session invalidation and reset the client.
     */ async logout ( avoidRequest ? ) { this . user = null ; this . emit ( "logout" ) ; ! avoidRequest && ( await this . req ( "POST" , "/auth/session/logout" ) ) ; this . reset ( ) ; } /**
     * Reset the client by setting properties to their original value or deleting them entirely.
     * Disconnects the current WebSocket.
     */ reset ( ) { this . user = null ; this . websocket . disconnect ( ) ; delete this . session ; this . users = new Users ( this ) ; this . channels = new Channels ( this ) ; this . servers = new Servers ( this ) ; this . members = new Members ( this ) ; this . messages = new Messages ( this ) ; } param data returns register ( data ) { return this . request ( "POST" , "/auth/account/create" , { data } ) ; } param source returns markdownToText ( source ) { return source . replace ( RE_MENTIONS , ( sub , id ) => { const user = this . users . get ( id as string ) ; if ( user ) { return `@${ user . username }` ; } return sub ; } ) . replace ( RE_SPOILER , "<spoiler>" ) ; } param url returns proxyFile ( url ) { if ( this . configuration ?. features . january . enabled ) { return `${ this . configuration . features . january . url }/proxy?url=${ encodeURIComponent ( url ) }` ; } } param attachment param options param allowAnimation param fallback returns generateFileURL ( attachment ? , ... args ) { const [ options , allowAnimation , fallback ] = args ; const autumn = this . configuration ?. features . autumn ; if ( ! autumn ?. enabled ) return fallback ; if ( ! attachment ) return fallback ; const { tag , _id , content_type , metadata } = attachment ; if ( metadata ?. type === 'Image' ) { if ( Math . min ( metadata . width , metadata . height ) <= 0 || ( content_type === "image/gif" && Math . max ( metadata . width , metadata . height ) >= 1024 ) ) return fallback ; } let query = "" ; if ( options ) { if ( ! allowAnimation || content_type !== "image/gif" ) { query = "?" + Object . keys ( options ) . map ( ( k ) => `${ k }=${ options [ k as keyof SizeOptions ] }` ) . join ( "&" ) ; } } return `${ autumn . url }/${ tag }/${ _id }${ query }` ; } }