1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-22 10:22:39 +01:00
This commit is contained in:
Flam3rboy 2021-01-30 19:58:15 +01:00
parent 4f9eb951ce
commit 76000f8fa1
39 changed files with 2084 additions and 2002 deletions

17
.vscode/launch.json vendored
View File

@ -8,8 +8,17 @@
"sourceMaps": true,
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/dist/test/db_test.js",
"name": "Launch Server",
"program": "${workspaceFolder}/dist/index.js",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
},
{
"sourceMaps": true,
"type": "node",
"request": "launch",
"name": "Test",
"program": "${workspaceFolder}/dist/test/mongo_test.js",
"preLaunchTask": "tsc: build - tsconfig.json",
"outFiles": ["${workspaceFolder}/dist/**/*.js"]
},
@ -18,7 +27,9 @@
"program": "${file}",
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node"
"runtimeArgs": ["--nolazy", "-r", "ts-node/register/transpile-only"],
"type": "node",
"resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"]
}
]
}

2636
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -22,16 +22,24 @@
"@types/node-fetch": "^2.5.7",
"express": "^4.17.1",
"express-cache-middleware": "^1.0.1",
"express-validator": "^6.9.2",
"faker": "^5.1.0",
"lambert-db": "^1.0.3",
"lambert-server": "^1.0.3",
"missing-native-js-functions": "^1.0.8",
"jsonwebtoken": "^8.5.1",
"jwa": "^2.0.0",
"jws": "^4.0.0",
"lambert-db": "^1.1.0",
"lambert-server": "^1.0.8",
"missing-native-js-functions": "^1.1.6",
"node-fetch": "^2.6.1",
"rethinkdb-ts": "^2.4.5"
},
"devDependencies": {
"@types/faker": "^5.1.5",
"@types/node": "^14.14.10",
"@types/jsonwebtoken": "^8.5.0",
"@types/jws": "^3.2.3",
"@types/node": "^14.14.22",
"lambert-server": "file:../../Trenite/Lambert-server",
"ts-node": "^9.1.1",
"typescript": "^4.1.2"
}
}

View File

