mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-02 04:56:07 +00:00
1.3.1
This commit is contained in:
parent
4b8c59bcff
commit
93f40d22e9
@ -1,5 +1,7 @@
|
||||
# @biscuitland/api-types
|
||||
|
||||
## Most importantly, api-types is:
|
||||
|
||||
1:1 type definitions package for the [Discord](https://discord.com/developers/docs/intro) API.
|
||||
|
||||
[<img src="https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white">](https://github.com/oasisjs/biscuit)
|
||||
@ -23,7 +25,7 @@ import type { DiscordUser } from '@biscuitland/api-types';
|
||||
## Example for [Deno](https://deno.land/)
|
||||
|
||||
```ts
|
||||
import type { DiscordUser } from "https://unpkg.com/@biscuitland/api-types@1.2.0/dist/index.d.ts";
|
||||
import type { DiscordUser } from "https://unpkg.com/@biscuitland/api-types@1.3.1/dist/index.d.ts";
|
||||
```
|
||||
|
||||
We deliver this package through [unpkg](https://unpkg.com/) and it does contain constants and routes too
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@biscuitland/api-types",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.1",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
|
@ -5,7 +5,7 @@ export const BASE_URL = 'https://discord.com/api';
|
||||
export const API_VERSION = 10;
|
||||
|
||||
/** https://github.com/oasisjs/biscuit/releases */
|
||||
export const BISCUIT_VERSION = '1.2.0';
|
||||
export const BISCUIT_VERSION = '1.3.1';
|
||||
|
||||
/** https://discord.com/developers/docs/reference#user-agent */
|
||||
export const USER_AGENT = `DiscordBot (https://github.com/oasisjs/biscuit, v${BISCUIT_VERSION})`;
|
||||
|
12
packages/cache/README.md
vendored
12
packages/cache/README.md
vendored
@ -1,11 +1,13 @@
|
||||
# @biscuitland/cache
|
||||
Structures to create a custom cache completely decoupled from the rest of the library. You can choose to use a `MemoryCacheAdapter` or a `RedisCacheAdapter` according to your needs.
|
||||
|
||||
In progress.
|
||||
|
||||
[<img src="https://img.shields.io/badge/GitHub-100000?style=for-the-badge&logo=github&logoColor=white">](https://github.com/oasisjs/biscuit)
|
||||
[<img src="https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white">](https://discord.gg/XNw2RZFzaP)
|
||||
|
||||
## Links
|
||||
* [Website](https://biscuitjs.com/)
|
||||
* [Documentation](https://docs.biscuitjs.com/)
|
||||
* [Discord](https://discord.gg/XNw2RZFzaP)
|
||||
* [core](https://www.npmjs.com/package/@biscuitland/core) | [api-types](https://www.npmjs.com/package/@biscuitland/api-types) | [rest](https://www.npmjs.com/package/@biscuitland/rest) | [ws](https://www.npmjs.com/package/@biscuitland/ws) | [helpers](https://www.npmjs.com/package/@biscuitland/helpers)
|
||||
|
||||
- [Website](https://biscuitjs.com/)
|
||||
- [Documentation](https://docs.biscuitjs.com/)
|
||||
- [Discord](https://discord.gg/XNw2RZFzaP)
|
||||
- [core](https://www.npmjs.com/package/@biscuitland/core) | [api-types](https://www.npmjs.com/package/@biscuitland/api-types) | [rest](https://www.npmjs.com/package/@biscuitland/rest) | [ws](https://www.npmjs.com/package/@biscuitland/ws) | [helpers](https://www.npmjs.com/package/@biscuitland/helpers)
|
||||
|
4
packages/cache/package.json
vendored
4
packages/cache/package.json
vendored
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@biscuitland/cache",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.1",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
@ -23,7 +23,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@biscuitland/api-types": "^1.2.0",
|
||||
"@biscuitland/api-types": "^1.3.1",
|
||||
"ioredis": "^5.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
29
packages/cache/src/adapters/cache-adapter.ts
vendored
29
packages/cache/src/adapters/cache-adapter.ts
vendored
@ -3,29 +3,48 @@ export interface CacheAdapter {
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
get<T = unknown>(name: string): Promise<T | unknown>;
|
||||
get(id: string): any | Promise<any>;
|
||||
get(id: string, guild?: string): string | Promise<string>;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
set(name: string, data: unknown): Promise<void>;
|
||||
set(id: string, data: any, expire?: number): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
remove(name: string): Promise<void>;
|
||||
count(to: string): number | Promise<number>;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
clear(): Promise<void>;
|
||||
remove(id: string): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
close?(): Promise<void>;
|
||||
contains(to: string, id: string): boolean | Promise<boolean>;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
getToRelationship(to: string): string[] | Promise<string[]>;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
addToRelationship(to: string, id: string): void | Promise<void>;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
removeToRelationship(to: string, id: string): void | Promise<void>;
|
||||
}
|
||||
|
108
packages/cache/src/adapters/memory-cache-adapter.ts
vendored
108
packages/cache/src/adapters/memory-cache-adapter.ts
vendored
@ -1,51 +1,119 @@
|
||||
import { CacheAdapter } from './cache-adapter';
|
||||
import type { CacheAdapter } from './cache-adapter';
|
||||
|
||||
interface Options {
|
||||
expire?: number;
|
||||
}
|
||||
|
||||
export class MemoryCacheAdapter implements CacheAdapter {
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
private static readonly DEFAULTS = {
|
||||
};
|
||||
|
||||
private readonly data = new Map<string, any>();
|
||||
private readonly relationships = new Map<string, string[]>();
|
||||
private readonly storage = new Map<string, { data: any; expire?: number }>();
|
||||
|
||||
readonly options: Options;
|
||||
|
||||
constructor(options?: Options) {
|
||||
this.options = Object.assign(MemoryCacheAdapter.DEFAULTS, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async get<T = unknown>(name: string): Promise<T | null> {
|
||||
const data = this.data.get(name);
|
||||
get<T = any>(id: string): T | null {
|
||||
const data = this.storage.get(id);
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
if (data) {
|
||||
if (data.expire && data.expire < Date.now()) {
|
||||
this.storage.delete(id);
|
||||
} else {
|
||||
return JSON.parse(data.data);
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.parse(data);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async set(name: string, data: unknown): Promise<void> {
|
||||
const stringData = JSON.stringify(data, (_, v) =>
|
||||
typeof v === 'bigint' ? v.toString() : v
|
||||
);
|
||||
|
||||
this.data.set(name, stringData);
|
||||
set(id: string, data: any, expire = this.options.expire): void {
|
||||
if (expire) {
|
||||
this.storage.set(id, { data: JSON.stringify(data), expire: Date.now() + expire });
|
||||
} else {
|
||||
this.storage.set(id, { data: JSON.stringify(data) });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async remove(name: string): Promise<void> {
|
||||
this.data.delete(name);
|
||||
count(to: string): number {
|
||||
return this.getToRelationship(to).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async clear(): Promise<void> {
|
||||
this.data.clear();
|
||||
remove(id: string): void {
|
||||
this.storage.delete(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
contains(to: string, id: string): boolean {
|
||||
return this.getToRelationship(to).includes(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
getToRelationship(to: string): string[] {
|
||||
return this.relationships.get(to) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
addToRelationship(to: string, id: string): void {
|
||||
const data = this.getToRelationship(to);
|
||||
|
||||
if (data.includes(id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push(id);
|
||||
|
||||
const has = !!this.relationships.get(to);
|
||||
|
||||
if (!has) {
|
||||
this.relationships.set(to, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
removeToRelationship(to: string, id: string): void {
|
||||
const data = this.getToRelationship(to);
|
||||
|
||||
if (data) {
|
||||
const idx = data.indexOf(id);
|
||||
|
||||
if (idx === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,29 @@
|
||||
import type { Redis, RedisOptions } from 'ioredis';
|
||||
import type { CacheAdapter } from './cache-adapter';
|
||||
|
||||
import { CacheAdapter } from './cache-adapter';
|
||||
import Redis, { RedisOptions } from 'ioredis';
|
||||
import IORedis from 'ioredis';
|
||||
|
||||
export interface BaseOptions {
|
||||
prefix?: string;
|
||||
interface BaseOptions {
|
||||
namespace: string;
|
||||
expire?: number;
|
||||
}
|
||||
|
||||
export interface BuildOptions extends BaseOptions, RedisOptions {}
|
||||
interface BuildOptions extends BaseOptions, RedisOptions { }
|
||||
|
||||
export interface ClientOptions extends BaseOptions {
|
||||
interface ClientOptions extends BaseOptions {
|
||||
client: Redis;
|
||||
}
|
||||
|
||||
export type Options = BuildOptions | ClientOptions;
|
||||
|
||||
type Options = BuildOptions | ClientOptions;
|
||||
|
||||
export class RedisCacheAdapter implements CacheAdapter {
|
||||
static readonly DEFAULTS = {
|
||||
prefix: 'biscuitland',
|
||||
private static readonly DEFAULTS = {
|
||||
namespace: 'biscuitland'
|
||||
};
|
||||
|
||||
private readonly client: Redis;
|
||||
|
||||
options: Options;
|
||||
readonly options: Options;
|
||||
|
||||
constructor(options?: Options) {
|
||||
this.options = Object.assign(RedisCacheAdapter.DEFAULTS, options);
|
||||
@ -35,17 +36,12 @@ export class RedisCacheAdapter implements CacheAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
_getPrefix(name: string) {
|
||||
return `${this.options.prefix}:${name}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async get<T = unknown>(name: string): Promise<T | null> {
|
||||
const completKey = this._getPrefix(name);
|
||||
const data = await this.client.get(completKey);
|
||||
async get(id: string): Promise<any> {
|
||||
const data = await this.client.get(this.composite(id));
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
@ -58,38 +54,67 @@ export class RedisCacheAdapter implements CacheAdapter {
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async set(name: string, data: unknown): Promise<void> {
|
||||
const stringData = JSON.stringify(data, (_, v) =>
|
||||
typeof v === 'bigint' ? v.toString() : v
|
||||
);
|
||||
|
||||
const completeKey = this._getPrefix(name);
|
||||
|
||||
await this.client.set(completeKey, stringData);
|
||||
async set(id: string, data: unknown, expire = this.options.expire): Promise<void> {
|
||||
if (expire) {
|
||||
await this.client.set(this.composite(id), JSON.stringify(data), 'EX', expire);
|
||||
} else {
|
||||
await this.client.set(this.composite(id), JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async remove(name: string): Promise<void> {
|
||||
const completKey = this._getPrefix(name);
|
||||
await this.client.del(completKey);
|
||||
async count(_to: string): Promise<number> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async clear(): Promise<void> {
|
||||
this.client.disconnect();
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.client.del(this.composite(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async close(): Promise<void> {
|
||||
this.client.disconnect();
|
||||
async contains(_to: string, _id: string): Promise<boolean> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async getToRelationship(_to: string): Promise<string[]> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async addToRelationship(_to: string, _id: string): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async removeToRelationship(_to: string, _id: string): Promise<void> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
composite(id: string): string {
|
||||
return `${this.options.namespace}:${id}`;
|
||||
}
|
||||
}
|
215
packages/cache/src/cache.ts
vendored
Normal file
215
packages/cache/src/cache.ts
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
import { MemoryCacheAdapter } from './adapters/memory-cache-adapter';
|
||||
// import { RedisCacheAdapter } from './adapters/redis-cache-adapter';
|
||||
|
||||
import {
|
||||
ChannelResource,
|
||||
GuildEmojiResource,
|
||||
GuildMemberResource,
|
||||
GuildResource,
|
||||
GuildRoleResource,
|
||||
GuildStickerResource,
|
||||
UserResource,
|
||||
VoiceResource
|
||||
} from './resources';
|
||||
|
||||
/**
|
||||
* add options and adaptor passable by options
|
||||
* @default MemoryCacheAdapter
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add more adapters and options
|
||||
* Allow passing customizable resources and deleting resources
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add presence system (disabled by default)
|
||||
* Add TTL option (default 7 days)
|
||||
* Add permissions resource (accessible as a subResource)
|
||||
*/
|
||||
export class Cache {
|
||||
readonly channels: ChannelResource;
|
||||
|
||||
readonly emojis: GuildEmojiResource;
|
||||
readonly members: GuildMemberResource;
|
||||
readonly guilds: GuildResource;
|
||||
readonly roles: GuildRoleResource;
|
||||
readonly stickers: GuildStickerResource;
|
||||
|
||||
readonly users: UserResource;
|
||||
readonly voices: VoiceResource;
|
||||
|
||||
ready: boolean;
|
||||
|
||||
constructor() {
|
||||
this.ready = false;
|
||||
|
||||
/** this change to memory */
|
||||
const adapter = new MemoryCacheAdapter();
|
||||
|
||||
this.channels = new ChannelResource(adapter);
|
||||
|
||||
this.emojis = new GuildEmojiResource(adapter);
|
||||
this.members = new GuildMemberResource(adapter);
|
||||
|
||||
this.guilds = new GuildResource(adapter);
|
||||
this.roles = new GuildRoleResource(adapter);
|
||||
this.stickers = new GuildStickerResource(adapter);
|
||||
|
||||
this.users = new UserResource(adapter);
|
||||
this.voices = new VoiceResource(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async start(event: { t: string; d: any }) {
|
||||
switch (event.t) {
|
||||
case 'READY':
|
||||
await this.users.set(event.d.user.id, event.d.user);
|
||||
|
||||
const guilds: Array<Promise<any> | undefined> = [];
|
||||
|
||||
for (const guild of event.d.guilds) {
|
||||
guilds.push(this.guilds.set(guild.id, guild));
|
||||
}
|
||||
|
||||
await Promise.all(guilds);
|
||||
|
||||
this.ready = true;
|
||||
break;
|
||||
|
||||
case 'USER_UPDATE':
|
||||
await this.users.set(event.d.id, event.d);
|
||||
break;
|
||||
|
||||
case 'GUILD_CREATE':
|
||||
await this.guilds.set(event.d.id, event.d);
|
||||
break;
|
||||
|
||||
case 'GUILD_UPDATE':
|
||||
this.guilds.set(event.d.id, event.d);
|
||||
break;
|
||||
|
||||
case 'GUILD_DELETE':
|
||||
if (event.d.unavailable) {
|
||||
await this.guilds.set(event.d.id, event.d);
|
||||
} else {
|
||||
await this.guilds.remove(event.d.id);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'CHANNEL_CREATE':
|
||||
// modify [Add elimination system]
|
||||
await this.channels.set(event.d.id, event.d);
|
||||
break;
|
||||
|
||||
case 'CHANNEL_UPDATE':
|
||||
// modify [Add elimination system]
|
||||
await this.channels.set(event.d.id, event.d);
|
||||
break;
|
||||
|
||||
case 'CHANNEL_DELETE':
|
||||
// modify [Add elimination system]
|
||||
await this.channels.remove(event.d.id);
|
||||
break;
|
||||
|
||||
case 'GUILD_ROLE_CREATE':
|
||||
await this.roles.set(
|
||||
event.d.role.id,
|
||||
event.d.guild_id,
|
||||
event.d.role
|
||||
);
|
||||
break;
|
||||
|
||||
case 'GUILD_ROLE_UPDATE':
|
||||
await this.roles.set(
|
||||
event.d.role.id,
|
||||
event.d.guild_id,
|
||||
event.d.role
|
||||
);
|
||||
break;
|
||||
|
||||
case 'GUILD_ROLE_DELETE':
|
||||
await this.roles.remove(event.d.role.id, event.d.guild_id);
|
||||
break;
|
||||
|
||||
case 'GUILD_EMOJIS_UPDATE':
|
||||
// modify [Add elimination system]
|
||||
for (const v of event.d.emojis) {
|
||||
await this.emojis?.set(v.id, event.d.guild_id, v);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'GUILD_STICKERS_UPDATE':
|
||||
// modify [Add elimination system]
|
||||
for (const v of event.d.stickers) {
|
||||
await this.stickers?.set(v.id, event.d.guild_id, v);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'GUILD_MEMBER_ADD':
|
||||
await this.members.set(
|
||||
event.d.user.id,
|
||||
event.d.guild_id,
|
||||
event.d
|
||||
);
|
||||
break;
|
||||
|
||||
case 'GUILD_MEMBER_UPDATE':
|
||||
await this.members.set(
|
||||
event.d.user.id,
|
||||
event.d.guild_id,
|
||||
event.d
|
||||
);
|
||||
break;
|
||||
|
||||
case 'GUILD_MEMBER_REMOVE':
|
||||
await this.members.remove(event.d.user.id, event.d.guild_id);
|
||||
break;
|
||||
|
||||
case 'GUILD_MEMBERS_CHUNK':
|
||||
const members: Array<Promise<any> | undefined> = [];
|
||||
|
||||
for (const member of event.d.members) {
|
||||
members.push(
|
||||
this.members.set(
|
||||
member.user.id,
|
||||
event.d.guild_id,
|
||||
member
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(members);
|
||||
|
||||
break;
|
||||
|
||||
case 'VOICE_STATE_UPDATE':
|
||||
if (!event.d.guild_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.d.user_id && event.d.member) {
|
||||
await this.members.set(
|
||||
event.d.user_id,
|
||||
event.d.guild_id,
|
||||
event.d.member
|
||||
);
|
||||
}
|
||||
|
||||
if (event.d.channel_id != null) {
|
||||
await this.voices.set(
|
||||
event.d.user_id,
|
||||
event.d.guild_id,
|
||||
event.d
|
||||
);
|
||||
} else {
|
||||
await this.voices.remove(event.d.user_id, event.d.guild_id);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
2
packages/cache/src/index.ts
vendored
2
packages/cache/src/index.ts
vendored
@ -2,3 +2,5 @@ export { CacheAdapter } from './adapters/cache-adapter';
|
||||
|
||||
export { MemoryCacheAdapter } from './adapters/memory-cache-adapter';
|
||||
export { RedisCacheAdapter } from './adapters/redis-cache-adapter';
|
||||
|
||||
export { Cache } from './cache';
|
||||
|
67
packages/cache/src/resources/base-resource.ts
vendored
Normal file
67
packages/cache/src/resources/base-resource.ts
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
import { CacheAdapter } from '../adapters/cache-adapter';
|
||||
|
||||
export class BaseResource {
|
||||
namespace = 'base';
|
||||
|
||||
adapter!: CacheAdapter; // replace
|
||||
|
||||
constructor() {
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async count(to: string): Promise<number> {
|
||||
return await this.adapter.count(this.hashId(to));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async contains(to: string, id: string): Promise<boolean> {
|
||||
return await this.adapter.contains(this.hashId(to), id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async getToRelationship(to: string): Promise<string[]> {
|
||||
return await this.adapter.getToRelationship(this.hashId(to));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async addToRelationship(to: string, id: string): Promise<void> {
|
||||
await this.adapter.addToRelationship(this.hashId(to), id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc // to-do replace
|
||||
*/
|
||||
|
||||
async removeToRelationship(to: string, id: string): Promise<void> {
|
||||
await this.adapter.removeToRelationship(this.hashId(to), id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
hashId(id: string): string {
|
||||
return `${this.namespace}.${id}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
hashGuildId(id: string, guild: string): string {
|
||||
return `${this.namespace}.${guild}.${id}`;
|
||||
}
|
||||
}
|
81
packages/cache/src/resources/channel-resource.ts
vendored
Normal file
81
packages/cache/src/resources/channel-resource.ts
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
import { CacheAdapter } from '../adapters/cache-adapter';
|
||||
import { DiscordChannel } from '@biscuitland/api-types';
|
||||
|
||||
import { BaseResource } from './base-resource';
|
||||
|
||||
export class ChannelResource extends BaseResource {
|
||||
namespace: 'channel' = 'channel';
|
||||
|
||||
adapter: CacheAdapter;
|
||||
|
||||
constructor(adapter: CacheAdapter) {
|
||||
super();
|
||||
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async get(id: string): Promise<DiscordChannel | null> {
|
||||
const kv = await this.adapter.get(this.hashId(id));
|
||||
|
||||
if (kv) {
|
||||
return kv;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc // to-do rework
|
||||
*/
|
||||
|
||||
async set(id: string, data: any, expire?: number): Promise<void> {
|
||||
delete data.recipients;
|
||||
|
||||
await this.addToRelationship(id);
|
||||
await this.adapter.set(this.hashId(id), data, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async count(): Promise<number> {
|
||||
return await this.adapter.count(this.namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.adapter.remove(this.hashId(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async contains(id: string): Promise<boolean> {
|
||||
return await this.adapter.contains(this.namespace, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async getToRelationship(): Promise<string[]> {
|
||||
return await this.adapter.getToRelationship(this.namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async addToRelationship(id: string): Promise<void> {
|
||||
await this.adapter.addToRelationship(this.namespace, id);
|
||||
}
|
||||
}
|
51
packages/cache/src/resources/guild-emoji-resource.ts
vendored
Normal file
51
packages/cache/src/resources/guild-emoji-resource.ts
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
import { CacheAdapter } from '../adapters/cache-adapter';
|
||||
import { DiscordEmoji } from '@biscuitland/api-types';
|
||||
|
||||
import { BaseResource } from './base-resource';
|
||||
|
||||
export class GuildEmojiResource extends BaseResource {
|
||||
namespace: 'emoji' = 'emoji';
|
||||
|
||||
adapter: CacheAdapter;
|
||||
|
||||
constructor(adapter: CacheAdapter) {
|
||||
super();
|
||||
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async get(id: string, guild: string): Promise<DiscordEmoji | null> {
|
||||
const kv = await this.adapter.get(this.hashGuildId(id, guild));
|
||||
|
||||
if (kv) {
|
||||
return kv;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async set(
|
||||
id: string,
|
||||
guild: string,
|
||||
data: any,
|
||||
expire?: number
|
||||
): Promise<void> {
|
||||
await this.adapter.set(this.hashGuildId(id, guild), data, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async remove(id: string, guild: string): Promise<void> {
|
||||
await this.adapter.remove(this.hashGuildId(id, guild));
|
||||
}
|
||||
}
|
72
packages/cache/src/resources/guild-member-resource.ts
vendored
Normal file
72
packages/cache/src/resources/guild-member-resource.ts
vendored
Normal file
@ -0,0 +1,72 @@
|
||||
import { CacheAdapter } from '../adapters/cache-adapter';
|
||||
import { DiscordMember } from '@biscuitland/api-types';
|
||||
|
||||
import { BaseResource } from './base-resource';
|
||||
import { UserResource } from './user-resource';
|
||||
|
||||
export class GuildMemberResource extends BaseResource {
|
||||
namespace: 'member' = 'member';
|
||||
|
||||
adapter: CacheAdapter;
|
||||
users: UserResource;
|
||||
|
||||
constructor(adapter: CacheAdapter) {
|
||||
super();
|
||||
|
||||
this.adapter = adapter;
|
||||
this.users = new UserResource(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async get(
|
||||
id: string,
|
||||
guild: string
|
||||
): Promise<(DiscordMember & { id: string }) | null> {
|
||||
const kv = await this.adapter.get(this.hashGuildId(id, guild));
|
||||
|
||||
if (kv) {
|
||||
return kv;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async set(
|
||||
id: string,
|
||||
guild: string,
|
||||
data: any,
|
||||
expire?: number
|
||||
): Promise<void> {
|
||||
if (!data.id) {
|
||||
data.id = id;
|
||||
}
|
||||
|
||||
if (data.user) {
|
||||
await this.users.set(data.user.id, data.user);
|
||||
}
|
||||
|
||||
if (!data.guild_id) {
|
||||
data.guild_id = guild;
|
||||
}
|
||||
|
||||
delete data.user;
|
||||
|
||||
await this.addToRelationship(id, guild);
|
||||
await this.adapter.set(this.hashGuildId(id, guild), data, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async remove(id: string, guild: string): Promise<void> {
|
||||
await this.adapter.remove(this.hashGuildId(id, guild));
|
||||
}
|
||||
}
|
174
packages/cache/src/resources/guild-resource.ts
vendored
Normal file
174
packages/cache/src/resources/guild-resource.ts
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
import { CacheAdapter } from '../adapters/cache-adapter';
|
||||
import { DiscordGuild } from '@biscuitland/api-types';
|
||||
|
||||
import { ChannelResource } from './channel-resource';
|
||||
import { GuildEmojiResource } from './guild-emoji-resource';
|
||||
import { GuildMemberResource } from './guild-member-resource';
|
||||
import { GuildRoleResource } from './guild-role-resource';
|
||||
import { GuildStickerResource } from './guild-sticker-resource';
|
||||
import { VoiceResource } from './voice-resource';
|
||||
|
||||
import { BaseResource } from './base-resource';
|
||||
|
||||
export class GuildResource extends BaseResource {
|
||||
namespace: 'guild' = 'guild';
|
||||
|
||||
adapter: CacheAdapter;
|
||||
|
||||
private channels: ChannelResource;
|
||||
private emojis: GuildEmojiResource;
|
||||
private members: GuildMemberResource;
|
||||
private roles: GuildRoleResource;
|
||||
private stickers: GuildStickerResource;
|
||||
private voices: VoiceResource;
|
||||
|
||||
constructor(adapter: CacheAdapter) {
|
||||
super();
|
||||
|
||||
this.adapter = adapter;
|
||||
|
||||
this.channels = new ChannelResource(adapter);
|
||||
this.emojis = new GuildEmojiResource(adapter);
|
||||
this.members = new GuildMemberResource(adapter);
|
||||
this.roles = new GuildRoleResource(adapter);
|
||||
this.stickers = new GuildStickerResource(adapter);
|
||||
this.voices = new VoiceResource(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async get(id: string): Promise<DiscordGuild | null> {
|
||||
const kv = await this.adapter.get(this.hashId(id));
|
||||
|
||||
if (kv) {
|
||||
return kv;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async set(id: string, data: any, expire?: number): Promise<void> {
|
||||
if (data.channels) {
|
||||
const channels: Array<Promise<any> | undefined> = [];
|
||||
|
||||
for (const channel of data.channels) {
|
||||
await this.channels.set(channel.id, channel);
|
||||
}
|
||||
|
||||
await Promise.all(channels);
|
||||
}
|
||||
|
||||
if (data.members) {
|
||||
const members: Array<Promise<any> | undefined> = [];
|
||||
|
||||
for (const member of data.members) {
|
||||
await this.members.set(member.user.id, id, member);
|
||||
}
|
||||
|
||||
await Promise.all(members);
|
||||
}
|
||||
|
||||
if (data.roles) {
|
||||
const roles: Array<Promise<any> | undefined> = [];
|
||||
|
||||
for (const role of data.roles) {
|
||||
await this.roles.set(role.id, id, role);
|
||||
}
|
||||
|
||||
await Promise.all(roles);
|
||||
}
|
||||
|
||||
if (data.stickers) {
|
||||
const stickers: Array<Promise<any> | undefined> = [];
|
||||
|
||||
for (const sticker of data.stickers) {
|
||||
await this.stickers.set(sticker.id, id, sticker);
|
||||
}
|
||||
|
||||
await Promise.all(stickers);
|
||||
}
|
||||
|
||||
if (data.emojis) {
|
||||
const emojis: Array<Promise<any> | undefined> = [];
|
||||
|
||||
for (const emoji of data.emojis) {
|
||||
await this.emojis.set(emoji.id, id, emoji);
|
||||
}
|
||||
|
||||
await Promise.all(emojis);
|
||||
}
|
||||
|
||||
if (data.voice_states) {
|
||||
const voices: Array<Promise<any>> = [];
|
||||
|
||||
for (const voice of data.voice_states) {
|
||||
if (!voice.guild_id) {
|
||||
voice.guild_id = id;
|
||||
}
|
||||
|
||||
voices.push(this.voices.set(voice.user_id, id, voice));
|
||||
}
|
||||
|
||||
await Promise.all(voices);
|
||||
}
|
||||
|
||||
delete data.channels;
|
||||
delete data.members;
|
||||
delete data.roles;
|
||||
delete data.stickers;
|
||||
delete data.emojis;
|
||||
|
||||
delete data.presences;
|
||||
|
||||
delete data.voice_states;
|
||||
|
||||
await this.addToRelationship(id);
|
||||
await this.adapter.set(this.hashId(id), data, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async count(): Promise<number> {
|
||||
return await this.adapter.count(this.namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.adapter.remove(this.hashId(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async contains(id: string): Promise<boolean> {
|
||||
return await this.adapter.contains(this.namespace, id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async getToRelationship(): Promise<string[]> {
|
||||
return await this.adapter.getToRelationship(this.namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async addToRelationship(id: string): Promise<void> {
|
||||
await this.adapter.addToRelationship(this.namespace, id);
|
||||
}
|
||||
}
|
59
packages/cache/src/resources/guild-role-resource.ts
vendored
Normal file
59
packages/cache/src/resources/guild-role-resource.ts
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
import { CacheAdapter } from '../adapters/cache-adapter';
|
||||
import { DiscordRole } from '@biscuitland/api-types';
|
||||
|
||||
import { BaseResource } from './base-resource';
|
||||
|
||||
export class GuildRoleResource extends BaseResource {
|
||||
namespace: 'role' = 'role';
|
||||
|
||||
adapter: CacheAdapter;
|
||||
|
||||
constructor(adapter: CacheAdapter) {
|
||||
super();
|
||||
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async get(id: string, guild: string): Promise<DiscordRole | null> {
|
||||
const kv = await this.adapter.get(this.hashGuildId(id, guild));
|
||||
|
||||
if (kv) {
|
||||
return kv;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async set(
|
||||
id: string,
|
||||
guild: string,
|
||||
data: any,
|
||||
expire?: number
|
||||
): Promise<void> {
|
||||
if (!data.id) {
|
||||
data.id = id;
|
||||
}
|
||||
|
||||
if (!data.guild_id) {
|
||||
data.guild_id = guild;
|
||||
}
|
||||
|
||||
await this.adapter.set(this.hashGuildId(id, guild), data, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async remove(id: string, guild: string): Promise<void> {
|
||||
await this.adapter.remove(this.hashGuildId(id, guild));
|
||||
}
|
||||
}
|
59
packages/cache/src/resources/guild-sticker-resource.ts
vendored
Normal file
59
packages/cache/src/resources/guild-sticker-resource.ts
vendored
Normal file
@ -0,0 +1,59 @@
|
||||
import { CacheAdapter } from '../adapters/cache-adapter';
|
||||
import { DiscordSticker } from '@biscuitland/api-types';
|
||||
|
||||
import { BaseResource } from './base-resource';
|
||||
|
||||
export class GuildStickerResource extends BaseResource {
|
||||
namespace: 'sticker' = 'sticker';
|
||||
|
||||
adapter: CacheAdapter;
|
||||
|
||||
constructor(adapter: CacheAdapter) {
|
||||
super();
|
||||
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async get(id: string, guild: string): Promise<DiscordSticker | null> {
|
||||
const kv = await this.adapter.get(this.hashGuildId(id, guild));
|
||||
|
||||
if (kv) {
|
||||
return kv;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async set(
|
||||
id: string,
|
||||
guild: string,
|
||||
data: any,
|
||||
expire?: number
|
||||
): Promise<void> {
|
||||
if (!data.id) {
|
||||
data.id = id;
|
||||
}
|
||||
|
||||
if (!data.guild_id) {
|
||||
data.guild_id = guild;
|
||||
}
|
||||
|
||||
await this.adapter.set(this.hashGuildId(id, guild), data, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async remove(id: string, guild: string): Promise<void> {
|
||||
await this.adapter.remove(this.hashGuildId(id, guild));
|
||||
}
|
||||
}
|
13
packages/cache/src/resources/index.ts
vendored
Normal file
13
packages/cache/src/resources/index.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
export { BaseResource } from './base-resource';
|
||||
export { ChannelResource } from './channel-resource';
|
||||
|
||||
export { GuildEmojiResource } from './guild-emoji-resource';
|
||||
export { GuildMemberResource } from './guild-member-resource';
|
||||
|
||||
export { GuildResource } from './guild-resource';
|
||||
|
||||
export { GuildRoleResource } from './guild-role-resource';
|
||||
export { GuildStickerResource } from './guild-sticker-resource';
|
||||
|
||||
export { UserResource } from './user-resource';
|
||||
export { VoiceResource } from './voice-resource';
|
46
packages/cache/src/resources/user-resource.ts
vendored
Normal file
46
packages/cache/src/resources/user-resource.ts
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
import { CacheAdapter } from '../adapters/cache-adapter';
|
||||
import { DiscordUser } from '@biscuitland/api-types';
|
||||
|
||||
import { BaseResource } from './base-resource';
|
||||
|
||||
export class UserResource extends BaseResource {
|
||||
namespace: 'user' = 'user';
|
||||
|
||||
adapter: CacheAdapter;
|
||||
|
||||
constructor(adapter: CacheAdapter) {
|
||||
super();
|
||||
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async get(id: string): Promise<DiscordUser | null> {
|
||||
const kv = await this.adapter.get(this.hashId(id));
|
||||
|
||||
if (kv) {
|
||||
return kv;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async set(id: string, data: any, expire?: number): Promise<void> {
|
||||
await this.adapter.set(this.hashId(id), data, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
await this.adapter.remove(this.hashId(id));
|
||||
}
|
||||
}
|
57
packages/cache/src/resources/voice-resource.ts
vendored
Normal file
57
packages/cache/src/resources/voice-resource.ts
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
import { CacheAdapter } from '../adapters/cache-adapter';
|
||||
import { DiscordVoiceState } from '@biscuitland/api-types';
|
||||
|
||||
import { BaseResource } from './base-resource';
|
||||
|
||||
export class VoiceResource extends BaseResource {
|
||||
namespace: 'voice' = 'voice';
|
||||
|
||||
adapter: CacheAdapter;
|
||||
|
||||
constructor(adapter: CacheAdapter) {
|
||||
super();
|
||||
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async get(id: string, guild: string): Promise<DiscordVoiceState | null> {
|
||||
const kv = await this.adapter.get(this.hashGuildId(id, guild));
|
||||
|
||||
if (kv) {
|
||||
return kv;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async set(
|
||||
id: string,
|
||||
guild: string,
|
||||
data: any,
|
||||
expire?: number
|
||||
): Promise<void> {
|
||||
if (!data.guild_id) {
|
||||
data.guild_id = guild;
|
||||
}
|
||||
|
||||
delete data.member;
|
||||
|
||||
await this.adapter.set(this.hashGuildId(id, guild), data, expire);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
|
||||
async remove(id: string, guild: string): Promise<void> {
|
||||
await this.adapter.remove(this.hashGuildId(id, guild));
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@biscuitland/core",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.1",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
@ -23,9 +23,9 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@biscuitland/api-types": "^1.2.0",
|
||||
"@biscuitland/rest": "^1.2.0",
|
||||
"@biscuitland/ws": "^1.2.0"
|
||||
"@biscuitland/api-types": "^1.3.1",
|
||||
"@biscuitland/rest": "^1.3.1",
|
||||
"@biscuitland/ws": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^6.1.3"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@biscuitland/helpers",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.1",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
@ -23,8 +23,8 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@biscuitland/api-types": "^1.2.0",
|
||||
"@biscuitland/core": "^1.2.0"
|
||||
"@biscuitland/api-types": "^1.3.1",
|
||||
"@biscuitland/core": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^6.1.3"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@biscuitland/rest",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.1",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
@ -23,7 +23,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@biscuitland/api-types": "^1.2.0"
|
||||
"@biscuitland/api-types": "^1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsup": "^6.1.3"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@biscuitland/ws",
|
||||
"version": "1.3.0",
|
||||
"version": "1.3.1",
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
@ -23,7 +23,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@biscuitland/api-types": "^1.2.0",
|
||||
"@biscuitland/api-types": "^1.3.1",
|
||||
"ws": "^8.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user