UNPKG

@haelp/teto

Version:

A typescript-based controllable TETR.IO client.

374 lines (339 loc) 10.2 kB
import { Events, type Social as SocialTypes } from "../../types"; import { APITypes } from "../../utils"; import type { Client } from "../client"; import { Relationship } from "./relationship"; interface SocialInitData { online: number; notifications: SocialTypes.Notification[]; friends: ReturnType<typeof processRelationship>[]; other: ReturnType<typeof processRelationship>[]; blocked: SocialTypes.Blocked[]; } const processRelationship = (r: SocialTypes.Relationship, selfID: string) => ({ ...r, user: { id: r.from._id === selfID ? r.to._id : r.from._id, username: r.from._id === selfID ? r.to.username : r.from.username, avatar: r.from._id === selfID ? r.to.avatar_revision : r.from.avatar_revision } }); export class Social { private client: Client; /** The current number of people online */ public online: number; /** List of the client's friends */ public friends: Relationship[]; /** List of "pending" relationships (shows in `other` tab on TETR.IO) */ public other: Relationship[]; /** people you block */ public blocked: SocialTypes.Blocked[]; /** Notifications */ public notifications: SocialTypes.Notification[]; /** @hideconstructor */ private constructor(client: Client, init: SocialInitData) { this.client = client; this.init(); this.online = init.online; this.friends = init.friends.map( (r) => new Relationship( { id: r.user.id, relationshipID: r._id, username: r.user.username, avatar: r.user.avatar }, this, this.client ) ); this.other = init.other.map( (r) => new Relationship( { id: r.user.id, relationshipID: r._id, username: r.user.username, avatar: r.user.avatar }, this, this.client ) ); this.blocked = init.blocked; this.notifications = init.notifications; } static async create( client: Client, initData: Events.in.Client["client.ready"]["social"] ) { const rel = initData.relationships.map((r) => processRelationship(r, client.user.id) ); const data: SocialInitData = { online: initData.total_online, notifications: initData.notifications, friends: rel.filter((r) => r.type === "friend"), other: rel.filter((r) => r.type === "pending"), blocked: rel .filter((r) => r.type === "block") .map((r) => ({ id: r.user.id, username: r.user.username, avatar: r.user.avatar })) }; return new Social(client, data); } private get api() { return this.client.api; } private init() { setTimeout( () => this.notifications.forEach((n) => { if (n.type === "friend") { if (!n.seen) { const rel = processRelationship( n.data.relationship, this.client.user.id ); this.client.emit("client.friended", { id: rel.user.id, name: rel.user.username, avatar: rel.user.avatar }); } } }), 0 ); this.client.on("social.online", (count) => { this.online = count; }); this.client.on("social.notification", async (n) => { this.notifications.splice(0, 0, n); if (n.type === "friend") { const rel = processRelationship( n.data.relationship, this.client.user.id ); this.client.emit("client.friended", { id: rel.user.id, name: rel.user.username, avatar: rel.user.avatar }); const user = this.get({ id: rel.user.id }); if (!user) { this.other.push( new Relationship( { id: rel.user.id, username: rel.user.username, avatar: rel.user.avatar, relationshipID: "" }, this, this.client ) ); } } }); this.client.on("social.dm", async (dm) => { let user = this.get({ id: dm.data.user }); if (user) { if (!user.dmsLoaded) await user.loadDms(); else user.dms.push(dm); } else { const u = await this.who(dm.data.user); this.other.push( new Relationship( { id: u._id, username: u.username, avatar: u.avatar_revision, relationshipID: "" }, this, this.client ) ); user = this.get({ id: dm.data.user })!; await user.loadDms(); } this.client.emit("client.dm", { user, content: dm.data.content, reply: async (content: string) => { await this.dm(dm.data.user, content); } }); }); } /** * Marks all notifications as read * @example * client.social.markNotificationsAsRead(); */ markNotificationsAsRead() { this.client.emit("social.notification.ack"); } /** * Get a user + social data from the list of users and pending friends (OTHER tab in TETR.IO) * @example * const user = await client.social.get('halp') * @example * const user = await client.social.get('646f633d276f42a80ba44304'); * @example * const user = await client.social.get({ username: 'halp' }); * @example * const user = await client.social.get({ id: '646f633d276f42a80ba44304 }); * @example * // You can then use the object to read user data and interact with the user * await user.dm('wanna play?'); * await user.invite(); */ get(target: string): Relationship | null; get(target: { id: string }): Relationship | null; get(target: { username: string }): Relationship | null; get( target: string | { id: string } | { username: string } ): Relationship | null { if (typeof target === "string") return ( this.friends.find((r) => r.id === target || r.username === target) || this.other.find((r) => r.id === target || r.username === target) || null ); else if ("id" in target) return ( this.friends.find((r) => r.id === target.id) || this.other.find((r) => r.id === target.id) || null ); else return ( this.friends.find((r) => r.username === target.username) || this.other.find((r) => r.username === target.username) || null ); } /** * Get the user id given a username */ async resolve(username: string) { return await this.api.users.resolve(username); } /** Get a users' information based on their userid or username * @example * const user = await client.social.who(await client.social.resolve('halp')); */ who(id: string): Promise<APITypes.Users.User>; async who(id: string): Promise<APITypes.Users.User> { return this.api.users.get({ id }); } /** * Send a message to a specified user (based on id) * @example * await client.social.dm(await client.social.resolve('halp'), 'what\'s up?'); */ async dm(userID: string, message: string) { return await this.client.wrap( "social.dm", { recipient: userID, msg: message }, "social.dm", ["social.dm.fail", "client.error"] ); } /** * Send a user a friend request * @example * await client.social.friend(await client.social.resolve('halp')); * @returns false if the user is already friended, true otherwise * @throws {Error} If an error occurs (such as the user has blocked the client, etc) */ async friend(userID: string) { if (this.friends.find((r) => r.id === userID)) return false; await this.api.social.friend(userID); const userData = await this.who(userID); this.friends.push( new Relationship( { id: userData._id, relationshipID: "", username: userData.username, avatar: userData.avatar_revision }, this, this.client ) ); return true; } /** * Unfriend a user. Note: unfriending a user will unblock them if they are blocked. * @example * await client.social.unfriend(await client.social.resolve('halp')); * @returns false if the user is not unfriended, true otherwise */ async unfriend(userID: string) { if (!this.friends.find((r) => r.id === userID)) return false; await this.api.social.unfriend(userID); this.friends = this.friends.filter((r) => r.id !== userID); return true; } /** * Block a user * @example * await client.social.block(await client.social.resolve('halp')); * @returns false if the user is already blocked, true otherwise */ async block(userID: string) { if (this.blocked.find((r) => r.id === userID)) return false; await this.api.social.block(userID); } /** * Unblock a user. Note: unblocking a user will unfriend them if they are friended. * @example * await client.social.unblock(await client.social.resolve('halp')); * @returns false if the user is not unblocked, true otherwise */ async unblock(userID: string) { await this.api.social.unblock(userID); } /** * Invite a user to your room * @example * await client.social.invite(await client.social.resolve('halp')); */ invite(userID: string) { return new Promise<void>(async (resolve, reject) => { let r = false; this.client.emit("social.invite", userID); const l = (e: string) => { if (r) return; r = true; reject(e); }; this.client.once("client.error", l); await new Promise((r) => setTimeout(r, 100)); this.client.off("client.error", l); r = true; resolve(); }); } /** * Set the client's status * @example * client.social.status('online', 'lobby:X-QP'); */ status(status: SocialTypes.Status, detail: SocialTypes.Detail | String = "") { this.client.emit("social.presence", { status, detail }); } } export * from "./relationship";