@ -1,5 +1,8 @@
import fs from "fs/promises";
import { Server, ServerOptions } from "lambert-server";
import { Authentication, GlobalRateLimit } from "./middlewares/";
import Config from "./util/Config";
import db from "./util/Database";
export interface DiscordServerOptions extends ServerOptions {}
@ -19,6 +22,13 @@ export class DiscordServer extends Server {
}
async start() {
await db.init();
console.log("[DB] connected");
await Promise.all([Config.init()]);
this.app.use(GlobalRateLimit);
this.app.use(Authentication);
// recursively loads files in routes/
this.routes = await this.registerRoutes(__dirname + "/routes/");
// const indexHTML = await (await fetch("https://discord.com/app")).buffer();

View File

@ -1,38 +0,0 @@
import fs from "fs/promises";
import "missing-native-js-functions";
export interface traverseDirectoryOptions {
dirname: string;
filter?: RegExp;
excludeDirs?: RegExp;
recursive?: boolean;
}
const DEFAULT_EXCLUDE_DIR = /^\./;
const DEFAULT_FILTER = /^([^\.].*)\.js$/;
export async function traverseDirectory<T>(
options: traverseDirectoryOptions,
action: (path: string) => T
): Promise<T[]> {
if (!options.filter) options.filter = DEFAULT_FILTER;
if (!options.excludeDirs) options.excludeDirs = DEFAULT_EXCLUDE_DIR;
const routes = await fs.readdir(options.dirname);
const promises = <Promise<T | T[] | undefined>[]>routes.map(async (file) => {
const path = options.dirname + file;
const stat = await fs.lstat(path);
if (path.match(<RegExp>options.excludeDirs)) return;
if (stat.isFile() && path.match(<RegExp>options.filter)) {
return action(path);
} else if (options.recursive && stat.isDirectory()) {
return traverseDirectory({ ...options, dirname: path + "/" }, action);
}
});
const result = await Promise.all(promises);
const t = <(T | undefined)[]>result.flat();
return <T[]>t.filter((x) => x != undefined);
}

View File

@ -1,38 +0,0 @@
import fs from "fs/promises";
import "missing-native-js-functions";
export interface traverseDirectoryOptions {
dirname: string;
filter?: RegExp;
excludeDirs?: RegExp;
recursive?: boolean;
}
const DEFAULT_EXCLUDE_DIR = /^\./;
const DEFAULT_FILTER = /^([^\.].*)\.js$/;
export async function traverseDirectory<T>(
options: traverseDirectoryOptions,
action: (path: string) => T
): Promise<T[]> {
if (!options.filter) options.filter = DEFAULT_FILTER;
if (!options.excludeDirs) options.excludeDirs = DEFAULT_EXCLUDE_DIR;
const routes = await fs.readdir(options.dirname);
const promises = <Promise<T | T[] | undefined>[]>routes.map(async (file) => {
const path = options.dirname + file;
const stat = await fs.lstat(path);
if (path.match(<RegExp>options.excludeDirs)) return;
if (stat.isFile() && path.match(<RegExp>options.filter)) {
return action(path);
} else if (options.recursive && stat.isDirectory()) {
return traverseDirectory({ ...options, dirname: path + "/" }, action);
}
});
const result = await Promise.all(promises);
const t = <(T | undefined)[]>result.flat();
return <T[]>t.filter((x) => x != undefined);
}

View File

@ -1,3 +1,7 @@
process.on("uncaughtException", console.error);
process.on("unhandledRejection", console.error);
setTimeout(() => {}, 100000000);
import { DiscordServer } from "./Server";
const server = new DiscordServer({ port: 3000 });

4
src/middlewares/index.ts Normal file
View File

@ -0,0 +1,4 @@
import { Authentication } from "./Authentication";
import { GlobalRateLimit } from "./GlobalRateLimit";
export { Authentication, GlobalRateLimit };

View File

@ -1,59 +0,0 @@
import { Snowflake } from "./Snowflake";
export interface Guild {
afkChannel?: Snowflake;
afkTimeout: number;
onlineCount: number;
available: boolean;
banner: string | null;
channels: GuildChannelManager;
readonly createdTimestamp: number;
defaultMessageNotifications: DefaultMessageNotifications | number;
deleted: boolean;
description: string | null;
discoverySplash: string | null;
embedChannel: GuildChannel | null;
embedChannelID: Snowflake | null;
embedEnabled: boolean;
emojis: GuildEmojiManager;
explicitContentFilter: ExplicitContentFilterLevel;
features: GuildFeatures[];
icon: string | null;
id: Snowflake;
joinedTimestamp: number;
large: boolean;
maximumMembers: number | null;
maximumPresences: number | null;
memberCount: number;
members: GuildMemberManager;
mfaLevel: number;
name: string;
readonly nameAcronym: string;
readonly owner: Snowflake | null;
ownerID: Snowflake;
readonly partnered: boolean;
preferredLocale: string;
premiumSubscriptionCount: number | null;
premiumTier: PremiumTier;
presences: PresenceManager;
readonly publicUpdatesChannel: TextChannel | null;
publicUpdatesChannelID: Snowflake | null;
region: string;
roles: RoleManager;
readonly rulesChannel: TextChannel | null;
rulesChannelID: Snowflake | null;
readonly shard: WebSocketShard;
shardID: number;
splash: string | null;
readonly systemChannel: TextChannel | null;
systemChannelFlags: Readonly<SystemChannelFlags>;
systemChannelID: Snowflake | null;
vanityURLCode: string | null;
vanityURLUses: number | null;
verificationLevel: VerificationLevel;
readonly verified: boolean;
readonly voiceStates: VoiceStateManager;
readonly widgetChannel: TextChannel | null;
widgetChannelID: Snowflake | null;
widgetEnabled: boolean | null;
}

View File

@ -1 +0,0 @@
export type Snowflake = string;

View File

@ -0,0 +1,9 @@
import { Request, Response, Router } from "express";
import { check } from "../../../../util/instanceOf";
const router: Router = Router();
router.post("/", check({ test: String, $user: String }), (req: Request, res: Response) => {
res.send("OK");
});
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -0,0 +1,4 @@
import { Router } from "express";
const router: Router = Router();
export default router;

View File

@ -3,10 +3,10 @@
* (../../client/index.html)
*/
import { Router } from "express";
import fetch from "node-fetch";
import fetch, { Response } from "node-fetch";
const router = Router();
const cache = new Map();
const router: Router = Router();
const cache = new Map<string, Response>();
const assetEndpoint = "https://discord.com/assets/";
export async function getCache(key: string): Promise<Response> {

View File

@ -1,9 +0,0 @@
import { r } from "rethinkdb-ts";
async function main() {
const connection = await r.connect({ port: 28015, host: "192.168.178.122" });
r.db("test");
}
main();

37
src/test/jwt.ts Normal file
View File

@ -0,0 +1,37 @@
const jwa = require("jwa");
var STR64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".split("");
function base64url(string: string, encoding: string) {
// @ts-ignore
return Buffer.from(string, encoding).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
}
function to64String(input: number, current = ""): string {
if (input < 0 && current.length == 0) {
input = input * -1;
}
var modify = input % 64;
var remain = Math.floor(input / 64);
var result = STR64[modify] + current;
return remain <= 0 ? result : to64String(remain, result);
}
function to64Parse(input: string) {
var result = 0;
var toProc = input.split("");
var e;
for (e in toProc) {
result = result * 64 + STR64.indexOf(toProc[e]);
}
return result;
}
// @ts-ignore
const start = `${base64url("311129357362135041")}.${to64String(Date.now())}`;
const signature = jwa("HS256").sign(start, `test`);
const token = `${start}.${signature}`;
console.log(token);
// MzExMTI5MzU3MzYyMTM1MDQx.XdQb_rA.907VgF60kocnOTl32MSUWGSSzbAytQ0jbt36KjLaxuY
// MzExMTI5MzU3MzYyMTM1MDQx.XdQbaPy.4vGx4L7IuFJGsRe6IL3BeybLIvbx4Vauvx12pwNsy2U

8
src/test/jwt2.ts Normal file
View File

@ -0,0 +1,8 @@
import jwt from "jsonwebtoken";
// console.log(jwt.sign("test", "test"));
jwt.verify(`${"2WmFS_EAdYFCBOFM9pVPo9g4bpuI2I9U_JGTCfrx7Tk".repeat(1000000)}`, "test", (err, decoded) => {
if (err) console.error(err);
console.log(decoded);
});

14
src/test/mongo_test.ts Normal file
View File

@ -0,0 +1,14 @@
import mongoose from "mongoose";
async function main() {
const mongoConnection = await mongoose.createConnection(
"mongodb://localhost:27017/lambert?readPreference=secondaryPreferred",
{
useNewUrlParser: true,
useUnifiedTopology: false,
}
);
console.log("connected");
}
main();

34
src/test/rethink_test.ts Normal file
View File

@ -0,0 +1,34 @@
import { r } from "rethinkdb-ts";
async function main() {
const connection = await r.connect({ port: 28015 });
const db = r.db("test");
const cursor = await db
.table("guilds")
.get(0)
.changes({ squash: true })
.map(function (row) {
return row("old_val")
.keys()
.setUnion(row("new_val").keys())
.concatMap(function (key) {
return r.branch(
row("old_val")(key).ne(row("new_val")(key)).default(true),
[[key, row("new_val")(key).default(null)]],
[]
);
})
.coerceTo("object");
})
.run(connection);
console.log("each");
cursor.each(function (err, row) {
if (err) throw err;
console.log(row);
});
console.log("eachend");
}
main();

146
src/util/BitField.ts Normal file
View File

@ -0,0 +1,146 @@
"use strict";
// https://github.com/discordjs/discord.js/blob/master/src/util/BitField.js
// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah
export type BitFieldResolvable = number | BitField | string | BitFieldResolvable[];
/**
* Data structure that makes it easy to interact with a bitfield.
*/
export class BitField {
public bitfield: number;
/**
* Numeric bitfield flags.
* <info>Defined in extension classes</info>
*/
public static FLAGS: Record<string, number>;
/**
*/
constructor(bits: BitFieldResolvable = 0) {
/**
* Bitfield of the packed bits
* @type {number}
*/
this.bitfield = BitField.resolve(bits);
}
/**
* Checks whether the bitfield has a bit, or any of multiple bits.
*/
any(bit: BitFieldResolvable): boolean {
return (this.bitfield & BitField.resolve(bit)) !== 0;
}
/**
* Checks if this bitfield equals another
*/
equals(bit: BitFieldResolvable): boolean {
return this.bitfield === BitField.resolve(bit);
}
/**
* Checks whether the bitfield has a bit, or multiple bits.
*/
has(bit: BitFieldResolvable): boolean {
if (Array.isArray(bit)) return bit.every((p) => this.has(p));
bit = BitField.resolve(bit);
return (this.bitfield & bit) === bit;
}
/**
* Gets all given bits that are missing from the bitfield.
*/
missing(bits: BitFieldResolvable) {
if (!Array.isArray(bits)) bits = new BitField(bits).toArray();
return bits.filter((p) => !this.has(p));
}
/**
* Freezes these bits, making them immutable.
*/
freeze(): Readonly<BitField> {
return Object.freeze(this);
}
/**
* Adds bits to these ones.
* @param {...BitFieldResolvable} [bits] Bits to add
* @returns {BitField} These bits or new BitField if the instance is frozen.
*/
add(...bits: BitFieldResolvable[]): BitField {
let total = 0;
for (const bit of bits) {
total |= BitField.resolve(bit);
}
if (Object.isFrozen(this)) return new BitField(this.bitfield | total);
this.bitfield |= total;
return this;
}
/**
* Removes bits from these.
* @param {...BitFieldResolvable} [bits] Bits to remove
*/
remove(...bits: BitFieldResolvable[]) {
let total = 0;
for (const bit of bits) {
total |= BitField.resolve(bit);
}
if (Object.isFrozen(this)) return new BitField(this.bitfield & ~total);
this.bitfield &= ~total;
return this;
}
/**
* Gets an object mapping field names to a {@link boolean} indicating whether the
* bit is available.
* @param {...*} hasParams Additional parameters for the has method, if any
*/
serialize() {
const serialized: Record<string, boolean> = {};
for (const [flag, bit] of Object.entries(BitField.FLAGS)) serialized[flag] = this.has(bit);
return serialized;
}
/**
* Gets an {@link Array} of bitfield names based on the bits available.
*/
toArray(): string[] {
return Object.keys(BitField.FLAGS).filter((bit) => this.has(bit));
}
toJSON() {
return this.bitfield;
}
valueOf() {
return this.bitfield;
}
*[Symbol.iterator]() {
yield* this.toArray();
}
/**
* Data that can be resolved to give a bitfield. This can be:
* * A bit number (this can be a number literal or a value taken from {@link BitField.FLAGS})
* * An instance of BitField
* * An Array of BitFieldResolvable
* @typedef {number|BitField|BitFieldResolvable[]} BitFieldResolvable
*/
/**
* Resolves bitfields to their numeric form.
* @param {BitFieldResolvable} [bit=0] - bit(s) to resolve
* @returns {number}
*/
static resolve(bit: BitFieldResolvable = 0): number {
if (typeof bit === "number" && bit >= 0) return bit;
if (bit instanceof BitField) return bit.bitfield;
if (Array.isArray(bit)) return bit.map((p) => this.resolve(p)).reduce((prev, p) => prev | p, 0);
if (typeof bit === "string" && typeof this.FLAGS[bit] !== "undefined") return this.FLAGS[bit];
throw new RangeError("BITFIELD_INVALID: " + bit);
}
}

25
src/util/Config.ts Normal file
View File

@ -0,0 +1,25 @@
import "missing-native-js-functions";
import db from "./Database";
import { DefaultOptions } from "./Constants";
import { ProviderCache } from "lambert-db";
var Config: ProviderCache;
async function init() {
Config = db.data.config.cache();
await Config.init();
await Config.set(DefaultOptions.merge(Config.cache));
}
function get() {
return <DefaultOptions>Config.get();
}
function set(val: any) {
return Config.set(val);
}
export default {
init,
get: get,
set: set,
};

679
src/util/Constants.ts Normal file
View File

@ -0,0 +1,679 @@
import crypto from "crypto";
import { VerifyOptions } from "jsonwebtoken";
export interface DefaultOptions {
user: {
maxGuilds: number;
maxUsername: number;
maxFriends: number;
};
guild: {
maxRoles: number;
maxMembers: number;
maxChannels: number;
maxChannelsInCategory: number;
hideOfflineMember: number;
};
message: {
characters: number;
ttsCharacters: number;
maxReactions: number;
maxAttachmentSize: number;
};
channel: {
maxPins: number;
maxTopic: number;
};
server: {
jwtSecret: string;
ipRateLimit: {
enabled: boolean;
count: number;
timespan: number;
};
forwadedFor: false | string;
};
}
export const DefaultOptions: DefaultOptions = {
user: {
maxGuilds: 100,
maxUsername: 32,
maxFriends: 1000,
},
guild: {
maxRoles: 250,
maxMembers: 250000,
maxChannels: 500,
maxChannelsInCategory: 50,
hideOfflineMember: 1000,
},
message: {
characters: 2000,
ttsCharacters: 200,
maxReactions: 20,
maxAttachmentSize: 8388608,
},
channel: {
maxPins: 50,
maxTopic: 1024,
},
server: {
jwtSecret: crypto.randomBytes(256).toString("base64"),
ipRateLimit: {
enabled: true,
count: 1000,
timespan: 1000 * 60 * 10,
},
forwadedFor: false,
// forwadedFor: "X-Forwarded-For" // nginx/reverse proxy
// forwadedFor: "CF-Connecting-IP" // cloudflare:
},
};
export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] };
export const WSCodes = {
1000: "WS_CLOSE_REQUESTED",
4004: "TOKEN_INVALID",
4010: "SHARDING_INVALID",
4011: "SHARDING_REQUIRED",
4013: "INVALID_INTENTS",
4014: "DISALLOWED_INTENTS",
};
const AllowedImageFormats = ["webp", "png", "jpg", "jpeg", "gif"];
const AllowedImageSizes = Array.from({ length: 9 }, (e, i) => 2 ** (i + 4));
function makeImageUrl(root: string, { format = "webp", size = 512 } = {}) {
if (format && !AllowedImageFormats.includes(format)) throw new Error("IMAGE_FORMAT: " + format);
if (size && !AllowedImageSizes.includes(size)) throw new RangeError("IMAGE_SIZE: " + size);
return `${root}.${format}${size ? `?size=${size}` : ""}`;
}
/**
* Options for Image URLs.
* @typedef {Object} ImageURLOptions
* @property {string} [format] One of `webp`, `png`, `jpg`, `jpeg`, `gif`. If no format is provided,
* defaults to `webp`.
* @property {boolean} [dynamic] If true, the format will dynamically change to `gif` for
* animated avatars; the default is false.
* @property {number} [size] One of `16`, `32`, `64`, `128`, `256`, `512`, `1024`, `2048`, `4096`
*/
export const Endpoints = {
CDN(root: string) {
return {
Emoji: (emojiID: string, format = "png") => `${root}/emojis/${emojiID}.${format}`,
Asset: (name: string) => `${root}/assets/${name}`,
DefaultAvatar: (discriminator: string) => `${root}/embed/avatars/${discriminator}.png`,
Avatar: (userID: string, hash: string, format = "webp", size: number, dynamic = false) => {
if (dynamic) format = hash.startsWith("a_") ? "gif" : format;
return makeImageUrl(`${root}/avatars/${userID}/${hash}`, { format, size });
},
Banner: (guildID: string, hash: string, format = "webp", size: number) =>
makeImageUrl(`${root}/banners/${guildID}/${hash}`, { format, size }),
Icon: (guildID: string, hash: string, format = "webp", size: number, dynamic = false) => {
if (dynamic) format = hash.startsWith("a_") ? "gif" : format;
return makeImageUrl(`${root}/icons/${guildID}/${hash}`, { format, size });
},
AppIcon: (
clientID: string,
hash: string,
{ format = "webp", size }: { format?: string; size?: number } = {}
) => makeImageUrl(`${root}/app-icons/${clientID}/${hash}`, { size, format }),
AppAsset: (
clientID: string,
hash: string,
{ format = "webp", size }: { format?: string; size?: number } = {}
) => makeImageUrl(`${root}/app-assets/${clientID}/${hash}`, { size, format }),
GDMIcon: (channelID: string, hash: string, format = "webp", size: number) =>
makeImageUrl(`${root}/channel-icons/${channelID}/${hash}`, { size, format }),
Splash: (guildID: string, hash: string, format = "webp", size: number) =>
makeImageUrl(`${root}/splashes/${guildID}/${hash}`, { size, format }),
DiscoverySplash: (guildID: string, hash: string, format = "webp", size: number) =>
makeImageUrl(`${root}/discovery-splashes/${guildID}/${hash}`, { size, format }),
TeamIcon: (
teamID: string,
hash: string,
{ format = "webp", size }: { format?: string; size?: number } = {}
) => makeImageUrl(`${root}/team-icons/${teamID}/${hash}`, { size, format }),
};
},
invite: (root: string, code: string) => `${root}/${code}`,
botGateway: "/gateway/bot",
};
/**
* The current status of the client. Here are the available statuses:
* * READY: 0
* * CONNECTING: 1
* * RECONNECTING: 2
* * IDLE: 3
* * NEARLY: 4
* * DISCONNECTED: 5
* * WAITING_FOR_GUILDS: 6
* * IDENTIFYING: 7
* * RESUMING: 8
* @typedef {number} Status
*/
export const Status = {
READY: 0,
CONNECTING: 1,
RECONNECTING: 2,
IDLE: 3,
NEARLY: 4,
DISCONNECTED: 5,
WAITING_FOR_GUILDS: 6,
IDENTIFYING: 7,
RESUMING: 8,
};
/**
* The current status of a voice connection. Here are the available statuses:
* * CONNECTED: 0
* * CONNECTING: 1
* * AUTHENTICATING: 2
* * RECONNECTING: 3
* * DISCONNECTED: 4
* @typedef {number} VoiceStatus
*/
export const VoiceStatus = {
CONNECTED: 0,
CONNECTING: 1,
AUTHENTICATING: 2,
RECONNECTING: 3,
DISCONNECTED: 4,
};
export const OPCodes = {
DISPATCH: 0,
HEARTBEAT: 1,
IDENTIFY: 2,
STATUS_UPDATE: 3,
VOICE_STATE_UPDATE: 4,
VOICE_GUILD_PING: 5,
RESUME: 6,
RECONNECT: 7,
REQUEST_GUILD_MEMBERS: 8,
INVALID_SESSION: 9,
HELLO: 10,
HEARTBEAT_ACK: 11,
};
export const VoiceOPCodes = {
IDENTIFY: 0,
SELECT_PROTOCOL: 1,
READY: 2,
HEARTBEAT: 3,
SESSION_DESCRIPTION: 4,
SPEAKING: 5,
HELLO: 8,
CLIENT_CONNECT: 12,
CLIENT_DISCONNECT: 13,
};
export const Events = {
RATE_LIMIT: "rateLimit",
CLIENT_READY: "ready",
GUILD_CREATE: "guildCreate",
GUILD_DELETE: "guildDelete",
GUILD_UPDATE: "guildUpdate",
GUILD_UNAVAILABLE: "guildUnavailable",
GUILD_AVAILABLE: "guildAvailable",
GUILD_MEMBER_ADD: "guildMemberAdd",
GUILD_MEMBER_REMOVE: "guildMemberRemove",
GUILD_MEMBER_UPDATE: "guildMemberUpdate",
GUILD_MEMBER_AVAILABLE: "guildMemberAvailable",
GUILD_MEMBER_SPEAKING: "guildMemberSpeaking",
GUILD_MEMBERS_CHUNK: "guildMembersChunk",
GUILD_INTEGRATIONS_UPDATE: "guildIntegrationsUpdate",
GUILD_ROLE_CREATE: "roleCreate",
GUILD_ROLE_DELETE: "roleDelete",
INVITE_CREATE: "inviteCreate",
INVITE_DELETE: "inviteDelete",
GUILD_ROLE_UPDATE: "roleUpdate",
GUILD_EMOJI_CREATE: "emojiCreate",
GUILD_EMOJI_DELETE: "emojiDelete",
GUILD_EMOJI_UPDATE: "emojiUpdate",
GUILD_BAN_ADD: "guildBanAdd",
GUILD_BAN_REMOVE: "guildBanRemove",
CHANNEL_CREATE: "channelCreate",
CHANNEL_DELETE: "channelDelete",
CHANNEL_UPDATE: "channelUpdate",
CHANNEL_PINS_UPDATE: "channelPinsUpdate",
MESSAGE_CREATE: "message",
MESSAGE_DELETE: "messageDelete",
MESSAGE_UPDATE: "messageUpdate",
MESSAGE_BULK_DELETE: "messageDeleteBulk",
MESSAGE_REACTION_ADD: "messageReactionAdd",
MESSAGE_REACTION_REMOVE: "messageReactionRemove",
MESSAGE_REACTION_REMOVE_ALL: "messageReactionRemoveAll",
MESSAGE_REACTION_REMOVE_EMOJI: "messageReactionRemoveEmoji",
USER_UPDATE: "userUpdate",
PRESENCE_UPDATE: "presenceUpdate",
VOICE_SERVER_UPDATE: "voiceServerUpdate",
VOICE_STATE_UPDATE: "voiceStateUpdate",
VOICE_BROADCAST_SUBSCRIBE: "subscribe",
VOICE_BROADCAST_UNSUBSCRIBE: "unsubscribe",
TYPING_START: "typingStart",
TYPING_STOP: "typingStop",
WEBHOOKS_UPDATE: "webhookUpdate",
ERROR: "error",
WARN: "warn",
DEBUG: "debug",
SHARD_DISCONNECT: "shardDisconnect",
SHARD_ERROR: "shardError",
SHARD_RECONNECTING: "shardReconnecting",
SHARD_READY: "shardReady",
SHARD_RESUME: "shardResume",
INVALIDATED: "invalidated",
RAW: "raw",
};
export const ShardEvents = {
CLOSE: "close",
DESTROYED: "destroyed",
INVALID_SESSION: "invalidSession",
READY: "ready",
RESUMED: "resumed",
ALL_READY: "allReady",
};
/**
* The type of Structure allowed to be a partial:
* * USER
* * CHANNEL (only affects DMChannels)
* * GUILD_MEMBER
* * MESSAGE
* * REACTION
* <warn>Partials require you to put checks in place when handling data, read the Partials topic listed in the
* sidebar for more information.</warn>
* @typedef {string} PartialType
*/
export const PartialTypes = keyMirror(["USER", "CHANNEL", "GUILD_MEMBER", "MESSAGE", "REACTION"]);
/**
* The type of a websocket message event, e.g. `MESSAGE_CREATE`. Here are the available events:
* * READY
* * RESUMED
* * GUILD_CREATE
* * GUILD_DELETE
* * GUILD_UPDATE
* * INVITE_CREATE
* * INVITE_DELETE
* * GUILD_MEMBER_ADD
* * GUILD_MEMBER_REMOVE
* * GUILD_MEMBER_UPDATE
* * GUILD_MEMBERS_CHUNK
* * GUILD_INTEGRATIONS_UPDATE
* * GUILD_ROLE_CREATE
* * GUILD_ROLE_DELETE
* * GUILD_ROLE_UPDATE
* * GUILD_BAN_ADD
* * GUILD_BAN_REMOVE
* * GUILD_EMOJIS_UPDATE
* * CHANNEL_CREATE
* * CHANNEL_DELETE
* * CHANNEL_UPDATE
* * CHANNEL_PINS_UPDATE
* * MESSAGE_CREATE
* * MESSAGE_DELETE
* * MESSAGE_UPDATE
* * MESSAGE_DELETE_BULK
* * MESSAGE_REACTION_ADD
* * MESSAGE_REACTION_REMOVE
* * MESSAGE_REACTION_REMOVE_ALL
* * MESSAGE_REACTION_REMOVE_EMOJI
* * USER_UPDATE
* * PRESENCE_UPDATE
* * TYPING_START
* * VOICE_STATE_UPDATE
* * VOICE_SERVER_UPDATE
* * WEBHOOKS_UPDATE
* @typedef {string} WSEventType
*/
export const WSEvents = keyMirror([
"READY",
"RESUMED",
"GUILD_CREATE",
"GUILD_DELETE",
"GUILD_UPDATE",
"INVITE_CREATE",
"INVITE_DELETE",
"GUILD_MEMBER_ADD",
"GUILD_MEMBER_REMOVE",
"GUILD_MEMBER_UPDATE",
"GUILD_MEMBERS_CHUNK",
"GUILD_INTEGRATIONS_UPDATE",
"GUILD_ROLE_CREATE",
"GUILD_ROLE_DELETE",
"GUILD_ROLE_UPDATE",
"GUILD_BAN_ADD",
"GUILD_BAN_REMOVE",
"GUILD_EMOJIS_UPDATE",
"CHANNEL_CREATE",
"CHANNEL_DELETE",
"CHANNEL_UPDATE",
"CHANNEL_PINS_UPDATE",
"MESSAGE_CREATE",
"MESSAGE_DELETE",
"MESSAGE_UPDATE",
"MESSAGE_DELETE_BULK",
"MESSAGE_REACTION_ADD",
"MESSAGE_REACTION_REMOVE",
"MESSAGE_REACTION_REMOVE_ALL",
"MESSAGE_REACTION_REMOVE_EMOJI",
"USER_UPDATE",
"PRESENCE_UPDATE",
"TYPING_START",
"VOICE_STATE_UPDATE",
"VOICE_SERVER_UPDATE",
"WEBHOOKS_UPDATE",
]);
/**
* The type of a message, e.g. `DEFAULT`. Here are the available types:
* * DEFAULT
* * RECIPIENT_ADD
* * RECIPIENT_REMOVE
* * CALL
* * CHANNEL_NAME_CHANGE
* * CHANNEL_ICON_CHANGE
* * PINS_ADD
* * GUILD_MEMBER_JOIN
* * USER_PREMIUM_GUILD_SUBSCRIPTION
* * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1
* * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2
* * USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3
* * CHANNEL_FOLLOW_ADD
* * GUILD_DISCOVERY_DISQUALIFIED
* * GUILD_DISCOVERY_REQUALIFIED
* * REPLY
* @typedef {string} MessageType
*/
export const MessageTypes = [
"DEFAULT",
"RECIPIENT_ADD",
"RECIPIENT_REMOVE",
"CALL",
"CHANNEL_NAME_CHANGE",
"CHANNEL_ICON_CHANGE",
"PINS_ADD",
"GUILD_MEMBER_JOIN",
"USER_PREMIUM_GUILD_SUBSCRIPTION",
"USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_1",
"USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_2",
"USER_PREMIUM_GUILD_SUBSCRIPTION_TIER_3",
"CHANNEL_FOLLOW_ADD",
null,
"GUILD_DISCOVERY_DISQUALIFIED",
"GUILD_DISCOVERY_REQUALIFIED",
null,
null,
null,
"REPLY",
];
/**
* The types of messages that are `System`. The available types are `MessageTypes` excluding:
* * DEFAULT
* * REPLY
* @typedef {string} SystemMessageType
*/
export const SystemMessageTypes = MessageTypes.filter(
(type: string | null) => type && type !== "DEFAULT" && type !== "REPLY"
);
/**
* <info>Bots cannot set a `CUSTOM_STATUS`, it is only for custom statuses received from users</info>
* The type of an activity of a users presence, e.g. `PLAYING`. Here are the available types:
* * PLAYING
* * STREAMING
* * LISTENING
* * WATCHING
* * CUSTOM_STATUS
* * COMPETING
* @typedef {string} ActivityType
*/
export const ActivityTypes = ["PLAYING", "STREAMING", "LISTENING", "WATCHING", "CUSTOM_STATUS", "COMPETING"];
export const ChannelTypes = {
TEXT: 0,
DM: 1,
VOICE: 2,
GROUP: 3,
CATEGORY: 4,
NEWS: 5,
STORE: 6,
};
export const ClientApplicationAssetTypes = {
SMALL: 1,
BIG: 2,
};
export const Colors = {
DEFAULT: 0x000000,
WHITE: 0xffffff,
AQUA: 0x1abc9c,
GREEN: 0x2ecc71,
BLUE: 0x3498db,
YELLOW: 0xffff00,
PURPLE: 0x9b59b6,
LUMINOUS_VIVID_PINK: 0xe91e63,
GOLD: 0xf1c40f,
ORANGE: 0xe67e22,
RED: 0xe74c3c,
GREY: 0x95a5a6,
NAVY: 0x34495e,
DARK_AQUA: 0x11806a,
DARK_GREEN: 0x1f8b4c,
DARK_BLUE: 0x206694,
DARK_PURPLE: 0x71368a,
DARK_VIVID_PINK: 0xad1457,
DARK_GOLD: 0xc27c0e,
DARK_ORANGE: 0xa84300,
DARK_RED: 0x992d22,
DARK_GREY: 0x979c9f,
DARKER_GREY: 0x7f8c8d,
LIGHT_GREY: 0xbcc0c0,
DARK_NAVY: 0x2c3e50,
BLURPLE: 0x7289da,
GREYPLE: 0x99aab5,
DARK_BUT_NOT_BLACK: 0x2c2f33,
NOT_QUITE_BLACK: 0x23272a,
};
/**
* The value set for the explicit content filter levels for a guild:
* * DISABLED
* * MEMBERS_WITHOUT_ROLES
* * ALL_MEMBERS
* @typedef {string} ExplicitContentFilterLevel
*/
export const ExplicitContentFilterLevels = ["DISABLED", "MEMBERS_WITHOUT_ROLES", "ALL_MEMBERS"];
/**
* The value set for the verification levels for a guild:
* * NONE
* * LOW
* * MEDIUM
* * HIGH
* * VERY_HIGH
* @typedef {string} VerificationLevel
*/
export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"];
/**
* An error encountered while performing an API request. Here are the potential errors:
* * UNKNOWN_ACCOUNT
* * UNKNOWN_APPLICATION
* * UNKNOWN_CHANNEL
* * UNKNOWN_GUILD
* * UNKNOWN_INTEGRATION
* * UNKNOWN_INVITE
* * UNKNOWN_MEMBER
* * UNKNOWN_MESSAGE
* * UNKNOWN_OVERWRITE
* * UNKNOWN_PROVIDER
* * UNKNOWN_ROLE
* * UNKNOWN_TOKEN
* * UNKNOWN_USER
* * UNKNOWN_EMOJI
* * UNKNOWN_WEBHOOK
* * UNKNOWN_BAN
* * UNKNOWN_GUILD_TEMPLATE
* * BOT_PROHIBITED_ENDPOINT
* * BOT_ONLY_ENDPOINT
* * CHANNEL_HIT_WRITE_RATELIMIT
* * MAXIMUM_GUILDS
* * MAXIMUM_FRIENDS
* * MAXIMUM_PINS
* * MAXIMUM_ROLES
* * MAXIMUM_WEBHOOKS
* * MAXIMUM_REACTIONS
* * MAXIMUM_CHANNELS
* * MAXIMUM_ATTACHMENTS
* * MAXIMUM_INVITES
* * GUILD_ALREADY_HAS_TEMPLATE
* * UNAUTHORIZED
* * ACCOUNT_VERIFICATION_REQUIRED
* * REQUEST_ENTITY_TOO_LARGE
* * FEATURE_TEMPORARILY_DISABLED
* * USER_BANNED
* * ALREADY_CROSSPOSTED
* * MISSING_ACCESS
* * INVALID_ACCOUNT_TYPE
* * CANNOT_EXECUTE_ON_DM
* * EMBED_DISABLED
* * CANNOT_EDIT_MESSAGE_BY_OTHER
* * CANNOT_SEND_EMPTY_MESSAGE
* * CANNOT_MESSAGE_USER
* * CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL
* * CHANNEL_VERIFICATION_LEVEL_TOO_HIGH
* * OAUTH2_APPLICATION_BOT_ABSENT
* * MAXIMUM_OAUTH2_APPLICATIONS
* * INVALID_OAUTH_STATE
* * MISSING_PERMISSIONS
* * INVALID_AUTHENTICATION_TOKEN
* * NOTE_TOO_LONG
* * INVALID_BULK_DELETE_QUANTITY
* * CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL
* * INVALID_OR_TAKEN_INVITE_CODE
* * CANNOT_EXECUTE_ON_SYSTEM_MESSAGE
* * INVALID_OAUTH_TOKEN
* * BULK_DELETE_MESSAGE_TOO_OLD
* * INVALID_FORM_BODY
* * INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT
* * INVALID_API_VERSION
* * CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL
* * REACTION_BLOCKED
* * RESOURCE_OVERLOADED
* @typedef {string} APIError
*/
export const APIErrors = {
UNKNOWN_ACCOUNT: 10001,
UNKNOWN_APPLICATION: 10002,
UNKNOWN_CHANNEL: 10003,
UNKNOWN_GUILD: 10004,
UNKNOWN_INTEGRATION: 10005,
UNKNOWN_INVITE: 10006,
UNKNOWN_MEMBER: 10007,
UNKNOWN_MESSAGE: 10008,
UNKNOWN_OVERWRITE: 10009,
UNKNOWN_PROVIDER: 10010,
UNKNOWN_ROLE: 10011,
UNKNOWN_TOKEN: 10012,
UNKNOWN_USER: 10013,
UNKNOWN_EMOJI: 10014,
UNKNOWN_WEBHOOK: 10015,
UNKNOWN_BAN: 10026,
UNKNOWN_GUILD_TEMPLATE: 10057,
BOT_PROHIBITED_ENDPOINT: 20001,
BOT_ONLY_ENDPOINT: 20002,
CHANNEL_HIT_WRITE_RATELIMIT: 20028,
MAXIMUM_GUILDS: 30001,
MAXIMUM_FRIENDS: 30002,
MAXIMUM_PINS: 30003,
MAXIMUM_ROLES: 30005,
MAXIMUM_WEBHOOKS: 30007,
MAXIMUM_REACTIONS: 30010,
MAXIMUM_CHANNELS: 30013,
MAXIMUM_ATTACHMENTS: 30015,
MAXIMUM_INVITES: 30016,
GUILD_ALREADY_HAS_TEMPLATE: 30031,
UNAUTHORIZED: 40001,
ACCOUNT_VERIFICATION_REQUIRED: 40002,
REQUEST_ENTITY_TOO_LARGE: 40005,
FEATURE_TEMPORARILY_DISABLED: 40006,
USER_BANNED: 40007,
ALREADY_CROSSPOSTED: 40033,
MISSING_ACCESS: 50001,
INVALID_ACCOUNT_TYPE: 50002,
CANNOT_EXECUTE_ON_DM: 50003,
EMBED_DISABLED: 50004,
CANNOT_EDIT_MESSAGE_BY_OTHER: 50005,
CANNOT_SEND_EMPTY_MESSAGE: 50006,
CANNOT_MESSAGE_USER: 50007,
CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: 50008,
CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: 50009,
OAUTH2_APPLICATION_BOT_ABSENT: 50010,
MAXIMUM_OAUTH2_APPLICATIONS: 50011,
INVALID_OAUTH_STATE: 50012,
MISSING_PERMISSIONS: 50013,
INVALID_AUTHENTICATION_TOKEN: 50014,
NOTE_TOO_LONG: 50015,
INVALID_BULK_DELETE_QUANTITY: 50016,
CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: 50019,
INVALID_OR_TAKEN_INVITE_CODE: 50020,
CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: 50021,
INVALID_OAUTH_TOKEN: 50025,
BULK_DELETE_MESSAGE_TOO_OLD: 50034,
INVALID_FORM_BODY: 50035,
INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036,
INVALID_API_VERSION: 50041,
CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: 50074,
REACTION_BLOCKED: 90001,
RESOURCE_OVERLOADED: 130000,
};
/**
* The value set for a guild's default message notifications, e.g. `ALL`. Here are the available types:
* * ALL
* * MENTIONS
* @typedef {string} DefaultMessageNotifications
*/
export const DefaultMessageNotifications = ["ALL", "MENTIONS"];
/**
* The value set for a team members's membership state:
* * INVITED
* * ACCEPTED
* @typedef {string} MembershipStates
*/
export const MembershipStates = [
// They start at 1
null,
"INVITED",
"ACCEPTED",
];
/**
* The value set for a webhook's type:
* * Incoming
* * Channel Follower
* @typedef {string} WebhookTypes
*/
export const WebhookTypes = [
// They start at 1
null,
"Incoming",
"Channel Follower",
];
function keyMirror(arr: string[]) {
let tmp = Object.create(null);
for (const value of arr) tmp[value] = value;
return tmp;
}

