UNPKG

@amelix/phoenix.js

Version:

A feature-rich API wrapper for the critically acclaimed chatting app Phoenix.. or something.

369 lines (368 loc) 16.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Client = void 0; const https = require("https"); const socket_io_client_1 = require("socket.io-client"); const config_1 = require("../config"); const ClientUser_1 = require("./ClientUser"); const DeletedUser_1 = require("./DeletedUser"); const EventEmitter_1 = require("./EventEmitter"); const Invite_1 = require("./Invite"); const Member_1 = require("./Member"); const Message_1 = require("./Message"); const PhoenixResponse_1 = require("./PhoenixResponse"); const Server_1 = require("./Server"); const ServerChannel_1 = require("./ServerChannel"); const TextChannel_1 = require("./TextChannel"); const User_1 = require("./User"); class Client extends EventEmitter_1.default { constructor(token) { super(); this.token = token; this.servers = new Map(); this.channels = new Map(); this.users = new Map(); this.invites = new Map(); this.user = new ClientUser_1.default(this, {}); this.request("GET", "/users/me").then(res => { this.user = new ClientUser_1.default(this, res.body); this.build(); }).catch(e => { console.error(e); }); } build() { return __awaiter(this, void 0, void 0, function* () { for (let i in this.user.servers) { let id = this.user.servers[i]; let server = yield this.fetchServer(id); this.servers.set(id, server); server.channels.forEach(c => { c.server = server; c.editable = this.user.id === server.owner.id; c.deletable = c.editable; }); server.members.forEach(m => m.server = server); } this.connect(); }); } /** * Fully disconnect the client and delete the token. This terminates all authorised communiation with the Phoenix Server. * This is not available to bots. */ logout() { return __awaiter(this, void 0, void 0, function* () { if (this.user.bot) throw new Error("Bots cannot logout. Did you mean to refresh the token?"); yield this.request("POST", "/logout"); this.disconnect(); }); } connect() { const socketOptions = {}; socketOptions.query = { token: this.token }; this.socket = (0, socket_io_client_1.io)("https://" + config_1.hostname, socketOptions); this.socket.on('ready', () => this.emit('ready')); this.socket.on('serverJoin', memberData => { const member = new Member_1.default(this, memberData); if (member.user.mutualServers) member.user.mutualServers.set(member.server.id, member.server); if (this.user.id !== member.id) member.server.members.set(member.id, member); this.emit('serverJoin', member); }); this.socket.on('serverEdit', (oldServerData, newServerData) => __awaiter(this, void 0, void 0, function* () { const oldServer = this.servers.get(oldServerData.id); if (!oldServer) return; const newServer = oldServer.clone(); newServer.name = newServerData.name; const member = newServer.members.get(newServerData.owner); if (member) newServer.owner = member; if (newServerData.icon && newServerData.defaultIcon) newServer.iconURL = config_1.defaultIcon; this.servers.set(oldServer.id, newServer); this.emit('serverEdit', oldServer, newServer); })); this.socket.on('serverDelete', (serverData) => { const server = new Server_1.default(this, serverData); this.servers.delete(serverData.id); for (let id of server.channels.keys()) { this.channels.delete(id); } this.emit('serverDelete', server); }); this.socket.on('channelCreate', (channelData) => { let channel; if (channelData.type === "text") { channel = new TextChannel_1.default(this, channelData); channel.server.channels.set(channel.id, channel); } // Add other types as they get created if (channel) { this.channels.set(channel.id, channel); this.emit('channelCreate', channel); } }); this.socket.on('channelEdit', (oldChannelData, newChannelData) => { let newChannel = this.channels.get(oldChannelData.id); if (!newChannel) return; const oldChannel = newChannel.clone(); newChannel.name = newChannelData.name; if (newChannel instanceof ServerChannel_1.default) { newChannel.position = newChannelData.position; newChannel.server.channels.set(oldChannelData.id, newChannel); } this.emit('channelEdit', oldChannel, newChannel); }); this.socket.on('channelDelete', (channelData) => { let channel = this.channels.get(channelData.id); if (!channel) return; channel = channel.clone(); this.channels.delete(channelData.id); if (channel instanceof ServerChannel_1.default) { channel.server.channels.delete(channelData.id); } this.emit('channelDelete', channel); }); this.socket.on('typingStart', (userID, channelID) => { const user = this.users.get(userID), channel = this.channels.get(channelID); if (!channel || !(channel instanceof TextChannel_1.default)) return; if (userID !== this.user.id && !channel.isTyping(userID)) channel.usersTyping.push(userID); this.emit('typingStart', user, channel); }); this.socket.on('typingStop', (userID, channelID) => { const user = this.users.get(userID), channel = this.channels.get(channelID); if (!channel || !(channel instanceof TextChannel_1.default)) return; if (channel.isTyping(userID)) channel.usersTyping.splice(channel.usersTyping.indexOf(userID), 1); this.emit('typingStop', user, channel); }); this.socket.on('userEdit', (oldUserData, newUserData) => { const oldUser = this.users.get(oldUserData.id), newUser = new User_1.default(this, newUserData); if (newUserData.avatarUpdated) newUser.avatarURL = `https://amelix.xyz/avatars/${newUser.id}?lastmod=${newUser.avatarLastUpdated}`; this.users.set(newUserData.id, newUser); newUser.getMutualServers().forEach(s => { if (s.owner.id === newUser.id) s.owner.user = newUser; let member = s.members.get(newUser.id); if (member) member.user = newUser; s.channels.forEach(c => { c.messages.forEach(m => { var _a; if (((_a = m.author) === null || _a === void 0 ? void 0 : _a.id) === newUser.id) m.author = newUser; }); }); }); this.emit('userEdit', oldUser, newUser); }); this.socket.on('userDelete', (userData) => { const user = this.users.get(userData.id); if (!user) return; // Change every message sent by deleted user to properly show them as being deleted user.getMutualServers().forEach(s => { s.channels.forEach(c => { c.messages.forEach(m => { var _a; if (((_a = m.author) === null || _a === void 0 ? void 0 : _a.id) === user.id) m.author = new DeletedUser_1.default(this, m.author); }); }); }); this.users.delete(userData.id); this.emit('userDelete', user); }); this.socket.on('inviteCreate', inviteData => { const invite = new Invite_1.default(this, inviteData); this.invites.set(invite.id, invite); invite.channel.invites.set(invite.id, invite); this.emit('inviteCreate', invite); }); this.socket.on('inviteEdit', (oldInviteData, newInviteData) => { const oldInvite = new Invite_1.default(this, oldInviteData), newInvite = new Invite_1.default(this, newInviteData); if (oldInvite.id !== newInvite.id) { this.invites.delete(oldInvite.id); newInvite.channel.invites.delete(oldInvite.id); } this.invites.set(newInvite.id, newInvite); newInvite.channel.invites.set(newInvite.id, newInvite); this.emit('inviteEdit', oldInvite, newInvite); }); this.socket.on('inviteDelete', inviteData => { let invite = this.invites.get(inviteData.id); if (!invite) return; invite.channel.invites.delete(inviteData.id); this.invites.delete(inviteData.id); this.emit('inviteDelete', inviteData); }); this.socket.on('message', messageData => { const message = new Message_1.default(this, messageData); message.channel.messages.set(message.id, message); message.channel.messageIDs.push(message.id); this.emit('message', message); }); this.socket.on('messageEdit', (oldMessageData, newMessageData) => { const oldMessage = new Message_1.default(this, oldMessageData), newMessage = new Message_1.default(this, newMessageData); oldMessage.channel.messages.set(oldMessage.id, newMessage); this.emit('messageEdit', oldMessage, newMessage); }); this.socket.on('messageDelete', (messageData) => { const message = new Message_1.default(this, messageData); message.channel.messages.delete(message.id); message.channel.messageIDs.splice(message.channel.messageIDs.indexOf(messageData.id), 1); this.emit('messageDelete', message); }); } /** Terminate the real time communication between this client and the server. */ disconnect() { var _a; (_a = this.socket) === null || _a === void 0 ? void 0 : _a.disconnect(); } /** * Create a Phoenix server. All Phoenix servers will have a general text channel made on their creation. * This is not available to bots. */ createServer(name) { return __awaiter(this, void 0, void 0, function* () { if (this.user.bot) throw new Error("Bots cannot create servers."); const res = yield this.request("POST", "/servers", {}, name ? `{"name": "${name}"}` : ""), server = yield this.fetchServer(res.body); this.servers.set(server.id, server); return server; }); } request(method = "GET", path, headers = {}, write) { return __awaiter(this, void 0, void 0, function* () { if (typeof write === "object") write = JSON.stringify(write); headers.authorization = this.token; return new Promise((resolve, reject) => { const req = https.request({ hostname: config_1.hostname, port: 443, path: config_1.apiPath + path, method: method, headers: headers }, res => { let body = ""; res.on('data', chunk => { body += chunk; }); res.on('end', () => { var _a; if ((_a = res.statusCode) === null || _a === void 0 ? void 0 : _a.toString().startsWith("2")) { try { body = JSON.parse(body); } catch (_b) { } resolve(new PhoenixResponse_1.default(res, body)); } else { reject("An error occurred while connecting to Phoenix...\nServer responded with:\t" + res.statusCode + " " + res.statusMessage); } }); }); req.on('error', e => { reject(e); }); req.end(write); }); }); } fetchServer(serverResolvable) { return __awaiter(this, void 0, void 0, function* () { let data; if (typeof serverResolvable === "string") { let server = this.servers.get(serverResolvable); if (server) return server; const res = yield this.request("GET", "/servers/" + serverResolvable); data = res.body; } else data = serverResolvable; let server = new Server_1.default(this, data); for (let i in data.channels) { let id = data.channels[i]; let channel = yield this.fetchChannel(id); if (channel instanceof TextChannel_1.default) server.channels.set(id, channel); } for (let id of data.members) { if (!this.users.has(id)) this.users.set(id, yield this.fetchUser(id)); } const members = yield server.fetchMembers(); let owner = members.get(data.owner); if (owner) server.owner = owner; return server; }); } fetchChannel(id) { return __awaiter(this, void 0, void 0, function* () { let value = this.channels.get(id); if (value) return value; const res = yield this.request("GET", "/channels/" + id); let channel; if (res.body.type === "text") channel = new TextChannel_1.default(this, res.body); if (!channel) throw new Error("FetchChannel: Channel fetching failed, no idea why :)"); this.channels.set(id, channel); return channel; }); } /** Fetch a Phoenix user by ID. */ fetchUser(id) { return __awaiter(this, void 0, void 0, function* () { let user = this.users.get(id); if (user) return user; const res = yield this.request("GET", `/users/` + id); if (res.body.deletedAt) user = new DeletedUser_1.default(this, res.body); else user = new User_1.default(this, res.body); this.users.set(id, user); return user; }); } /** Fetch a Phoenix invite by ID. */ fetchInvite(id) { return __awaiter(this, void 0, void 0, function* () { let invite = this.invites.get(id); if (invite) return invite; const p = yield this.request("GET", `/invites/${id}`).catch(e => null); if (p === null) return; invite = new Invite_1.default(this, p.body); invite.channel.invites.set(id, invite); this.invites.set(id, invite); return invite; }); } } exports.Client = Client;