UNPKG

@haelp/teto

Version:

A typescript-based controllable TETR.IO client.

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