14
src/util/MessageFlags.ts Normal file
View File

@ -0,0 +1,14 @@
// https://github.com/discordjs/discord.js/blob/master/src/util/MessageFlags.js
// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah
import { BitField } from "./BitField";
export class MessageFlags extends BitField {
static FLAGS = {
CROSSPOSTED: 1 << 0,
IS_CROSSPOST: 1 << 1,
SUPPRESS_EMBEDS: 1 << 2,
SOURCE_MESSAGE_DELETED: 1 << 3,
URGENT: 1 << 4,
};
}

56
src/util/Permissions.ts Normal file
View File

@ -0,0 +1,56 @@
// https://github.com/discordjs/discord.js/blob/master/src/util/Permissions.js
// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah
import { BitField } from "./BitField";
export type PermissionResolvable = string | number | Permissions | PermissionResolvable[];
export class Permissions extends BitField {
static FLAGS = {
CREATE_INSTANT_INVITE: 1 << 0,
KICK_MEMBERS: 1 << 1,
BAN_MEMBERS: 1 << 2,
ADMINISTRATOR: 1 << 3,
MANAGE_CHANNELS: 1 << 4,
MANAGE_GUILD: 1 << 5,
ADD_REACTIONS: 1 << 6,
VIEW_AUDIT_LOG: 1 << 7,
PRIORITY_SPEAKER: 1 << 8,
STREAM: 1 << 9,
VIEW_CHANNEL: 1 << 10,
SEND_MESSAGES: 1 << 11,
SEND_TTS_MESSAGES: 1 << 12,
MANAGE_MESSAGES: 1 << 13,
EMBED_LINKS: 1 << 14,
ATTACH_FILES: 1 << 15,
READ_MESSAGE_HISTORY: 1 << 16,
MENTION_EVERYONE: 1 << 17,
USE_EXTERNAL_EMOJIS: 1 << 18,
VIEW_GUILD_INSIGHTS: 1 << 19,
CONNECT: 1 << 20,
SPEAK: 1 << 21,
MUTE_MEMBERS: 1 << 22,
DEAFEN_MEMBERS: 1 << 23,
MOVE_MEMBERS: 1 << 24,
USE_VAD: 1 << 25,
CHANGE_NICKNAME: 1 << 26,
MANAGE_NICKNAMES: 1 << 27,
MANAGE_ROLES: 1 << 28,
MANAGE_WEBHOOKS: 1 << 29,
MANAGE_EMOJIS: 1 << 30,
};
any(permission: PermissionResolvable, checkAdmin = true) {
return (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) || super.any(permission);
}
/**
* Checks whether the bitfield has a permission, or multiple permissions.
* @param {PermissionResolvable} permission Permission(s) to check for
* @param {boolean} [checkAdmin=true] Whether to allow the administrator permission to override
* @returns {boolean}
*/
has(permission: PermissionResolvable, checkAdmin = true) {
return (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) || super.has(permission);
}
}

