@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
JavaScript
"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;