feat: switch to new revolt-api

This commit is contained in:
Paul Makles
2022-03-22 18:06:52 +00:00
parent eb19ee0e9e
commit 4e9542d058
16 changed files with 392 additions and 1294 deletions
+6 -2
View File
@@ -7,7 +7,7 @@
## Example Usage (Javascript / ES6)
```javascript
import { Client } from "revolt.js";
import { Client } from "revolt-api";
let client = new Client();
@@ -31,7 +31,7 @@ For example, `node --experimental-specifier-resolution=node index.js`.
## Example Usage (Typescript)
```typescript
import { Client } from "revolt.js";
import { Client } from "revolt-api";
let client = new Client();
@@ -63,3 +63,7 @@ client.once('ready', () => {
});
});
```
## Revolt API Types
All `revolt-api` types are re-exported from this library.
+2 -2
View File
@@ -1,7 +1,7 @@
{
"type": "module",
"name": "revolt.js",
"version": "5.2.8",
"version": "6.0.0-rc.0",
"main": "dist/index.js",
"repository": "https://github.com/revoltchat/revolt.js",
"author": "Paul Makles <insrt.uk>",
@@ -16,7 +16,7 @@
"lodash.isequal": "^4.5.0",
"long": "^5.2.0",
"mobx": "^6.3.2",
"revolt-api": "0.5.3-alpha.12",
"revolt-api": "0.5.3-rc.7",
"ulid": "^2.3.0",
"ws": "^8.2.2"
},
+58 -125
View File
@@ -1,13 +1,9 @@
import Axios, { AxiosInstance, AxiosRequestConfig } from "axios";
import EventEmitter from "eventemitter3";
import defaultsDeep from "lodash.defaultsdeep";
import { action, makeObservable, observable } from "mobx";
import type { Session } from "revolt-api/types/Auth";
import type { AttachmentMetadata, SizeOptions } from "revolt-api/types/Autumn";
import type { RevoltConfiguration } from "revolt-api/types/Core";
import { MemberCompositeKey, Role } from "revolt-api/types/Servers";
import { Route, RoutePath, RouteMethod } from "./api/routes";
import type { DataCreateAccount, DataLogin, DataOnboard, Session } from "revolt-api";
import type { RevoltConfig, Metadata } from "revolt-api";
import { API, MemberCompositeKey, Role } from "revolt-api";
import Bots from "./maps/Bots";
import Channels, { Channel } from "./maps/Channels";
@@ -102,7 +98,12 @@ export const RE_MENTIONS = /<@([A-z0-9]{26})>/g;
export const RE_SPOILER = /!!.+!!/g;
export type FileArgs = [
options?: SizeOptions,
options?: {
max_side?: number,
size?: number,
width?: number,
height?: number
},
allowAnimation?: boolean,
fallback?: string,
];
@@ -110,13 +111,13 @@ export type FileArgs = [
export class Client extends EventEmitter {
heartbeat: number;
api: API;
session?: Session | string;
user: Nullable<User> = null;
options: ClientOptions;
websocket: WebSocketClient;
private Axios: AxiosInstance;
configuration?: RevoltConfiguration;
configuration?: RevoltConfig;
users: Users;
channels: Channels;
@@ -160,103 +161,36 @@ export class Client extends EventEmitter {
this.unreads = new Unreads(this);
}
this.Axios = Axios.create({ baseURL: this.apiURL });
this.api = new API({ baseURL: this.apiURL });
this.websocket = new WebSocketClient(this);
this.heartbeat = this.options.heartbeat;
if (options.debug) {
this.Axios.interceptors.request.use((request) => {
console.debug(
"[<]",
request.method?.toUpperCase(),
request.url,
);
return request;
});
this.Axios.interceptors.response.use((response) => {
console.debug(
"[>] (" + response.status + ":",
response.statusText + ")",
JSON.stringify(response.data),
);
return response;
});
}
}
/**
* ? Configuration.
*/
/**
* Get the current API base URL.
*/
get apiURL() {
return this.options.apiURL;
}
/**
* Whether debug mode is turned on.
*/
get debug() {
return this.options.debug;
}
/**
* Whether revolt.js should auto-reconnect
*/
get autoReconnect() {
return this.options.autoReconnect;
}
/**
* ? Axios request wrapper.
*/
req<M extends RouteMethod, T extends RoutePath>(
method: M,
url: T,
): Promise<Route<M, T>["response"]>;
req<M extends RouteMethod, T extends RoutePath>(
method: M,
url: T,
data: Route<M, T>["data"],
): Promise<Route<M, T>["response"]>;
/**
* Perform an HTTP request using Axios, specifying a route data object.
* @param method HTTP method
* @param url Target route
* @param data Route data object
* @returns The response body
*/
async req<M extends RouteMethod, T extends RoutePath>(
method: M,
url: T,
data?: Route<M, T>["data"],
): Promise<Route<M, T>["response"]> {
const res = await this.Axios.request({
method,
data,
url,
});
return res.data;
}
/**
* Perform an HTTP request using Axios, specifying a request config.
* @param method HTTP method
* @param url Target route
* @param data Axios request config object
* @returns The response body
*/
async request<M extends RouteMethod, T extends RoutePath>(
method: M,
url: T,
data: AxiosRequestConfig,
): Promise<Route<M, T>["response"]> {
const res = await this.Axios.request({
...data,
method,
url,
});
return res.data;
}
/**
* ? Authentication and connection.
*/
@@ -269,7 +203,7 @@ export class Client extends EventEmitter {
* configuration if it has already been fetched before.
*/
async connect() {
this.configuration = await this.req("GET", "/");
this.configuration = await this.api.get("/");
}
/**
@@ -279,22 +213,16 @@ export class Client extends EventEmitter {
if (!this.configuration) await this.connect();
}
private $generateHeaders(
session: Session | string | undefined = this.session,
) {
if (typeof session === "string") {
return {
"x-bot-token": session,
};
} else {
return {
"x-session-token": session?.token,
};
}
}
/**
* Update API object to use authentication.
*/
private $updateHeaders() {
this.Axios.defaults.headers = this.$generateHeaders();
this.api = new API({
baseURL: this.apiURL,
authentication: {
revolt: this.session
}
});
}
/**
@@ -302,11 +230,15 @@ export class Client extends EventEmitter {
* @param details Login data object
* @returns An onboarding function if onboarding is required, undefined otherwise
*/
async login(details: Route<"POST", "/auth/session/login">["data"]) {
async login(details: DataLogin) {
await this.fetchConfiguration();
this.session = await this.req("POST", "/auth/session/login", details);
return await this.$connect();
const data = await this.api.post("/auth/session/login", details);
if (data.result === 'Success') {
this.session = data;
return await this.$connect();
} else {
throw "MFA not implemented!";
}
}
/**
@@ -337,7 +269,7 @@ export class Client extends EventEmitter {
*/
private async $connect() {
this.$updateHeaders();
const { onboarding } = await this.req("GET", "/onboard/hello");
const { onboarding } = await this.api.get("/onboard/hello");
if (onboarding) {
return (username: string, loginAfterSuccess?: boolean) =>
this.completeOnboarding({ username }, loginAfterSuccess);
@@ -352,10 +284,10 @@ export class Client extends EventEmitter {
* @param loginAfterSuccess Defines whether to automatically log in and connect after onboarding finishes
*/
async completeOnboarding(
data: Route<"POST", "/onboard/complete">["data"],
data: DataOnboard,
loginAfterSuccess?: boolean,
) {
await this.req("POST", "/onboard/complete", data);
await this.api.post("/onboard/complete", data);
if (loginAfterSuccess) {
await this.websocket.connect();
}
@@ -371,7 +303,7 @@ export class Client extends EventEmitter {
* @returns Invite information.
*/
async fetchInvite(code: string) {
return await this.req("GET", `/invites/${code}` as "/invites/id");
return await this.api.get(`/invites/${code}`);
}
/**
@@ -380,7 +312,7 @@ export class Client extends EventEmitter {
* @returns Data provided by invite.
*/
async joinInvite(code: string) {
return await this.req("POST", `/invites/${code}` as "/invites/id");
return await this.api.post(`/invites/${code}`);
}
/**
@@ -388,7 +320,7 @@ export class Client extends EventEmitter {
* @param code The invite code.
*/
async deleteInvite(code: string) {
await this.req("DELETE", `/invites/${code}` as "/invites/id");
await this.api.delete(`/invites/${code}`);
}
/**
@@ -397,7 +329,7 @@ export class Client extends EventEmitter {
* @returns Key-value object of settings.
*/
async syncFetchSettings(keys: string[]) {
return await this.req("POST", "/sync/settings/fetch", { keys });
return await this.api.post("/sync/settings/fetch", { keys });
}
/**
@@ -416,11 +348,12 @@ export class Client extends EventEmitter {
typeof value === "string" ? value : JSON.stringify(value);
}
const query = timestamp ? `?timestamp=${timestamp}` : "";
await this.req(
"POST",
`/sync/settings/set${query}` as "/sync/settings/set",
requestData,
await this.api.post(
`/sync/settings/set`,
{
...requestData,
timestamp
}
);
}
@@ -429,7 +362,7 @@ export class Client extends EventEmitter {
* @returns Array of channel unreads.
*/
async syncFetchUnreads() {
return await this.req("GET", "/sync/unreads");
return await this.api.get("/sync/unreads");
}
/**
@@ -442,7 +375,7 @@ export class Client extends EventEmitter {
async logout(avoidRequest?: boolean) {
this.user = null;
this.emit("logout");
!avoidRequest && (await this.req("POST", "/auth/session/logout"));
!avoidRequest && (await this.api.post("/auth/session/logout"));
this.reset();
}
@@ -467,8 +400,8 @@ export class Client extends EventEmitter {
* @param data Registration data object
* @returns A promise containing a registration response object
*/
register(data: Route<"POST", "/auth/account/create">["data"]) {
return this.request("POST", "/auth/account/create", { data });
register(data: DataCreateAccount) {
return this.api.post("/auth/account/create", data);
}
/**
@@ -512,7 +445,7 @@ export class Client extends EventEmitter {
* @returns Generated URL or nothing
*/
generateFileURL(
attachment?: { tag: string; _id: string; content_type?: string, metadata?: AttachmentMetadata },
attachment?: { tag: string; _id: string; content_type?: string, metadata?: Metadata },
...args: FileArgs
) {
const [options, allowAnimation, fallback] = args;
@@ -537,7 +470,7 @@ export class Client extends EventEmitter {
query =
"?" +
Object.keys(options)
.map((k) => `${k}=${options[k as keyof SizeOptions]}`)
.map((k) => `${k}=${options[k as keyof FileArgs[0]]}`)
.join("&");
}
}
-853
View File
@@ -1,853 +0,0 @@
import { Account, Session, SessionInfo } from "revolt-api/types/Auth";
import type { Bot, PublicBot } from "revolt-api/types/Bots";
import type {
Channel,
DirectMessageChannel,
GroupChannel,
Message,
TextChannel,
VoiceChannel,
SendableEmbed
} from "revolt-api/types/Channels";
import type { RevoltConfiguration } from "revolt-api/types/Core";
import type { Invite, RetrievedInvite, ServerInvite } from "revolt-api/types/Invites";
import type {
Ban,
Category,
Member,
Role,
Server,
SystemMessageChannels,
} from "revolt-api/types/Servers";
import type { UserSettings, ChannelUnread } from "revolt-api/types/Sync";
import type {
Profile,
Relationship,
RelationshipOnly,
Status,
User,
} from "revolt-api/types/Users";
import { Override } from "revolt-api/types/_common";
export type RemoveUserField =
| "ProfileContent"
| "ProfileBackground"
| "StatusText"
| "Avatar";
export type RemoveChannelField = "Icon" | "Description";
export type RemoveServerField = "Icon" | "Banner" | "Description";
export type RemoveMemberField = "Nickname" | "Avatar";
export type RemoveRoleField = "Colour";
export type BulkMessageResponse = Message[]
| {
messages: Message[];
users: User[];
members?: Member[];
};
type Id = "id";
type Routes =
/**
* Core
*/
| {
// Query Revolt node.
method: "GET";
route: `/`;
data: undefined;
response: RevoltConfiguration;
}
/**
* Auth
*/
| {
method: "GET";
route: `/auth/account`;
data: undefined;
response: Account;
}
| {
method: "POST";
route: `/auth/account/create`;
data: {
email: string;
password: string;
invite?: string;
captcha?: string;
};
response: undefined;
}
| {
method: "POST";
route: `/auth/account/reverify`;
data: {
email: string;
captcha?: string;
};
response: undefined;
}
| {
method: "POST";
route: `/auth/account/verify/:code`;
data: undefined;
response: undefined;
}
| {
method: "POST";
route: `/auth/account/reset_password`;
data: {
email: string;
captcha?: string;
};
response: undefined;
}
| {
method: "PATCH";
route: `/auth/account/reset_password`;
data: {
password: string;
token: string;
};
response: undefined;
}
| {
method: "PATCH";
route: `/auth/account/change/password`;
data: {
password: string;
current_password: string;
};
response: undefined;
}
| {
method: "PATCH";
route: `/auth/account/change/email`;
data: {
email: string;
current_password: string;
};
response: undefined;
}
| {
method: "POST";
route: `/auth/session/login`;
data: {
email: string;
password?: string;
challenge?: string;
friendly_name?: string;
captcha?: string;
};
response: Session;
}
| {
method: "POST";
route: `/auth/session/logout`;
data: undefined;
response: undefined;
}
| {
method: "PATCH";
route: `/auth/session/${Id}`;
data: {
friendly_name: string;
};
response: undefined;
}
| {
method: "DELETE";
route: `/auth/session/${Id}`;
data: undefined;
response: undefined;
}
| {
method: "GET";
route: `/auth/session/all`;
data: undefined;
response: SessionInfo[];
}
| {
method: "DELETE";
route: `/auth/session/all`;
data: undefined;
response: undefined;
}
/**
* Onboarding
*/
| {
// Ask server for user status.
method: "GET";
route: `/onboard/hello`;
data: undefined;
response: {
onboarding: boolean;
id?: string;
};
}
| {
// Complete onboarding.
method: "POST";
route: `/onboard/complete`;
data: {
username: string;
};
response: {
onboarding: boolean;
};
}
/**
* Users
*/
| {
// Retrieve a user's information.
method: "GET";
route: `/users/${Id}`;
data: undefined;
response: User;
}
| {
// Edit user.
method: "PATCH";
route: `/users/@me`;
data: {
status?: Status;
profile?: {
content?: string;
background?: string;
};
avatar?: string;
remove?: RemoveUserField;
};
response: User;
}
| {
// Change username.
method: "PATCH";
route: `/users/@me/username`;
data: {
username: string;
password: string;
};
response: User;
}
| {
// Retrieve a user's profile.
method: "GET";
route: `/users/${Id}/profile`;
data: undefined;
response: Profile;
}
| {
// Fetch all your DM conversations.
method: "GET";
route: `/users/dms`;
data: undefined;
response: (DirectMessageChannel | GroupChannel)[];
}
| {
// Open a DM with another user.
method: "GET";
route: `/users/${Id}/dm`;
data: undefined;
response: DirectMessageChannel;
}
| {
// Fetch all of your relationships with other users.
method: "GET";
route: `/users/relationships`;
data: undefined;
response: Relationship[];
}
| {
// Fetch your relationship with another other user.
method: "GET";
route: `/users/${Id}/relationship`;
data: undefined;
response: RelationshipOnly;
}
| {
// Fetch your mutual relationships with another user.
method: "GET";
route: `/users/${Id}/mutual`;
data: undefined;
response: {
users: string[];
servers: string[];
};
}
| {
// Send a friend request / accept a friend request.
method: "PUT";
route: `/users/${Id}/friend`;
data: undefined;
response: RelationshipOnly;
}
| {
// Delete a friend request / remove a friend.
method: "DELETE";
route: `/users/${Id}/friend`;
data: undefined;
response: RelationshipOnly;
}
| {
// Block a user.
method: "PUT";
route: `/users/${Id}/block`;
data: undefined;
response: RelationshipOnly;
}
| {
// Unblock a user.
method: "DELETE";
route: `/users/${Id}/block`;
data: undefined;
response: RelationshipOnly;
}
| {
// Fetch a user's avatar.
method: "GET";
route: `/users/${Id}/avatar`;
data: undefined;
response: unknown;
}
| {
// Fetch default avatar for user.
method: "GET";
route: `/users/${Id}/default_avatar`;
data: undefined;
response: unknown;
}
/**
* Channels
*/
| {
// Fetch a channel by ID.
method: "GET";
route: `/channels/${Id}`;
data: undefined;
response: Channel;
}
| {
// Fetch a channel's members by ID.
method: "GET";
route: `/channels/${Id}/members`;
data: undefined;
response: User[];
}
| {
// Edit a channel.
method: "PATCH";
route: `/channels/${Id}`;
data: {
name?: string;
description?: string;
icon?: string;
nsfw?: boolean;
remove?: RemoveChannelField;
};
response: Channel;
}
| {
// Close DM channel or leave group channel.
method: "DELETE";
route: `/channels/${Id}`;
data: undefined;
response: undefined;
}
| {
// Create a group DM.
method: "POST";
route: `/channels/create`;
data: {
name: string;
description?: string;
nonce: string;
users: string[];
};
response: GroupChannel;
}
| {
// Create an invite to a channel.
method: "POST";
route: `/channels/${Id}/invites`;
data: undefined;
response: Invite;
}
| {
// Add member to group.
method: "PUT";
route: `/channels/${Id}/recipients/${Id}`;
data: undefined;
response: undefined;
}
| {
// Remove member from group.
method: "DELETE";
route: `/channels/${Id}/recipients/${Id}`;
data: undefined;
response: undefined;
}
| {
// Mark a channel as read at message.
method: "PUT";
route: `/channels/${Id}/ack/${Id}`;
data: undefined;
response: undefined;
}
| {
// Set default permissions for channel.
method: "PUT";
route: `/channels/${Id}/permissions/default`;
data: {
permissions?: Override | number;
};
response: Channel;
}
| {
// Set role permission for channel.
method: "PUT";
route: `/channels/${Id}/permissions/${Id}`;
data: {
permissions?: Override | number;
};
response: Channel;
}
/**
* Messaging
*/
| {
// Send message to channel.
method: "POST";
route: `/channels/${Id}/messages`;
data: {
content: string;
nonce: string;
attachments?: string[];
replies?: { id: string; mention: boolean }[];
embeds?: SendableEmbed[];
};
response: Message;
}
| {
// Fetch message from channel.
method: "GET";
route: `/channels/${Id}/messages/${Id}`;
data: undefined;
response: Message;
}
| {
// Query messages from channel (optionally include users as well).
method: "GET";
route: `/channels/${Id}/messages`;
data: {
limit?: number;
before?: string;
after?: string;
sort?: "Latest" | "Oldest";
nearby?: string;
include_users?: boolean;
};
response: BulkMessageResponse;
}
| {
// Search messages in a channel (optionally include users as well).
method: "POST";
route: `/channels/${Id}/search`;
data: {
query: string;
limit?: number;
before?: string;
after?: string;
sort?: "Relevance" | "Latest" | "Oldest";
include_users?: boolean;
};
response: BulkMessageResponse;
}
| {
// Query updated messages from channel.
method: "POST";
route: `/channels/${Id}/messages/stale`;
data: {
ids: string[];
};
response: {
updated: Message[];
deleted: string[];
};
}
| {
// Edit message.
method: "PATCH";
route: `/channels/${Id}/messages/${Id}`;
data: {
content?: string;
embeds?: SendableEmbed[];
};
response: Message;
}
| {
// Delete message.
method: "DELETE";
route: `/channels/${Id}/messages/${Id}`;
data: undefined;
response: undefined;
}
/**
* Servers
*/
| {
// Create a server.
method: "POST";
route: `/servers/create`;
data: {
name: string;
nonce: string;
};
response: Server;
}
| {
// Fetch a server by ID.
method: "GET";
route: `/servers/${Id}`;
data: undefined;
response: Server;
}
| {
// Delete or leave a server.
method: "DELETE";
route: `/servers/${Id}`;
data: undefined;
response: undefined;
}
| {
// Edit a server.
method: "PATCH";
route: `/servers/${Id}`;
data: {
name?: string;
description?: string;
icon?: string;
banner?: string;
categories?: Category[];
system_messages?: SystemMessageChannels;
nsfw?: boolean;
remove?: RemoveServerField;
};
response: Server;
}
| {
// Mark an entire server as read.
method: "PUT";
route: `/servers/${Id}/ack`;
data: undefined;
response: undefined;
}
| {
// Fetch a server's members
method: "GET";
route: `/servers/${Id}/members`;
data: undefined;
response: {
members: Member[];
users: User[];
};
}
| {
// Fetch a server member by their ID
method: "GET";
route: `/servers/${Id}/members/${Id}`;
data: undefined;
response: Member;
}
| {
// Edit a server member
method: "PATCH";
route: `/servers/${Id}/members/${Id}`;
data: {
nickname?: string;
avatar?: string;
roles?: string[];
remove?: RemoveMemberField;
};
response: Member;
}
| {
// Kick a server member
method: "DELETE";
route: `/servers/${Id}/members/${Id}`;
data: undefined;
response: undefined;
}
| {
// Ban a user from the server
method: "PUT";
route: `/servers/${Id}/bans/${Id}`;
data: {
reason?: string;
};
response: Ban;
}
| {
// Unban a user from the server
method: "DELETE";
route: `/servers/${Id}/bans/${Id}`;
data: undefined;
response: undefined;
}
| {
// Fetch a server's bans
method: "GET";
route: `/servers/${Id}/bans`;
data: undefined;
response: {
users: Pick<User, "_id" | "username" | "avatar">[];
bans: Ban[];
};
}
| {
// Create a new server channel
method: "POST";
route: `/servers/${Id}/channels`;
data: {
type?: "Text" | "Voice";
name: string;
description?: string;
nonce: string;
};
response: TextChannel | VoiceChannel;
}
| {
// Fetch a server's invites
method: "GET";
route: `/servers/${Id}/invites`;
data: undefined;
response: ServerInvite[];
}
| {
// Create role
method: "POST";
route: `/servers/${Id}/roles`;
data: {
name: string;
};
response: {
id: string;
role: Role;
};
}
| {
// Edit a role.
method: "PATCH";
route: `/servers/${Id}/roles/${Id}`;
data: {
name?: string;
colour?: string;
hoist?: boolean;
rank?: number;
remove?: RemoveRoleField;
};
response: Role;
}
| {
// Delete role
method: "DELETE";
route: `/servers/${Id}/roles/${Id}`;
data: undefined;
response: undefined;
}
| {
// Set default permission for server.
method: "PUT";
route: `/servers/${Id}/permissions/default`;
data: {
permissions: number;
};
response: Server;
}
| {
// Set role permission for server.
method: "PUT";
route: `/servers/${Id}/permissions/${Id}`;
data: {
permissions: Override | number;
};
response: Server;
}
/**
* Bots
*/
| {
// Create Bot
method: "POST";
route: `/bots/create`;
data: {
name: string;
};
response: Bot;
}
| {
// Fetch Owned Bots
method: "GET";
route: `/bots/@me`;
data: undefined;
response: {
bots: Bot[];
users: User[];
};
}
| {
// Fetch Bot
method: "GET";
route: `/bots/${Id}`;
data: undefined;
response: {
bot: Bot;
user: User;
};
}
| {
// Edit Bot
method: "PATCH";
route: `/bots/${Id}`;
data: {
name?: string;
public?: boolean;
interactionsURL?: string;
remove?: "InteractionsURL";
};
response: Bot;
}
| {
// Delete Bot
method: "DELETE";
route: `/bots/${Id}`;
data: undefined;
response: undefined;
}
| {
// Fetch Public Bot
method: "GET";
route: `/bots/${Id}/invite`;
data: undefined;
response: PublicBot;
}
| {
// Invite Public Bot
method: "POST";
route: `/bots/${Id}/invite`;
data: { server: string } | { group: string };
response: undefined;
}
/**
* Invites
*/
| {
// Fetch information about an invite.
method: "GET";
route: `/invites/${Id}`;
data: undefined;
response: RetrievedInvite;
}
| {
// Use an invite.
method: "POST";
route: `/invites/${Id}`;
data: undefined;
response: {
type: "Server";
channel: TextChannel;
server: Server;
};
}
| {
// Delete an invite.
method: "DELETE";
route: `/invites/${Id}`;
data: undefined;
response: undefined;
}
/**
* Sync
*/
| {
// Fetch user settings.
method: "POST";
route: `/sync/settings/fetch`;
data: {
keys: string[];
};
response: UserSettings;
}
| {
// Set user settings.
method: "POST";
route: `/sync/settings/set`;
data: {
[key: string]: string;
};
response: undefined;
}
| {
// Fetch unreads.
method: "GET";
route: `/sync/unreads`;
data: undefined;
response: ChannelUnread[];
}
/**
* Push API
*/
| {
method: "POST";
route: `/push/subscribe`;
data: {
endpoint: string;
p256dh: string;
auth: string;
};
response: undefined;
}
| {
method: "POST";
route: `/push/unsubscribe`;
data: undefined;
response: undefined;
}
/**
* Voice API
*/
| {
// Join voice call for channel.
method: "POST";
route: `/channels/${Id}/join_call`;
data: undefined;
response: {
token: string;
};
};
// ? Below are Typescript typings for extracting route data from the object above.
// ? https://artsy.github.io/blog/2018/11/21/conditional-types-in-typescript/
export type RoutePath = Routes["route"];
export type RouteMethod = Routes["method"];
type ExcludeRouteKey<K> = K extends "route" ? never : K;
type ExcludeRouteField<A> = { [K in ExcludeRouteKey<keyof A>]: A[K] };
type ExtractRouteParameters<A, T> = A extends { route: T }
? ExcludeRouteField<A>
: never;
type ExcludeMethodKey<K> = K extends "method" ? never : K;
type ExcludeMethodField<A> = { [K in ExcludeMethodKey<keyof A>]: A[K] };
type ExtractMethodParameters<A, T> = A extends { method: T }
? ExcludeMethodField<A>
: never;
export type Route<
M extends RouteMethod,
T extends RoutePath,
> = ExtractMethodParameters<ExtractRouteParameters<Routes, T>, M>;
+1
View File
@@ -4,3 +4,4 @@ export {
UserPermission,
Permission
} from "./api/permissions";
export * from "revolt-api";
+17 -20
View File
@@ -1,9 +1,9 @@
import { runInAction } from "mobx";
import { Route } from "../api/routes";
import { Client } from "../Client";
import { InviteBotDestination, DataEditBot, DataCreateBot, OwnedBotsResponse } from 'revolt-api';
export default class Bots {
client: Client;
@@ -17,10 +17,10 @@ export default class Bots {
* @returns Bot and User object
*/
async fetch(id: string) {
const { bot, user } = await this.client.req(
"GET",
`/bots/${id}` as "/bots/id",
const { bot, user } = await this.client.api.get(
`/bots/${id as ''}`,
);
return {
bot,
user: await this.client.users.fetch(user._id, user),
@@ -32,7 +32,7 @@ export default class Bots {
* @param id Bot ID
*/
async delete(id: string) {
await this.client.req("DELETE", `/bots/${id}` as "/bots/id");
await this.client.api.delete(`/bots/${id}`);
}
/**
@@ -41,9 +41,8 @@ export default class Bots {
* @returns Public Bot object
*/
async fetchPublic(id: string) {
return await this.client.req(
"GET",
`/bots/${id}/invite` as "/bots/id/invite",
return await this.client.api.get(
`/bots/${id}/invite`,
);
}
@@ -54,11 +53,10 @@ export default class Bots {
*/
async invite(
id: string,
destination: Route<"POST", "/bots/id/invite">["data"],
destination: InviteBotDestination,
) {
return await this.client.req(
"POST",
`/bots/${id}/invite` as "/bots/id/invite",
return await this.client.api.post(
`/bots/${id}/invite`,
destination,
);
}
@@ -69,10 +67,9 @@ export default class Bots {
* @returns Bot and User objects
*/
async fetchOwned() {
const { bots, users: userObjects } = await this.client.req(
"GET",
const { bots, users: userObjects } = await this.client.api.get(
`/bots/@me`,
);
) as OwnedBotsResponse;
const users = [];
for (const obj of userObjects) {
@@ -87,8 +84,8 @@ export default class Bots {
* @param id Bot ID
* @param data Bot edit data object
*/
async edit(id: string, data: Route<"PATCH", "/bots/id">["data"]) {
await this.client.req("PATCH", `/bots/${id}` as "/bots/id", data);
async edit(id: string, data: DataEditBot) {
await this.client.api.patch(`/bots/${id}`, data);
if (data.name) {
const user = this.client.users.get(id);
@@ -104,8 +101,8 @@ export default class Bots {
* Create a bot
* @param data Bot creation data
*/
async create(data: Route<"POST", "/bots/create">["data"]) {
const bot = await this.client.req("POST", "/bots/create", data);
async create(data: DataCreateBot) {
const bot = await this.client.api.post("/bots/create", data);
const user = await this.client.users.fetch(bot._id, {
_id: bot._id,
username: data.name,
+71 -80
View File
@@ -1,11 +1,15 @@
import type {
Channel as ChannelI,
DataCreateGroup,
DataEditChannel,
DataMessageSend,
FieldsChannel,
Message as MessageI,
} from "revolt-api/types/Channels";
import type { RemoveChannelField, Route } from "../api/routes";
import type { Attachment } from "revolt-api/types/Autumn";
import type { Member } from "revolt-api/types/Servers";
import type { User } from "revolt-api/types/Users";
OptionsMessageSearch,
} from "revolt-api";
import type { File } from "revolt-api";
import type { Member } from "revolt-api";
import type { User } from "revolt-api";
import { action, computed, makeAutoObservable, runInAction } from "mobx";
import isEqual from "lodash.isequal";
@@ -21,7 +25,8 @@ import {
Permission, UserPermission
} from "../api/permissions";
import { INotificationChecker } from "../util/Unreads";
import { Override, OverrideField } from "revolt-api/types/_common";
import { Override, OverrideField } from "revolt-api";
import { APIRoutes } from "revolt-api/dist/routes";
export class Channel {
client: Client;
@@ -75,7 +80,7 @@ export class Channel {
* Channel icon.
* @requires `Group`, `TextChannel`, `VoiceChannel`
*/
icon: Nullable<Attachment> = null;
icon: Nullable<File> = null;
/**
* Channel description.
@@ -279,7 +284,7 @@ export class Channel {
});
}
@action update(data: Partial<ChannelI>, clear?: RemoveChannelField) {
@action update(data: Partial<ChannelI>, clear: FieldsChannel[] = []) {
const apply = (key: string, target?: string) => {
if (
// @ts-expect-error TODO: clean up types here
@@ -292,13 +297,15 @@ export class Channel {
}
};
switch (clear) {
case "Description":
this.description = null;
break;
case "Icon":
this.icon = null;
break;
for (const entry of clear) {
switch (entry) {
case "Description":
this.description = null;
break;
case "Icon":
this.icon = null;
break;
}
}
apply("active");
@@ -338,24 +345,19 @@ export class Channel {
* @returns An array of the channel's members.
*/
async fetchMembers() {
const members = await this.client.req(
"GET",
`/channels/${this._id}/members` as "/channels/id/members",
const members = await this.client.api.get(
`/channels/${this._id as ''}/members`
);
return members.map(this.client.users.createObj);
}
/**
* Edit a channel
* @param data Channel editing route data
* @param data Edit data
*/
async edit(data: Route<"PATCH", "/channels/id">["data"]) {
return await this.client.req(
"PATCH",
`/channels/${this._id}` as "/channels/id",
data,
);
// ! FIXME: return $set in req
async edit(data: DataEditChannel) {
this.update(await this.client.api.patch(`/channels/${this._id}`, data));
}
/**
@@ -364,9 +366,8 @@ export class Channel {
*/
async delete(avoidReq?: boolean) {
if (!avoidReq)
await this.client.req(
"DELETE",
`/channels/${this._id}` as "/channels/id",
await this.client.api.delete(
`/channels/${this._id}`
);
runInAction(() => {
@@ -396,9 +397,8 @@ export class Channel {
* @param user_id ID of the target user
*/
async addMember(user_id: string) {
return await this.client.req(
"PUT",
`/channels/${this._id}/recipients/${user_id}` as "/channels/id/recipients/id",
return await this.client.api.put(
`/channels/${this._id}/recipients/${user_id as ''}`
);
}
@@ -407,9 +407,8 @@ export class Channel {
* @param user_id ID of the target user
*/
async removeMember(user_id: string) {
return await this.client.req(
"DELETE",
`/channels/${this._id}/recipients/${user_id}` as "/channels/id/recipients/id",
return await this.client.api.delete(
`/channels/${this._id}/recipients/${user_id as ''}`
);
}
@@ -421,20 +420,20 @@ export class Channel {
async sendMessage(
data:
| string
| (Omit<Route<"POST", "/channels/id/messages">["data"], "nonce"> & {
| (Omit<DataMessageSend, "nonce"> & {
nonce?: string;
}),
) {
const msg: Route<"POST", "/channels/id/messages">["data"] = {
const msg: DataMessageSend = {
nonce: ulid(),
...(typeof data === "string" ? { content: data } : data),
};
const message = await this.client.req(
"POST",
`/channels/${this._id}/messages` as "/channels/id/messages",
const message = await this.client.api.post(
`/channels/${this._id as ''}/messages`,
msg,
);
return this.client.messages.createObj(message, true);
}
@@ -444,10 +443,10 @@ export class Channel {
* @returns The message
*/
async fetchMessage(message_id: string) {
const message = await this.client.req(
"GET",
`/channels/${this._id}/messages/${message_id}` as "/channels/id/messages/id",
const message = await this.client.api.get(
`/channels/${this._id as ''}/messages/${message_id as ''}`,
);
return this.client.messages.createObj(message);
}
@@ -458,14 +457,13 @@ export class Channel {
*/
async fetchMessages(
params?: Omit<
Route<"GET", "/channels/id/messages">["data"],
(APIRoutes & { method: 'get', path: '/channels/{target}/messages' })['params'],
"include_users"
>,
) {
const messages = (await this.client.request(
"GET",
`/channels/${this._id}/messages` as "/channels/id/messages",
{ params },
const messages = (await this.client.api.get(
`/channels/${this._id}/messages`,
{ ...params },
)) as MessageI[];
return runInAction(() => messages.map(this.client.messages.createObj));
}
@@ -477,14 +475,13 @@ export class Channel {
*/
async fetchMessagesWithUsers(
params?: Omit<
Route<"GET", "/channels/id/messages">["data"],
(APIRoutes & { method: 'get', path: '/channels/{target}/messages' })['params'],
"include_users"
>,
) {
const data = (await this.client.request(
"GET",
`/channels/${this._id}/messages` as "/channels/id/messages",
{ params: { ...params, include_users: true } },
const data = (await this.client.api.get(
`/channels/${this._id}/messages`,
{ ...params, include_users: true },
)) as { messages: MessageI[]; users: User[]; members?: Member[] };
return runInAction(() => {
return {
@@ -502,13 +499,12 @@ export class Channel {
*/
async search(
params: Omit<
Route<"POST", "/channels/id/search">["data"],
OptionsMessageSearch,
"include_users"
>,
) {
const messages = (await this.client.req(
"POST",
`/channels/${this._id}/search` as "/channels/id/search",
const messages = (await this.client.api.post(
`/channels/${this._id}/search`,
params,
)) as MessageI[];
return runInAction(() => messages.map(this.client.messages.createObj));
@@ -521,13 +517,12 @@ export class Channel {
*/
async searchWithUsers(
params: Omit<
Route<"POST", "/channels/id/search">["data"],
OptionsMessageSearch,
"include_users"
>,
) {
const data = (await this.client.req(
"POST",
`/channels/${this._id}/search` as "/channels/id/search",
const data = (await this.client.api.post(
`/channels/${this._id}/search`,
{ ...params, include_users: true },
)) as { messages: MessageI[]; users: User[]; members?: Member[] };
return runInAction(() => {
@@ -545,9 +540,8 @@ export class Channel {
* @returns The stale messages
*/
async fetchStale(ids: string[]) {
const data = await this.client.req(
"POST",
`/channels/${this._id}/messages/stale` as "/channels/id/messages/stale",
/*const data = await this.client.api.post(
`/channels/${this._id}/messages/stale`,
{ ids },
);
@@ -558,7 +552,8 @@ export class Channel {
);
});
return data;
return data;*/
return { deprecated: ids };
}
/**
@@ -566,9 +561,8 @@ export class Channel {
* @returns Newly created invite code
*/
async createInvite() {
return await this.client.req(
"POST",
`/channels/${this._id}/invites` as "/channels/id/invites",
return await this.client.api.post(
`/channels/${this._id}/invites`,
);
}
@@ -577,9 +571,8 @@ export class Channel {
* @returns Join call response data
*/
async joinCall() {
return await this.client.req(
"POST",
`/channels/${this._id}/join_call` as "/channels/id/join_call",
return await this.client.api.post(
`/channels/${this._id as ''}/join_call`,
);
}
@@ -598,9 +591,8 @@ export class Channel {
ulid();
const performAck = () => {
delete this.ackLimit;
this.client.req(
"PUT",
`/channels/${this._id}/ack/${id}` as "/channels/id/ack/id",
this.client.api.put(
`/channels/${this._id}/ack/${id as ''}`,
);
};
@@ -626,9 +618,8 @@ export class Channel {
* @param permissions Permission value
*/
async setPermissions(role_id = "default", permissions: Override) {
return await this.client.req(
"PUT",
`/channels/${this._id}/permissions/${role_id}` as "/channels/id/permissions/id",
return await this.client.api.put(
`/channels/${this._id}/permissions/${role_id as ''}`,
{ permissions },
);
}
@@ -764,7 +755,7 @@ export default class Channels extends Collection<string, Channel> {
if (this.has(id)) return this.$get(id);
const res =
data ??
(await this.client.req("GET", `/channels/${id}` as "/channels/id"));
(await this.client.api.get(`/channels/${id as ''}`));
return this.createObj(res);
}
@@ -792,8 +783,8 @@ export default class Channels extends Collection<string, Channel> {
* @param data Group create route data
* @returns The newly-created group
*/
async createGroup(data: Route<"POST", "/channels/create">["data"]) {
const group = await this.client.req("POST", `/channels/create`, data);
async createGroup(data: DataCreateGroup) {
const group = await this.client.api.post(`/channels/create`, data);
return (await this.fetch(group._id, group))!;
}
}
+20 -19
View File
@@ -1,10 +1,11 @@
import type {
DataMemberEdit,
FieldsMember,
Member as MemberI,
MemberCompositeKey,
Role,
} from "revolt-api/types/Servers";
import type { RemoveMemberField, Route } from "../api/routes";
import type { Attachment } from "revolt-api/types/Autumn";
} from "revolt-api";
import type { File } from "revolt-api";
import { makeAutoObservable, runInAction, action, computed } from "mobx";
import isEqual from "lodash.isequal";
@@ -19,7 +20,7 @@ export class Member {
_id: MemberCompositeKey;
nickname: Nullable<string> = null;
avatar: Nullable<Attachment> = null;
avatar: Nullable<File> = null;
roles: Nullable<string[]> = null;
/**
@@ -50,7 +51,7 @@ export class Member {
});
}
@action update(data: Partial<MemberI>, clear?: RemoveMemberField) {
@action update(data: Partial<MemberI>, clear: FieldsMember[] = []) {
const apply = (key: string) => {
// This code has been tested.
if (
@@ -64,13 +65,15 @@ export class Member {
}
};
switch (clear) {
case "Nickname":
this.nickname = null;
break;
case "Avatar":
this.avatar = null;
break;
for (const field of clear) {
switch (field) {
case "Nickname":
this.nickname = null;
break;
case "Avatar":
this.avatar = null;
break;
}
}
apply("nickname");
@@ -83,10 +86,9 @@ export class Member {
* @param data Member editing route data
* @returns Server member object
*/
async edit(data: Route<"PATCH", "/servers/id/members/id">["data"]) {
return await this.client.req(
"PATCH",
`/servers/${this._id.server}/members/${this._id.user}` as "/servers/id/members/id",
async edit(data: DataMemberEdit) {
return await this.client.api.patch(
`/servers/${this._id.server as ''}/members/${this._id.user}`,
data,
);
}
@@ -96,9 +98,8 @@ export class Member {
* @param user_id User ID
*/
async kick() {
return await this.client.req(
"DELETE",
`/servers/${this._id.server}/members/${this._id.user}` as "/servers/id/members/id",
return await this.client.api.delete(
`/servers/${this._id.server}/members/${this._id.user as ''}`,
);
}
+13 -14
View File
@@ -1,11 +1,12 @@
import type {
DataEditMessage,
DataMessageSend,
Embed,
Masquerade,
Message as MessageI,
SystemMessage,
} from "revolt-api/types/Channels";
import type { Attachment } from "revolt-api/types/Autumn";
import type { Route } from "../api/routes";
} from "revolt-api";
import type { File } from "revolt-api";
import { makeAutoObservable, runInAction, action, computed } from "mobx";
import isEqual from "lodash.isequal";
@@ -24,7 +25,7 @@ export class Message {
author_id: string;
content: string | SystemMessage;
attachments: Nullable<Attachment[]>;
attachments: Nullable<File[]>;
edited: Nullable<Date>;
embeds: Nullable<Embed[]>;
mention_ids: Nullable<string[]>;
@@ -113,13 +114,13 @@ export class Message {
constructor(client: Client, data: MessageI) {
this.client = client;
this._id = data._id;
this.nonce = data.nonce;
this.nonce = data.nonce ?? undefined;
this.channel_id = data.channel;
this.author_id = data.author;
this.content = data.content;
this.attachments = toNullable(data.attachments);
this.edited = toNullableDate(data.edited);
this.edited = toNullableDate(data.edited as any);
this.embeds = toNullable(data.embeds);
this.mention_ids = toNullable(data.mentions);
this.reply_ids = toNullable(data.replies);
@@ -164,10 +165,9 @@ export class Message {
* Edit a message
* @param data Message edit route data
*/
async edit(data: Route<"PATCH", "/channels/id/messages/id">["data"]) {
return await this.client.req(
"PATCH",
`/channels/${this.channel_id}/messages/${this._id}` as "/channels/id/messages/id",
async edit(data: DataEditMessage) {
return await this.client.api.patch(
`/channels/${this.channel_id}/messages/${this._id as ''}`,
data,
);
}
@@ -176,9 +176,8 @@ export class Message {
* Delete a message
*/
async delete() {
return await this.client.req(
"DELETE",
`/channels/${this.channel_id}/messages/${this._id}` as "/channels/id/messages/id",
return await this.client.api.delete(
`/channels/${this.channel_id}/messages/${this._id as ''}`,
);
}
@@ -195,7 +194,7 @@ export class Message {
reply(
data:
| string
| (Omit<Route<"POST", "/channels/id/messages">["data"], "nonce"> & {
| (Omit<DataMessageSend, "nonce"> & {
nonce?: string;
}),
mention = true,
+62 -75
View File
@@ -1,12 +1,17 @@
import type {
Category,
DataBanCreate,
DataCreateChannel,
DataCreateServer,
DataEditRole,
DataEditServer,
FieldsServer,
Member,
Role,
Server as ServerI,
SystemMessageChannels,
} from "revolt-api/types/Servers";
import type { RemoveServerField, Route } from "../api/routes";
import type { Attachment } from "revolt-api/types/Autumn";
} from "revolt-api";
import type { File } from "revolt-api";
import { makeAutoObservable, action, runInAction, computed } from "mobx";
import isEqual from "lodash.isequal";
@@ -18,7 +23,7 @@ import { User } from "./Users";
import { Client, FileArgs } from "..";
import { decodeTime } from "ulid";
import { INotificationChecker } from "../util/Unreads";
import { Override } from "revolt-api/types/_common";
import { Override } from "revolt-api";
import Long from "long";
export class Server {
@@ -36,8 +41,8 @@ export class Server {
roles: Nullable<{ [key: string]: Role }> = null;
default_permissions: number;
icon: Nullable<Attachment> = null;
banner: Nullable<Attachment> = null;
icon: Nullable<File> = null;
banner: Nullable<File> = null;
nsfw: Nullable<boolean> = null;
flags: Nullable<number> = null;
@@ -126,7 +131,7 @@ export class Server {
});
}
@action update(data: Partial<ServerI>, clear?: RemoveServerField) {
@action update(data: Partial<ServerI>, clear: FieldsServer[] = []) {
const apply = (key: string, target?: string) => {
// This code has been tested.
if (
@@ -140,16 +145,18 @@ export class Server {
}
};
switch (clear) {
case "Banner":
this.banner = null;
break;
case "Description":
this.description = null;
break;
case "Icon":
this.icon = null;
break;
for (const entry of clear) {
switch (entry) {
case "Banner":
this.banner = null;
break;
case "Description":
this.description = null;
break;
case "Icon":
this.icon = null;
break;
}
}
apply("owner");
@@ -171,10 +178,9 @@ export class Server {
* @param data Channel create route data
* @returns The newly-created channel
*/
async createChannel(data: Route<"POST", "/servers/id/channels">["data"]) {
return await this.client.req(
"POST",
`/servers/${this._id}/channels` as "/servers/id/channels",
async createChannel(data: DataCreateChannel) {
return await this.client.api.post(
`/servers/${this._id}/channels`,
data,
);
}
@@ -183,10 +189,9 @@ export class Server {
* Edit a server
* @param data Server editing route data
*/
async edit(data: Route<"PATCH", "/servers/id">["data"]) {
return await this.client.req(
"PATCH",
`/servers/${this._id}` as "/servers/id",
async edit(data: DataEditServer) {
return await this.client.api.patch(
`/servers/${this._id}`,
data,
);
}
@@ -196,9 +201,8 @@ export class Server {
*/
async delete(avoidReq?: boolean) {
if (!avoidReq)
await this.client.req(
"DELETE",
`/servers/${this._id}` as "/servers/id",
await this.client.api.delete(
`/servers/${this._id}`,
);
runInAction(() => {
@@ -210,9 +214,8 @@ export class Server {
* Mark a server as read
*/
async ack() {
return await this.client.req(
"PUT",
`/servers/${this._id}/ack` as "/servers/id/ack",
return await this.client.api.put(
`/servers/${this._id}/ack`,
);
}
@@ -222,11 +225,10 @@ export class Server {
*/
async banUser(
user_id: string,
data: Route<"PUT", "/servers/id/bans/id">["data"],
data: DataBanCreate,
) {
return await this.client.req(
"PUT",
`/servers/${this._id}/bans/${user_id}` as "/servers/id/bans/id",
return await this.client.api.put(
`/servers/${this._id as ''}/bans/${user_id}`,
data,
);
}
@@ -236,9 +238,8 @@ export class Server {
* @param user_id User ID
*/
async unbanUser(user_id: string) {
return await this.client.req(
"DELETE",
`/servers/${this._id}/bans/${user_id}` as "/servers/id/bans/id",
return await this.client.api.delete(
`/servers/${this._id as ''}/bans/${user_id}`,
);
}
@@ -247,9 +248,8 @@ export class Server {
* @returns An array of the server's invites
*/
async fetchInvites() {
return await this.client.req(
"GET",
`/servers/${this._id}/invites` as "/servers/id/invites",
return await this.client.api.get(
`/servers/${this._id}/invites`,
);
}
@@ -258,9 +258,8 @@ export class Server {
* @returns An array of the server's bans.
*/
async fetchBans() {
return await this.client.req(
"GET",
`/servers/${this._id}/bans` as "/servers/id/bans",
return await this.client.api.get(
`/servers/${this._id}/bans`
);
}
@@ -273,10 +272,9 @@ export class Server {
role_id = "default",
permissions: Override | number
) {
return await this.client.req(
"PUT",
`/servers/${this._id}/permissions/${role_id}` as "/servers/id/permissions/id",
{ permissions },
return await this.client.api.put(
`/servers/${this._id}/permissions/${role_id as ''}`,
{ permissions: permissions as Override },
);
}
@@ -285,9 +283,8 @@ export class Server {
* @param name Role name
*/
async createRole(name: string) {
return await this.client.req(
"POST",
`/servers/${this._id}/roles` as "/servers/id/roles",
return await this.client.api.post(
`/servers/${this._id}/roles`,
{ name },
);
}
@@ -299,11 +296,10 @@ export class Server {
*/
async editRole(
role_id: string,
data: Route<"PATCH", "/servers/id/roles/id">["data"],
data: DataEditRole,
) {
return await this.client.req(
"PATCH",
`/servers/${this._id}/roles/${role_id}` as "/servers/id/roles/id",
return await this.client.api.patch(
`/servers/${this._id}/roles/${role_id as ''}`,
data,
);
}
@@ -313,9 +309,8 @@ export class Server {
* @param role_id Role ID
*/
async deleteRole(role_id: string) {
return await this.client.req(
"DELETE",
`/servers/${this._id}/roles/${role_id}` as "/servers/id/roles/id",
return await this.client.api.delete(
`/servers/${this._id}/roles/${role_id as ''}`,
);
}
@@ -332,9 +327,8 @@ export class Server {
});
if (existing) return existing;
const member = await this.client.req(
"GET",
`/servers/${this._id}/members/${user_id}` as "/servers/id/members/id",
const member = await this.client.api.get(
`/servers/${this._id as ''}/members/${user_id as ''}`,
);
return this.client.members.createObj(member);
}
@@ -344,16 +338,10 @@ export class Server {
* ! OPTIMISATION
*/
async syncMembers(skipOffline?: boolean) {
const data = await this.client.req(
"GET",
`/servers/${this._id}/members` as "/servers/id/members",
const data = await this.client.api.get(
`/servers/${this._id as ''}/members`,
);
// ! FIXME: if we do this on the server, we can save 7ms locally
// 7ms to sort both lists.
// data.users.sort((a,b)=>b._id.localeCompare(a._id));
// data.members.sort((a,b)=>b._id.user.localeCompare(a._id.user));
// This takes roughly 23ms.
runInAction(() => {
// This adds roughly 15ms to 23ms above.
@@ -396,9 +384,8 @@ export class Server {
* @returns An array of the server's members and their user objects.
*/
async fetchMembers() {
const data = await this.client.req(
"GET",
`/servers/${this._id}/members` as "/servers/id/members",
const data = await this.client.api.get(
`/servers/${this._id as ''}/members`,
);
// Note: this takes 986 ms (Testers server)
@@ -483,7 +470,7 @@ export default class Servers extends Collection<string, Server> {
if (this.has(id)) return this.$get(id, data);
const res =
data ??
(await this.client.req("GET", `/servers/${id}` as "/servers/id"));
(await this.client.api.get(`/servers/${id as ''}`));
return runInAction(async () => {
for (const channel of res.channels) {
@@ -521,8 +508,8 @@ export default class Servers extends Collection<string, Server> {
* @param data Server create route data
* @returns The newly-created server
*/
async createServer(data: Route<"POST", "/servers/create">["data"]) {
const server = await this.client.req("POST", `/servers/create`, data);
async createServer(data: DataCreateServer) {
const server = await this.client.api.post(`/servers/create`, data);
return this.fetch(server._id, server);
}
}
+36 -63
View File
@@ -1,10 +1,12 @@
import type {
BotInformation,
Status,
UserStatus,
User as UserI,
} from "revolt-api/types/Users";
import type { RemoveUserField, Route } from "../api/routes";
import type { Attachment } from "revolt-api/types/Autumn";
RelationshipStatus,
FieldsUser,
DataEditUser,
} from "revolt-api";
import type { File } from "revolt-api";
import { makeAutoObservable, action, runInAction, computed } from "mobx";
import isEqual from "lodash.isequal";
@@ -16,25 +18,15 @@ import { Client, FileArgs } from "..";
import _ from "lodash";
import { decodeTime } from "ulid";
enum RelationshipStatus {
None = "None",
User = "User",
Friend = "Friend",
Outgoing = "Outgoing",
Incoming = "Incoming",
Blocked = "Blocked",
BlockedOther = "BlockedOther",
}
export class User {
client: Client;
_id: string;
username: string;
avatar: Nullable<Attachment>;
avatar: Nullable<File>;
badges: Nullable<number>;
status: Nullable<Status>;
status: Nullable<UserStatus>;
relationship: Nullable<RelationshipStatus>;
online: Nullable<boolean>;
flags: Nullable<number>;
@@ -67,7 +59,7 @@ export class User {
});
}
@action update(data: Partial<UserI>, clear?: RemoveUserField) {
@action update(data: Partial<UserI>, clear: FieldsUser[] = []) {
const apply = (key: string) => {
// This code has been tested.
if (
@@ -85,13 +77,15 @@ export class User {
}
};
switch (clear) {
case "Avatar":
this.avatar = null;
break;
case "StatusText": {
if (this.status) {
this.status.text = undefined;
for (const entry of clear) {
switch (entry) {
case "Avatar":
this.avatar = null;
break;
case "StatusText": {
if (this.status) {
this.status.text = undefined;
}
}
}
}
@@ -114,11 +108,7 @@ export class User {
let dm = [...this.client.channels.values()].find(x => x.channel_type === 'DirectMessage' && x.recipient == this);
if (!dm) {
const data = await this.client.req(
"GET",
`/users/${this._id}/dm` as "/users/id/dm",
);
const data = await this.client.api.get(`/users/${this._id as ''}/dm`);
dm = await this.client.channels.fetch(data._id, data)!;
}
@@ -133,40 +123,28 @@ export class User {
* Send a friend request to a user
*/
async addFriend() {
await this.client.req(
"PUT",
`/users/${this.username}/friend` as "/users/id/friend",
);
await this.client.api.put(`/users/${this.username as ''}/friend`);
}
/**
* Remove a user from the friend list
*/
async removeFriend() {
await this.client.req(
"DELETE",
`/users/${this._id}/friend` as "/users/id/friend",
);
await this.client.api.delete(`/users/${this._id}/friend`);
}
/**
* Block a user
*/
async blockUser() {
await this.client.req(
"PUT",
`/users/${this._id}/block` as "/users/id/block",
);
await this.client.api.put(`/users/${this._id}/block`);
}
/**
* Unblock a user
*/
async unblockUser() {
await this.client.req(
"DELETE",
`/users/${this._id}/block` as "/users/id/block",
);
await this.client.api.delete(`/users/${this._id}/block`);
}
/**
@@ -174,10 +152,7 @@ export class User {
* @returns The profile of the user
*/
async fetchProfile() {
return await this.client.req(
"GET",
`/users/${this._id}/profile` as "/users/id/profile",
);
return await this.client.api.get(`/users/${this._id}/profile`);
}
/**
@@ -185,10 +160,7 @@ export class User {
* @returns The mutual connections of the current user and a target user
*/
async fetchMutual() {
return await this.client.req(
"GET",
`/users/${this._id}/mutual` as "/users/id/mutual",
);
return await this.client.api.get(`/users/${this._id}/mutual`);
}
/**
@@ -208,14 +180,14 @@ export class User {
@computed get permission() {
let permissions = 0;
switch (this.relationship) {
case RelationshipStatus.Friend:
case RelationshipStatus.User:
case 'Friend':
case 'User':
return U32_MAX;
case RelationshipStatus.Blocked:
case RelationshipStatus.BlockedOther:
case 'Blocked':
case 'BlockedOther':
return UserPermission.Access;
case RelationshipStatus.Incoming:
case RelationshipStatus.Outgoing:
case 'Incoming':
case 'Outgoing':
permissions = UserPermission.Access;
}
@@ -265,7 +237,8 @@ export default class Users extends Collection<string, User> {
if (this.has(id)) return this.$get(id, data);
const res =
data ??
(await this.client.req("GET", `/users/${id}` as "/users/id"));
(await this.client.api.get(`/users/${id as ''}`));
return this.createObj(res);
}
@@ -291,8 +264,8 @@ export default class Users extends Collection<string, User> {
* Edit the current user
* @param data User edit data object
*/
async edit(data: Route<"PATCH", "/users/@me">["data"]) {
await this.client.req("PATCH", "/users/@me", data);
async edit(data: DataEditUser) {
await this.client.api.patch("/users/@me", data);
}
/**
@@ -301,7 +274,7 @@ export default class Users extends Collection<string, User> {
* @param password Current password
*/
async changeUsername(username: string, password: string) {
return await this.client.req("PATCH", "/users/@me/username", {
return await this.client.api.patch("/users/@me/username", {
username,
password,
});
+10 -18
View File
@@ -18,7 +18,6 @@ async function user() {
const group = await client.channels.createGroup({
name: 'sussy',
nonce: ''+Math.random(),
users: []
});
@@ -26,14 +25,13 @@ async function user() {
content: "embed test",
embeds: [
{
type: 'Text',
title: 'We do a little!'
}
]
});
await msg.edit({
embeds: [{ type: 'Text', title: 'sus' }]
embeds: [{ title: 'sus' }]
});
});
@@ -41,38 +39,32 @@ async function user() {
if (message.content === "sus") {
message.channel!.sendMessage("sus!");
} else if (message.content === "bot") {
const bot = await client.req("POST", "/bots/create", {
const bot = await client.api.post("/bots/create", {
name: "basedbot12",
});
message.channel!.sendMessage(JSON.stringify(bot));
} else if (message.content === "my bots") {
message.channel!.sendMessage(
JSON.stringify(await client.req("GET", "/bots/@me")),
JSON.stringify(await client.api.get("/bots/@me")),
);
} else if (message.content === "join bot") {
await client.req(
"POST",
`/bots/01FCV7DCMRD9MT3JBYT5VEKVRD/invite` as "/bots/id/invite",
await client.api.post(
`/bots/${'01FCV7DCMRD9MT3JBYT5VEKVRD'}/invite`,
{ group: message.channel_id },
);
// { server: '01FATEGMHEE2M1QGPA65NS6V8K' });
} else if (message.content === "edit bot name") {
await client.req(
"PATCH",
`/bots/01FCV7DCMRD9MT3JBYT5VEKVRD` as "/bots/id",
await client.api.patch(
`/bots/${'01FCV7DCMRD9MT3JBYT5VEKVRD'}`,
{ name: "testingbkaka" },
);
} else if (message.content === "make bot public") {
await client.req(
"PATCH",
`/bots/01FCV7DCMRD9MT3JBYT5VEKVRD` as "/bots/id",
await client.api.patch(
`/bots/${'01FCV7DCMRD9MT3JBYT5VEKVRD'}`,
{ public: true },
);
} else if (message.content === "delete bot") {
await client.req(
"DELETE",
`/bots/01FCV7DCMRD9MT3JBYT5VEKVRD` as "/bots/id",
);
await client.api.delete(`/bots/${'01FCV7DCMRD9MT3JBYT5VEKVRD'}`);
}
});
+1 -1
View File
@@ -6,7 +6,7 @@ import {
ObservableMap,
runInAction,
} from "mobx";
import type { ChannelUnread } from "revolt-api/types/Sync";
import type { ChannelUnread } from "revolt-api";
import { ulid } from "ulid";
import { Channel } from "../maps/Channels";
import { Server } from "../maps/Servers";
+1 -1
View File
@@ -1,7 +1,7 @@
import { backOff } from "@insertish/exponential-backoff";
import WebSocket from "@insertish/isomorphic-ws";
import { runInAction } from "mobx";
import { Role } from "revolt-api/types/Servers";
import { Role } from "revolt-api";
import { Client } from "..";
import {
+9 -17
View File
@@ -1,20 +1,12 @@
import type { Session } from "revolt-api/types/Auth";
import type { Channel, Message } from "revolt-api/types/Channels";
import type { FieldsChannel, FieldsMember, FieldsServer, FieldsUser, Session } from "revolt-api";
import type { Channel, Message } from "revolt-api";
import type {
Member,
MemberCompositeKey,
Role,
Server,
} from "revolt-api/types/Servers";
import type { UserSettings } from "revolt-api/types/Sync";
import type { RelationshipStatus, User } from "revolt-api/types/Users";
import type {
RemoveChannelField,
RemoveServerField,
RemoveUserField,
RemoveMemberField,
} from "../api/routes";
} from "revolt-api";
import type { RelationshipStatus, User } from "revolt-api";
type WebSocketError = {
error:
@@ -60,7 +52,7 @@ export type ClientboundNotification =
type: "ChannelUpdate";
id: string;
data: Partial<Channel>;
clear?: RemoveChannelField;
clear?: FieldsChannel[];
}
| { type: "ChannelDelete"; id: string }
| { type: "ChannelGroupJoin"; id: string; user: string }
@@ -72,14 +64,14 @@ export type ClientboundNotification =
type: "ServerUpdate";
id: string;
data: Partial<Server>;
clear?: RemoveServerField;
clear?: FieldsServer[];
}
| { type: "ServerDelete"; id: string }
| {
type: "ServerMemberUpdate";
id: MemberCompositeKey;
data: Partial<Member>;
clear?: RemoveMemberField;
clear?: FieldsMember[];
}
| { type: "ServerMemberJoin"; id: string; user: string }
| { type: "ServerMemberLeave"; id: string; user: string }
@@ -94,8 +86,8 @@ export type ClientboundNotification =
type: "UserUpdate";
id: string;
data: Partial<User>;
clear?: RemoveUserField;
clear?: FieldsUser[];
}
| { type: "UserRelationship"; user: User; status: RelationshipStatus }
| { type: "UserPresence"; id: string; online: boolean }
| { type: "UserSettingsUpdate"; id: string; update: UserSettings };
| { type: "UserSettingsUpdate"; id: string; update: { [key: string]: [number, string] } };
+85 -4
View File
@@ -41,6 +41,16 @@
resolved "https://registry.yarnpkg.com/@insertish/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#5bcd6f73b93efa9ccdb6abf887ae808d40827169"
integrity sha512-kFD/p8T4Hkqr992QrdkbW/cQ/W/q2d9MPCobwzBv2PwTKLkCD9RaYDy6m17qRnSLQQ5PU0kHCG8kaOwAqzj1vQ==
"@insertish/oapi@0.1.13":
version "0.1.13"
resolved "https://registry.yarnpkg.com/@insertish/oapi/-/oapi-0.1.13.tgz#92579172bd6896152e9b68ef5bafcf8cc2048492"
integrity sha512-yj1Jk3VlNCjp0hNOsknevyR7PwVzSzc5pbUTuyhtyPlbuHRTv1b9DWs8Ki7xbF/mrIy2DzkHoUu4Ik/Q4oS93w==
dependencies:
typescript "^4.6.2"
optionalDependencies:
axios "^0.26.1"
openapi-typescript "^5.2.0"
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
@@ -234,6 +244,13 @@ axios@^0.21.4:
dependencies:
follow-redirects "^1.14.0"
axios@^0.26.1:
version "0.26.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
dependencies:
follow-redirects "^1.14.8"
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
@@ -539,6 +556,11 @@ follow-redirects@^1.14.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685"
integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==
follow-redirects@^1.14.8:
version "1.14.9"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
from@~0:
version "0.1.7"
resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe"
@@ -599,6 +621,11 @@ globals@^13.6.0, globals@^13.9.0:
dependencies:
type-fest "^0.20.2"
globalyzer@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465"
integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==
globby@^11.0.4:
version "11.0.4"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"
@@ -611,6 +638,11 @@ globby@^11.0.4:
merge2 "^1.3.0"
slash "^3.0.0"
globrex@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
handlebars@^4.7.7:
version "4.7.7"
resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
@@ -815,6 +847,11 @@ micromatch@^4.0.4:
braces "^3.0.1"
picomatch "^2.2.3"
mime@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
minimatch@^3.0.0, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -871,6 +908,18 @@ onigasm@^2.2.5:
dependencies:
lru-cache "^5.1.1"
openapi-typescript@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-5.2.0.tgz#c0712d7a17e4502ac083162c42f946c0e966a505"
integrity sha512-EGoPTmxrpiN40R6An5Wqol2l74sRb773pv/KXWYCQaMCXNtMGN7Jv+y/jY4B1Bd4hsIW2j9GFmQXxqfGmOXaxA==
dependencies:
js-yaml "^4.1.0"
mime "^3.0.0"
prettier "^2.5.1"
tiny-glob "^0.2.9"
undici "^4.14.1"
yargs-parser "^21.0.0"
optionator@^0.9.1:
version "0.9.1"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
@@ -927,6 +976,11 @@ prettier@^2.4.1:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c"
integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==
prettier@^2.5.1:
version "2.6.0"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.0.tgz#12f8f504c4d8ddb76475f441337542fa799207d4"
integrity sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==
progress@^2.0.0, progress@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
@@ -964,10 +1018,14 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
revolt-api@0.5.3-alpha.12:
version "0.5.3-alpha.12"
resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-alpha.12.tgz#78f25b567b840c1fd072595526592a422cb01f25"
integrity sha512-MM42oI5+5JJMnAs3JiOwSQOy/SUYzYs3M8YRC5QI4G6HU7CfyB2HNWh5jFsyRlcLdSi13dGazHm31FUPHsxOzw==
revolt-api@0.5.3-rc.7:
version "0.5.3-rc.7"
resolved "https://registry.yarnpkg.com/revolt-api/-/revolt-api-0.5.3-rc.7.tgz#eed82fcd014a25a4d080c4502c0ae4aa283adddb"
integrity sha512-72SJ+P19nRRD+xE8bXAV8mFZNBgtKkXogQACDvNSIdATUmszYl91YRPqX+UQ1Oz4ePgKFwyWPxrnmfede7Q83g==
dependencies:
"@insertish/oapi" "0.1.13"
axios "^0.26.1"
lodash.defaultsdeep "^4.6.1"
rimraf@^3.0.2:
version "3.0.2"
@@ -1076,6 +1134,14 @@ through@2, through@~2.3, through@~2.3.1:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
tiny-glob@^0.2.9:
version "0.2.9"
resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2"
integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==
dependencies:
globalyzer "0.1.0"
globrex "^0.1.2"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -1142,6 +1208,11 @@ typescript@^4.4.4:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
typescript@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4"
integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==
uglify-js@^3.1.4:
version "3.13.10"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.10.tgz#a6bd0d28d38f592c3adb6b180ea6e07e1e540a8d"
@@ -1152,6 +1223,11 @@ ulid@^2.3.0:
resolved "https://registry.yarnpkg.com/ulid/-/ulid-2.3.0.tgz#93063522771a9774121a84d126ecd3eb9804071f"
integrity sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==
undici@^4.14.1:
version "4.16.0"
resolved "https://registry.yarnpkg.com/undici/-/undici-4.16.0.tgz#469bb87b3b918818d3d7843d91a1d08da357d5ff"
integrity sha512-tkZSECUYi+/T1i4u+4+lwZmQgLXd4BLGlrc7KZPcLIW7Jpq99+Xpc30ONv7nS6F5UNOxp/HBZSSL9MafUrvJbw==
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@@ -1205,3 +1281,8 @@ yallist@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yargs-parser@^21.0.0:
version "21.0.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35"
integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==