@haelp/teto
Version:
A typescript-based controllable TETR.IO client.
1 lines • 18.4 kB
Source Map (JSON)
{"version":3,"sources":["../../../src/classes/client/index.ts"],"sourcesContent":["import type { Types } from \"../..\";\nimport type { Events, Game as GameTypes } from \"../../types\";\nimport { API, CONSTANTS, parseToken } from \"../../utils\";\nimport { Game } from \"../game\";\nimport { Ribbon } from \"../ribbon\";\nimport { Room } from \"../room\";\nimport { Social } from \"../social\";\nimport { ClientUtils } from \"../utils\";\nimport type { ClientOptions, ClientUser } from \"./types\";\n\nexport type * from \"./types\";\n\nexport class Client {\n /** User information */\n public user: ClientUser;\n /** Whether the client has been disconnected. If true, the client needs to be reconnected with `.reconnect()` or destroyed */\n public disconnected: boolean = false;\n /**\n * Utils for the client.\n * @deprecated - functionality has been moved to other sections. This may be removed in the future.\n */\n public utils: ClientUtils;\n /** The client's token */\n public token: string;\n /** @hidden */\n private _handling: GameTypes.Handling;\n\n /** Raw ribbon client, the backbone of TETR.IO multiplayer. You probably don't want to touch this unless you know what you are doing. */\n public ribbon: Ribbon;\n /** A helpful manager for all things social on TETR.IO (friends, dms etc.) */\n public social!: Social;\n /** The room the client is in (if it is in a room). You can make it non-nullable with `client.room!` */\n public room?: Room;\n /** The game the client is currently in if it is in a game. */\n public game?: Game;\n\n /** Useful for connecting to the main game API when none of the client helpers have the request you want to send. You can use `client.api.get` and `client.api.post` to easily send GET and POST requests. */\n public api: API;\n\n public rooms: {\n list(): ReturnType<API[\"rooms\"]>;\n join(id: string): Promise<Room>;\n create(type?: \"public\" | \"private\"): Promise<Room>;\n };\n\n /**\n * Raw ribbon handler.\n * @example\n * client.on('social.dm', () => console.log('DM received!'));\n */\n public on: typeof this.ribbon.emitter.on;\n /** Raw ribbon handler. */\n public off: typeof this.ribbon.emitter.off;\n /** Raw ribbon handler. You likely want to use `client.wait` instead. */\n public once: typeof this.ribbon.emitter.once;\n /** Raw ribbon handler for sending messages. */\n public emit: typeof this.ribbon.emit;\n\n /** @hideconstructor */\n private constructor(\n token: string,\n sessionID: string,\n ribbon: Ribbon,\n me: Awaited<ReturnType<API[\"users\"][\"me\"]>>,\n userAgent: string = CONSTANTS.userAgent,\n handling: GameTypes.Handling\n ) {\n this.token = token;\n this.ribbon = ribbon;\n this.on = ribbon.emitter.on.bind(ribbon.emitter);\n this.off = ribbon.emitter.off.bind(ribbon.emitter);\n this.once = ribbon.emitter.once.bind(ribbon.emitter);\n this.emit = ribbon.emit.bind(ribbon);\n\n this.user = {\n id: me._id,\n role: me.role,\n sessionID,\n username: me.username,\n userAgent\n };\n\n this._handling = handling;\n\n this.utils = new ClientUtils(this);\n\n this.api = new API({ token: this.token, userAgent });\n\n this.rooms = {\n list: () => this.api.rooms(),\n join: async (id: string) => {\n return await this.wrap(\n \"room.join\",\n id.toUpperCase(),\n \"client.room.join\"\n );\n },\n create: async (type = \"private\") => {\n return await this.wrap(\n \"room.create\",\n type === \"public\",\n \"client.room.join\"\n );\n }\n };\n\n this.init();\n }\n\n /**\n * Create a new client\n * @example\n * const client = await Client.connect({ token: 'your.jwt.token' });\n * @example\n * const client = await Client.connect({ username: 'halp', password: 'password' });\n * @example\n * // If playing games, pass in handling\n * const client = await Client.connect({\n * // ...login info\n * handling: {\n * arr: 0,\n * cancel: false,\n * das: 5,\n * dcd: 0,\n * safelock: false,\n * may20g: true,\n * sdf: 41\n * };\n * });\n * @example\n * // You can pass in a custom user agent\n * const client = await Client.connect({\n * // ...login info\n * userAgent: \"v8/001\"\n * });\n */\n static async connect(options: ClientOptions) {\n const api = new API();\n if (options.userAgent) {\n api.update({ userAgent: options.userAgent });\n }\n if (options.turnstile) {\n api.update({ turnstile: options.turnstile });\n }\n\n const sessionID = `SESS-${Math.floor(\n Math.random() * Number.MAX_SAFE_INTEGER\n )}`;\n let self: { id: string; token: string };\n if (\"token\" in options) {\n if (!options.token) throw new Error(\"No valid token was provided\");\n self = {\n token: options.token,\n id: parseToken(options.token)\n };\n } else if (\"username\" in options) {\n self = await api.users.authenticate(options.username, options.password);\n } else {\n throw new Error(\"Invalid client options\");\n }\n\n const token = self.token;\n\n api.update({ token });\n const me = api.users.me();\n\n const handling: Types.Game.Handling = options.handling || {\n arr: 0,\n cancel: false,\n das: 5,\n dcd: 0,\n safelock: false,\n may20g: true,\n sdf: 41,\n irs: \"tap\",\n ihs: \"tap\"\n };\n\n const ribbon = await Ribbon.create({\n token,\n handling,\n userAgent: options.userAgent || CONSTANTS.userAgent,\n ...(options.ribbon ?? {})\n });\n\n const data = await new Promise<Events.in.Client[\"client.ready\"]>(\n (resolve, reject) => {\n const t = setTimeout(() => {\n ribbon.destroy();\n reject(\"Failed to connect\");\n }, 5000);\n ribbon.emitter.once(\"client.ready\", (d) => {\n if (d) {\n clearTimeout(t);\n resolve(d);\n }\n });\n }\n );\n\n const client = new Client(\n token,\n sessionID,\n ribbon,\n await me,\n options.userAgent || CONSTANTS.userAgent,\n handling\n );\n\n client.social = await Social.create(client, data.social);\n\n return client;\n }\n\n /**\n * Wait for an event to occur. Wraps `client.once` into a typed Promise.\n * @returns the data from the event\n * @example\n * // wait for a notification (although you probably want to use `client.on` for this instead)\n * console.log(await client.wait('social.notification'));\n */\n wait<T extends keyof Events.in.all>(event: T) {\n return new Promise<Events.in.all[T]>((resolve) =>\n this.once(event, resolve)\n );\n }\n\n /**\n * Send a message and then wait for another message. Throws an error if a 'err' message is received before the response message\n * @param event - the `command` of the event to send\n * @param data - the data to send along with the command. For void (no) data, just pass in `undefined`\n * @param listen - the event to wait for before resolving.\n * @param error - a list of custom error events to listen for. Defaults to `[client.error]`.\n * @returns the data sent by the `listen` event\n * @throws an error if the error event provided (or `client.error`) is received from TETR.IO\n * @example\n * // This is just for example, use `client.room!.chat` instead\n * await client.wrap('room.chat', { content: 'Hello', pinned: false }, 'room.chat');\n */\n wrap<O extends keyof Events.out.all, I extends keyof Events.in.all>(\n event: O,\n data: Events.out.all[O],\n listen: I,\n error: (keyof Events.in.all)[] = [\"client.error\"]\n ) {\n return new Promise<Events.in.all[I]>((resolve, reject) => {\n const disband = () => {\n this.off(listen, rs);\n error.forEach((err) => this.off(err, rj));\n };\n const rs = (data: Events.in.all[I]) => {\n disband();\n resolve(data);\n };\n\n const rj = (error: string) => {\n disband();\n reject(error);\n };\n\n this.on(listen, rs);\n error.forEach((err) => this.on(err, rj));\n\n // @ts-expect-error\n this.emit(event, data);\n });\n }\n\n /** @hidden */\n private init() {\n this.on(\"room.join\", async () => {\n const data = await this.wait(\"room.update\");\n this.room = new Room(this, data);\n this.emit(\"client.room.join\", this.room!);\n });\n\n this.on(\"notify\", (notif) => {\n if (typeof notif === \"string\") {\n return this.emit(\"client.notify\", { msg: notif });\n } else if (\"type\" in notif) {\n switch (notif.type) {\n case \"deny\":\n this.emit(\"client.notify\", {\n msg: notif.msg,\n color: \"#FF2200\",\n icon: \"denied\",\n timeout: notif.timeout\n });\n break;\n case \"warn\":\n this.emit(\"client.notify\", {\n msg: notif.msg,\n color: \"#FFF43C\",\n icon: \"warning\"\n });\n break;\n case \"err\":\n this.emit(\"client.error\", notif.msg);\n this.emit(\"client.notify\", {\n msg: notif.msg,\n color: \"#FF4200\",\n icon: \"error\"\n });\n break;\n case \"announce\":\n this.emit(\"client.notify\", {\n msg: notif.msg,\n color: \"#FFCC00\",\n icon: \"announcement\",\n timeout: 1e4\n });\n break;\n case \"ok\":\n this.emit(\"client.notify\", {\n msg: notif.msg,\n color: \"#6AFF3C\",\n icon: \"ok\"\n });\n break;\n }\n } else {\n this.emit(\"client.notify\", notif);\n }\n });\n\n this.on(\"client.dead\", async () => {\n this.disconnected = true;\n });\n }\n\n /**\n * Reconnect the client to TETR.IO.\n * @throws {Error} if the client is already connected\n */\n async reconnect() {\n if (!this.disconnected) {\n throw new Error(\"Client is not disconnected.\");\n }\n\n const newRibbon = await this.ribbon.clone();\n this.ribbon.destroy();\n this.ribbon = newRibbon;\n\n const data = await new Promise<Events.in.Client[\"client.ready\"]>(\n (resolve, reject) => {\n const t = setTimeout(() => {\n newRibbon.destroy();\n reject(\"Failed to connect\");\n }, 5000);\n this.ribbon.emitter.once(\"client.ready\", (d) => {\n if (d) {\n clearTimeout(t);\n resolve(d);\n }\n });\n }\n );\n\n this.social = await Social.create(this, data.social);\n }\n\n /** The client's current handling. */\n get handling() {\n return this._handling;\n }\n\n /** Change the client's current handling (do not use while in a room) */\n set handling(handling: GameTypes.Handling) {\n if (this.room)\n throw new Error(\n \"Do not set the handling in a room (you will be banned)!\"\n );\n this._handling = handling;\n this.emit(\"config.handling\", handling);\n }\n\n /** Clean up the client. Leaves any rooms first. */\n async destroy() {\n if (this.room) {\n try {\n await this.room.leave();\n } catch (e) {}\n }\n this.ribbon.destroy();\n if (this.room) delete this.room;\n if (this.game) delete this.game;\n }\n}\n"],"names":["API","CONSTANTS","parseToken","Ribbon","Room","Social","ClientUtils","Client","user","disconnected","utils","token","_handling","ribbon","social","room","game","api","rooms","on","off","once","emit","sessionID","me","userAgent","handling","emitter","bind","id","_id","role","username","list","join","wrap","toUpperCase","create","type","init","connect","options","update","turnstile","Math","floor","random","Number","MAX_SAFE_INTEGER","self","Error","users","authenticate","password","arr","cancel","das","dcd","safelock","may20g","sdf","irs","ihs","data","Promise","resolve","reject","t","setTimeout","destroy","d","clearTimeout","client","wait","event","listen","error","disband","rs","forEach","err","rj","notif","msg","color","icon","timeout","reconnect","newRibbon","clone","leave","e"],"mappings":"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"}