View File

@ -1,16 +1,20 @@
// @ts-nocheck
// github.com/discordjs/discord.js/blob/master/src/util/Snowflake.js
// https://github.com/discordjs/discord.js/blob/master/src/util/Snowflake.js
// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah
"use strict";
// Discord epoch (2015-01-01T00:00:00.000Z)
const EPOCH = 1420070400000;
let INCREMENT = 0;
/**
* A container for useful snowflake-related methods.
*/
class SnowflakeUtil {
export class Snowflake {
static EPOCH = 1420070400000;
static INCREMENT = 0;
static processId = 0;
static workerId = 0;
constructor() {
throw new Error(`The ${this.constructor.name} class may not be instantiated.`);
}
@ -91,11 +95,14 @@ class SnowflakeUtil {
`"timestamp" argument must be a number (received ${isNaN(timestamp) ? "NaN" : typeof timestamp})`
);
}
if (INCREMENT >= 4095) INCREMENT = 0;
const BINARY = `${(timestamp - EPOCH).toString(2).padStart(42, "0")}0000100000${(INCREMENT++)
if (Snowflake.INCREMENT >= 4095) Snowflake.INCREMENT = 0;
let workerBin = Snowflake.workerId.toString(2).padStart(5, "0");
let processBin = Snowflake.processId.toString(2).padStart(5, "0");
const BINARY = `${(timestamp - EPOCH)
.toString(2)
.padStart(12, "0")}`;
return SnowflakeUtil.binaryToID(BINARY);
.padStart(42, "0")}${workerBin}${processBin}${(Snowflake.INCREMENT++).toString(2).padStart(12, "0")}`;
return Snowflake.binaryToID(BINARY);
}
/**
@ -115,7 +122,7 @@ class SnowflakeUtil {
* @returns {DeconstructedSnowflake} Deconstructed snowflake
*/
static deconstruct(snowflake) {
const BINARY = SnowflakeUtil.idToBinary(snowflake).toString(2).padStart(64, "0");
const BINARY = Snowflake.idToBinary(snowflake).toString(2).padStart(64, "0");
const res = {
timestamp: parseInt(BINARY.substring(0, 42), 2) + EPOCH,
workerID: parseInt(BINARY.substring(42, 47), 2),
@ -141,5 +148,3 @@ class SnowflakeUtil {
return EPOCH;
}
}
module.exports = SnowflakeUtil;

22
src/util/UserFlags.ts Normal file
View File

@ -0,0 +1,22 @@
// https://github.com/discordjs/discord.js/blob/master/src/util/UserFlags.js
// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah
import { BitField } from "./BitField";
export class UserFlags extends BitField {
static FLAGS = {
DISCORD_EMPLOYEE: 1 << 0,
PARTNERED_SERVER_OWNER: 1 << 1,
HYPESQUAD_EVENTS: 1 << 2,
BUGHUNTER_LEVEL_1: 1 << 3,
HOUSE_BRAVERY: 1 << 6,
HOUSE_BRILLIANCE: 1 << 7,
HOUSE_BALANCE: 1 << 8,
EARLY_SUPPORTER: 1 << 9,
TEAM_USER: 1 << 10,
SYSTEM: 1 << 12,
BUGHUNTER_LEVEL_2: 1 << 14,
VERIFIED_BOT: 1 << 16,
EARLY_VERIFIED_BOT_DEVELOPER: 1 << 17,
};
}

121
src/util/instanceOf.ts Normal file
View File

@ -0,0 +1,121 @@
// different version of lambert-server instanceOf with discord error format
import { NextFunction, Request, Response } from "express";
import { Tuple } from "lambert-server";
const OPTIONAL_PREFIX = "$";
export function check(schema: any) {
return (req: Request, res: Response, next: NextFunction) => {
try {
const result = instanceOf(schema, req.body, { path: "body" });
if (result === true) return next();
throw result;
} catch (error) {
return res.status(400).json({ code: 50035, message: "Invalid Form Body", success: false, errors: error });
}
};
}
class FieldError extends Error {
constructor(public code: string, public message: string) {
super(message);
}
}
export function instanceOf(
type: any,
value: any,
{ path = "", optional = false, errors = {} }: { path?: string; optional?: boolean; errors?: any } = {}
): Boolean {
try {
if (!type) return true; // no type was specified
if (value == null) {
if (optional) return true;
throw new FieldError("BASE_TYPE_REQUIRED", `This field is required`);
}
switch (type) {
case String:
if (typeof value === "string") return true;
throw new FieldError("BASE_TYPE_STRING", `This field must be a string`);
case Number:
value = Number(value);
if (typeof value === "number" && !isNaN(value)) return true;
throw new FieldError("BASE_TYPE_NUMBER", `This field must be a number`);
case BigInt:
try {
value = BigInt(value);
if (typeof value === "bigint") return true;
} catch (error) {}
throw new FieldError("BASE_TYPE_BIGINT", `This field must be a bigint`);
case Boolean:
if (value == "true") value = true;
if (value == "false") value = false;
if (typeof value === "boolean") return true;
throw new FieldError("BASE_TYPE_BOOLEAN", `This field must be a boolean`);
}
if (typeof type === "object") {
if (type?.constructor?.name != "Object") {
if (type instanceof Tuple) {
if ((<Tuple>type).types.some((x) => instanceOf(x, value, { path, optional, errors }))) return true;
throw new FieldError("BASE_TYPE_CHOICES", `This field must be one of (${type.types})`);
}
if (value instanceof type) return true;
throw new FieldError("BASE_TYPE_CLASS", `This field must be an instance of ${type}`);
}
if (typeof value !== "object") throw new FieldError("BASE_TYPE_OBJECT", `This field must be a object`);
if (Array.isArray(type)) {
if (!Array.isArray(value)) throw new FieldError("BASE_TYPE_ARRAY", `This field must be an array`);
if (!type.length) return true; // type array didn't specify any type
return (
value.every((val, i) => {
errors[i] = {};
return (
instanceOf(type[0], val, { path: `${path}[${i}]`, optional, errors: errors[i] }) === true
);
}) || errors
);
}
const diff = Object.keys(value).missing(
Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x))
);
if (diff.length) throw new FieldError("UNKOWN_FIELD", `Unkown key ${diff}`);
return (
Object.keys(type).every((key) => {
let newKey = key;
const OPTIONAL = key.startsWith(OPTIONAL_PREFIX);
if (OPTIONAL) newKey = newKey.slice(OPTIONAL_PREFIX.length);
errors[key] = {};
return (
instanceOf(type[key], value[newKey], {
path: `${path}.${newKey}`,
optional: OPTIONAL,
errors: errors[key],
}) === true
);
}) || errors
);
} else if (typeof type === "number" || typeof type === "string" || typeof type === "boolean") {
if (value === type) return true;
throw new FieldError("BASE_TYPE_CONSTANT", `This field must be ${value}`);
} else if (typeof type === "bigint") {
if (BigInt(value) === type) return true;
throw new FieldError("BASE_TYPE_CONSTANT", `This field must be ${value}`);
}
return type == value;
} catch (error) {
let e = error as FieldError;
errors._errors = [{ message: e.message, code: e.code }];
return errors;
}
}

View File

@ -4,14 +4,14 @@
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"lib": ["ES2015", "DOM"] /* Specify library files to be included in the compilation. */,
"lib": ["ES2020"] /* Specify library files to be included in the compilation. */,
"allowJs": true /* Allow javascript files to be compiled. */,
"checkJs": true /* Report errors in .js files. */,
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true /* Generates corresponding '.d.ts' file. */,
"declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */,
"declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */,
"sourceMap": true /* Generates corresponding '.map' file. */,
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist/" /* Redirect output structure to the directory. */,
@ -27,7 +27,7 @@
/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
// "strictNullChecks": true, /* Enable strict null checks. */
"strictNullChecks": true /* Enable strict null checks. */,
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */,