feat: implement Bot and PublicBot

This commit is contained in:
Paul Makles
2023-04-12 11:20:04 +01:00
parent d04b402d3e
commit 353a2eb427
11 changed files with 320 additions and 25 deletions
+4 -1
View File
@@ -6,6 +6,7 @@ import type { DataLogin, RevoltConfig } from "revolt-api";
import { Channel, Emoji, Message, Server, ServerMember, User } from "./classes";
import {
BotCollection,
ChannelCollection,
ChannelUnreadCollection,
EmojiCollection,
@@ -134,13 +135,14 @@ export type ClientOptions = Partial<EventClientOptions> & {
* Revolt.js Clients
*/
export class Client extends EventEmitter<Events> {
readonly bots;
readonly channels;
readonly channelUnreads;
readonly emojis;
readonly messages;
readonly users;
readonly servers;
readonly serverMembers;
readonly users;
readonly api: API;
readonly options: ClientOptions;
@@ -199,6 +201,7 @@ export class Client extends EventEmitter<Events> {
this.connectionFailureCount = connectionFailureCount;
this.#setConnectionFailureCount = setConnectionFailureCount;
this.bots = new BotCollection(this);
this.channels = new ChannelCollection(this);
this.channelUnreads = new ChannelUnreadCollection(this);
this.emojis = new EmojiCollection(this);
+130
View File
@@ -0,0 +1,130 @@
import { DataEditBot } from "revolt-api";
import { decodeTime } from "ulid";
import { BotCollection } from "../collections";
/**
* Bot Class
*/
export class Bot {
readonly #collection: BotCollection;
readonly id: string;
/**
* Construct Bot
* @param collection Collection
* @param id Id
*/
constructor(collection: BotCollection, id: string) {
this.#collection = collection;
this.id = id;
}
/**
* Convert to string
* @returns String
*/
toString() {
return `<@${this.id}>`;
}
/**
* Time when this user created their account
*/
get createdAt() {
return new Date(decodeTime(this.id));
}
/**
* Corresponding user
*/
get user() {
return this.#collection.client.users.get(this.id);
}
/**
* Owner's Id
*/
get ownerId() {
return this.#collection.getUnderlyingObject(this.id).ownerId;
}
/**
* Owner
*/
get owner() {
return this.#collection.client.users.get(this.ownerId);
}
/**
* Bot Token
*/
get token() {
return this.#collection.getUnderlyingObject(this.id).token;
}
/**
* Whether this bot can be invited by anyone
*/
get public() {
return this.#collection.getUnderlyingObject(this.id).public;
}
/**
* Whether this bot has analytics enabled
*/
get analytics() {
return this.#collection.getUnderlyingObject(this.id).analytics;
}
/**
* Whether this bot shows up on Discover
*/
get discoverable() {
return this.#collection.getUnderlyingObject(this.id).discoverable;
}
/**
* Interactions URL
*/
get interactionsUrl() {
return this.#collection.getUnderlyingObject(this.id).interactionsUrl;
}
/**
* Link to terms of service
*/
get termsOfServiceUrl() {
return this.#collection.getUnderlyingObject(this.id).termsOfServiceUrl;
}
/**
* Link to privacy policy
*/
get privacyPolicyUrl() {
return this.#collection.getUnderlyingObject(this.id).privacyPolicyUrl;
}
/**
* Bot Flags
*/
get flags() {
return this.#collection.getUnderlyingObject(this.id).flags;
}
/**
* Edit a bot
* @param data Changes
*/
async edit(data: DataEditBot) {
await this.#collection.client.api.patch(`/bots/${this.id as ""}`, data);
}
/**
* Delete a bot
*/
async delete() {
await this.#collection.client.api.delete(`/bots/${this.id as ""}`);
this.#collection.delete(this.id);
}
}
+5 -7
View File
@@ -358,21 +358,19 @@ export class Channel {
/**
* Delete or leave a channel
* @param leaveSilently Whether to not send a message on leave
* @param noRequest Whether to not send a request
* @requires `DirectMessage`, `Group`, `TextChannel`, `VoiceChannel`
*/
async delete(leaveSilently?: boolean, noRequest?: boolean) {
if (!noRequest)
await this.#collection.client.api.delete(`/channels/${this.id as ""}`, {
leave_silently: leaveSilently,
});
async delete(leaveSilently?: boolean) {
await this.#collection.client.api.delete(`/channels/${this.id as ""}`, {
leave_silently: leaveSilently,
});
if (this.type === "DirectMessage") {
this.#collection.updateUnderlyingObject(this.id, "active", false);
return;
}
this.#collection.client.channels.delete(this.id);
this.#collection.delete(this.id);
}
/**
+47
View File
@@ -0,0 +1,47 @@
import { API, Channel, Client, File, Server } from "..";
/**
* Public Bot Class
*/
export class PublicBot {
#client: Client;
readonly id: string;
readonly username: string;
readonly avatar?: File;
readonly description?: string;
/**
* Construct Public Bot
* @param client Client
* @param data Data
*/
constructor(client: Client, data: API.PublicBot) {
this.#client = client;
this.id = data._id;
this.username = data.username;
this.avatar = data.avatar ? new File(client, data.avatar) : undefined;
this.description = data.description!;
}
/**
* Add the bot to a server
* @param server Server
*/
addToServer(server: Server | string) {
this.#client.api.post(`/bots/${this.id as ""}/invite`, {
server: server instanceof Server ? server.id : server,
});
}
/**
* Add the bot to a group
* @param group Group
*/
addToGroup(group: Channel | string) {
// TODO: should use GroupChannel once that is added
this.#client.api.post(`/bots/${this.id as ""}/invite`, {
group: group instanceof Channel ? group.id : group,
});
}
}
+4 -6
View File
@@ -338,13 +338,11 @@ export class Server {
/**
* Delete or leave a server
* @param leaveSilently Whether to not send a message on leave
* @param noRequest Whether to not send a request
*/
async delete(leaveSilently?: boolean, avoidReq?: boolean) {
if (!avoidReq)
await this.#collection.client.api.delete(`/servers/${this.id as ""}`, {
leave_silently: leaveSilently,
});
async delete(leaveSilently?: boolean) {
await this.#collection.client.api.delete(`/servers/${this.id as ""}`, {
leave_silently: leaveSilently,
});
this.#collection.delete(this.id);
}
+10 -8
View File
@@ -1,9 +1,11 @@
export { Channel } from "./Channel";
export { Emoji } from "./Emoji";
export { Message } from "./Message";
export { Server } from "./Server";
export { ServerMember } from "./ServerMember";
export { User } from "./User";
export { File } from "./File";
export { ChannelUnread } from "./ChannelUnread";
export * from "./Bot";
export * from "./Channel";
export * from "./ChannelUnread";
export * from "./Emoji";
export * from "./File";
export * from "./Message";
export * from "./MessageEmbed";
export * from "./PublicBot";
export * from "./Server";
export * from "./ServerMember";
export * from "./User";
+70
View File
@@ -0,0 +1,70 @@
import { OwnedBotsResponse } from "revolt-api";
import { API, Bot, PublicBot } from "..";
import { HydratedBot } from "../hydration/bot";
import { ClassCollection } from ".";
export class BotCollection extends ClassCollection<Bot, HydratedBot> {
/**
* Fetch bot by ID
* @param id Id
* @returns Bot
*/
async fetch(id: string): Promise<Bot> {
const bot = this.get(id);
if (bot) return bot;
const data = await this.client.api.get(`/bots/${id as ""}`);
this.client.users.getOrCreate(data.user._id, data.user);
return this.getOrCreate(data.bot._id, data.bot);
}
/**
* Fetch owned bots
* @returns List of bots
*/
async fetchOwned(): Promise<Bot[]> {
const data = (await this.client.api.get("/bots/@me")) as OwnedBotsResponse;
data.users.forEach((user) => this.client.users.getOrCreate(user._id, user));
return data.bots.map((bot) => this.getOrCreate(bot._id, bot));
}
/**
* Fetch public bot by ID
* @param id Id
* @returns Public Bot
*/
async fetchPublic(id: string): Promise<PublicBot> {
const data = await this.client.api.get(`/bots/${id as ""}/invite`);
return new PublicBot(this.client, data);
}
/**
* Get or create
* @param id Id
* @param data Data
* @returns Bot
*/
getOrCreate(id: string, data: API.Bot) {
if (this.has(id)) {
return this.get(id)!;
} else {
const instance = new Bot(this, id);
this.create(id, "bot", instance, this.client, data);
return instance;
}
}
/**
* Create a bot
* @param name Bot name
* @returns The newly-created bot
*/
async createBot(name: string) {
const bot = await this.client.api.post(`/bots/create`, {
name,
});
return this.getOrCreate(bot._id, bot);
}
}
+1
View File
@@ -1,5 +1,6 @@
export * from "./Collection";
export { BotCollection } from "./BotCollection";
export { ChannelCollection } from "./ChannelCollection";
export { ChannelUnreadCollection } from "./ChannelUnreadCollection";
export { EmojiCollection } from "./EmojiCollection";
+44
View File
@@ -0,0 +1,44 @@
import { Bot as ApiBot } from "revolt-api";
import { Hydrate } from ".";
export type HydratedBot = {
id: string;
ownerId: string;
token: string;
public: boolean;
analytics: boolean;
discoverable: boolean;
interactionsUrl?: string;
termsOfServiceUrl?: string;
privacyPolicyUrl?: string;
flags: BotFlags;
};
export const botHydration: Hydrate<ApiBot, HydratedBot> = {
keyMapping: {
_id: "id",
owner: "ownerId",
interactions_url: "interactionsUrl",
terms_of_service_url: "termsOfServiceUrl",
privacy_policy_url: "privacyPolicyUrl",
},
functions: {
id: (bot) => bot._id,
ownerId: (bot) => bot.owner,
token: (bot) => bot.token,
public: (bot) => bot.public,
analytics: (bot) => bot.analytics!,
discoverable: (bot) => bot.discoverable!,
interactionsUrl: (bot) => bot.interactions_url!,
termsOfServiceUrl: (bot) => bot.terms_of_service_url!,
privacyPolicyUrl: (bot) => bot.privacy_policy_url!,
flags: (bot) => bot.flags!,
},
initialHydration: () => ({}),
};
/**
* Flags attributed to users
*/
export enum BotFlags {}
+2
View File
@@ -1,3 +1,4 @@
import { botHydration } from "./bot";
import { channelHydration } from "./channel";
import { channelUnreadHydration } from "./channelUnread";
import { emojiHydration } from "./emoji";
@@ -66,6 +67,7 @@ function hydrateInternal<Input extends object, Output>(
}
const hydrators = {
bot: botHydration,
channel: channelHydration,
channelUnread: channelUnreadHydration,
emoji: emojiHydration,
+3 -3
View File
@@ -32,13 +32,13 @@ export const userHydration: Hydrate<ApiUser, HydratedUser> = {
functions: {
id: (user) => user._id,
username: (user) => user.username,
relationship: (user) => user.relationship ?? "None",
relationship: (user) => user.relationship!,
online: (user) => user.online!,
privileged: (user) => user.privileged,
badges: (user) => user.badges ?? 0,
flags: (user) => user.flags ?? 0,
badges: (user) => user.badges!,
flags: (user) => user.flags!,
avatar: (user, ctx) => new File(ctx, user.avatar!),
status: (user) => user.status!,