Add ESLint (#941)

* Add eslint, switch to lint-staged for precommit

* Fix all ESLint errors

* Update GH workflow to check prettier and eslint
This commit is contained in:
Madeline
2023-01-20 18:10:47 +11:00
committed by GitHub
parent 0c815fde91
commit 084dc0be08
157 changed files with 737 additions and 666 deletions
+1 -1
View File
@@ -18,6 +18,6 @@
export class GifConfiguration {
enabled: boolean = true;
provider: "tenor" = "tenor"; // more coming soon
provider = "tenor" as const; // more coming soon
apiKey?: string = "LIVDSRZULELA";
}
+3 -5
View File
@@ -44,16 +44,14 @@ export class DmChannelDTO {
obj.type = channel.type;
obj.recipients = (
await Promise.all(
channel
.recipients!.filter(
(r) => !excluded_recipients.includes(r.user_id),
)
channel.recipients
?.filter((r) => !excluded_recipients.includes(r.user_id))
.map(async (r) => {
return await User.findOneOrFail({
where: { id: r.user_id },
select: PublicUserProjection,
});
}),
}) || [],
)
).map((u) => new MinimalPublicUserDTO(u));
return obj;
+8 -8
View File
@@ -23,7 +23,7 @@ export interface IReadyGuildDTO {
channels: Channel[];
data_mode: string; // what is this
emojis: Emoji[];
guild_scheduled_events: any[];
guild_scheduled_events: unknown[]; // TODO
id: string;
large: boolean | undefined;
lazy: boolean;
@@ -57,12 +57,12 @@ export interface IReadyGuildDTO {
max_video_channel_users: number | undefined;
max_members: number | undefined;
nsfw_level: number | undefined;
hub_type?: any | null; // ????
hub_type?: unknown | null; // ????
};
roles: Role[];
stage_instances: any[];
stage_instances: unknown[];
stickers: Sticker[];
threads: any[];
threads: unknown[];
version: string;
}
@@ -71,7 +71,7 @@ export class ReadyGuildDTO implements IReadyGuildDTO {
channels: Channel[];
data_mode: string; // what is this
emojis: Emoji[];
guild_scheduled_events: any[];
guild_scheduled_events: unknown[];
id: string;
large: boolean | undefined;
lazy: boolean;
@@ -105,12 +105,12 @@ export class ReadyGuildDTO implements IReadyGuildDTO {
max_video_channel_users: number | undefined;
max_members: number | undefined;
nsfw_level: number | undefined;
hub_type?: any | null; // ????
hub_type?: unknown | null; // ????
};
roles: Role[];
stage_instances: any[];
stage_instances: unknown[];
stickers: Sticker[];
threads: any[];
threads: unknown[];
version: string;
constructor(guild: Guild) {
+3 -11
View File
@@ -16,16 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {
Column,
Entity,
JoinColumn,
ManyToOne,
OneToOne,
RelationId,
} from "typeorm";
import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { Team } from "./Team";
import { User } from "./User";
@@ -44,7 +36,7 @@ export class Application extends BaseClass {
summary: string = "";
@Column({ type: "simple-json", nullable: true })
type?: any;
type?: object; // TODO: this type is bad
@Column()
hook: boolean = true;
@@ -176,6 +168,6 @@ export interface ApplicationCommandInteractionData {
export interface ApplicationCommandInteractionDataOption {
name: string;
value?: any;
value?: unknown;
options?: ApplicationCommandInteractionDataOption[];
}
+2 -2
View File
@@ -173,8 +173,8 @@ export interface AuditLogChangeValue {
explicit_content_filter?: number;
default_message_notifications?: number;
vanity_url_code?: string;
$add?: {}[];
$remove?: {}[];
$add?: object[]; // TODO: These types are bad.
$remove?: object[];
prune_delete_days?: number;
widget_enabled?: boolean;
widget_channel_id?: string;
+2 -2
View File
@@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
import { BaseClass } from "./BaseClass";
import { User } from "./User";
import crypto from "crypto";
@@ -38,7 +38,7 @@ export class BackupCode extends BaseClass {
}
export function generateMfaBackupCodes(user_id: string) {
let backup_codes: BackupCode[] = [];
const backup_codes: BackupCode[] = [];
for (let i = 0; i < 10; i++) {
const code = BackupCode.create({
user: { id: user_id },
+7 -2
View File
@@ -29,7 +29,7 @@ import { getDatabase } from "../util/Database";
import { OrmUtils } from "../imports/OrmUtils";
export class BaseClassWithoutId extends BaseEntity {
private get construct(): any {
private get construct() {
return this.constructor;
}
@@ -37,19 +37,24 @@ export class BaseClassWithoutId extends BaseEntity {
return getDatabase()?.getMetadata(this.construct);
}
assign(props: any) {
assign(props: object) {
OrmUtils.mergeDeep(this, props);
return this;
}
// TODO: fix eslint
// eslint-disable-next-line @typescript-eslint/no-explicit-any
toJSON(): any {
return Object.fromEntries(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/no-non-null-assertion
this.metadata!.columns // @ts-ignore
.map((x) => [x.propertyName, this[x.propertyName]])
.concat(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.metadata.relations.map((x) => [
x.propertyName,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this[x.propertyName],
]),
+7 -7
View File
@@ -35,7 +35,6 @@ import {
Snowflake,
trimSpecial,
InvisibleCharacters,
ChannelTypes,
} from "../util";
import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces";
import { Recipient } from "./Recipient";
@@ -219,7 +218,7 @@ export class Channel extends BaseClass {
!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") &&
channel.name
) {
for (var character of InvisibleCharacters)
for (const character of InvisibleCharacters)
if (channel.name.includes(character))
throw new HTTPError(
"Channel name cannot include invalid characters",
@@ -237,7 +236,7 @@ export class Channel extends BaseClass {
403,
);
if (channel.name.match(/\-\-+/g))
if (channel.name.match(/--+/g))
throw new HTTPError(
"Channel name cannot include multiple adjacent dashes.",
403,
@@ -344,8 +343,9 @@ export class Channel extends BaseClass {
relations: ["channel", "channel.recipients"],
});
for (let ur of userRecipients) {
let re = ur.channel.recipients!.map((r) => r.user_id);
for (const ur of userRecipients) {
if (!ur.channel.recipients) continue;
const re = ur.channel.recipients.map((r) => r.user_id);
if (re.length === channelRecipients.length) {
if (containsAll(re, channelRecipients)) {
if (channel == null) {
@@ -380,8 +380,8 @@ export class Channel extends BaseClass {
const channel_dto = await DmChannelDTO.from(channel);
if (type === ChannelType.GROUP_DM) {
for (let recipient of channel.recipients!) {
if (type === ChannelType.GROUP_DM && channel.recipients) {
for (const recipient of channel.recipients) {
await emitEvent({
event: "CHANNEL_CREATE",
data: channel_dto.excludedRecipients([recipient.user_id]),
+4 -2
View File
@@ -20,8 +20,10 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { User } from "./User";
export interface PublicConnectedAccount
extends Pick<ConnectedAccount, "name" | "type" | "verified"> {}
export type PublicConnectedAccount = Pick<
ConnectedAccount,
"name" | "type" | "verified"
>;
@Entity("connected_accounts")
export class ConnectedAccount extends BaseClass {
-1
View File
@@ -20,7 +20,6 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { User } from ".";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { Role } from "./Role";
@Entity("emojis")
export class Emoji extends BaseClass {
+1 -25
View File
@@ -16,32 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {
Column,
Entity,
JoinColumn,
ManyToOne,
OneToMany,
RelationId,
} from "typeorm";
import { Column, Entity } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { PublicUserProjection, User } from "./User";
import { HTTPError } from "lambert-server";
import {
containsAll,
emitEvent,
getPermission,
Snowflake,
trimSpecial,
InvisibleCharacters,
} from "../util";
import { BitField, BitFieldResolvable, BitFlag } from "../util/BitField";
import { Recipient } from "./Recipient";
import { Message } from "./Message";
import { ReadState } from "./ReadState";
import { Invite } from "./Invite";
import { DmChannelDTO } from "../dtos";
@Entity("security_settings")
export class SecuritySettings extends BaseClass {
+3 -5
View File
@@ -20,10 +20,8 @@ import {
Column,
Entity,
JoinColumn,
ManyToMany,
ManyToOne,
OneToMany,
OneToOne,
RelationId,
} from "typeorm";
import { Config, handleFile, Snowflake } from "..";
@@ -370,12 +368,12 @@ export class Guild extends BaseClass {
}
});
for (const channel of body.channels?.sort((a, b) =>
for (const channel of body.channels.sort((a) =>
a.parent_id ? 1 : -1,
)) {
var id = ids.get(channel.id) || Snowflake.generate();
const id = ids.get(channel.id) || Snowflake.generate();
var parent_id = ids.get(channel.parent_id);
const parent_id = ids.get(channel.parent_id);
await Channel.createChannel(
{ ...channel, guild_id, id, parent_id },
+16 -9
View File
@@ -33,7 +33,7 @@ import {
RelationId,
} from "typeorm";
import { Guild } from "./Guild";
import { Config, emitEvent, FieldErrors } from "../util";
import { Config, emitEvent } from "../util";
import {
GuildCreateEvent,
GuildDeleteEvent,
@@ -212,12 +212,16 @@ export class Member extends BaseClassWithoutId {
}
static async addRole(user_id: string, guild_id: string, role_id: string) {
const [member, role] = await Promise.all([
const [member] = await Promise.all([
Member.findOneOrFail({
where: { id: user_id, guild_id },
relations: ["user", "roles"], // we don't want to load the role objects just the ids
//@ts-ignore
select: ["index", "roles.id"], // TODO fix type
select: {
index: true,
roles: {
id: true,
},
},
}),
Role.findOneOrFail({
where: { id: role_id, guild_id },
@@ -249,8 +253,12 @@ export class Member extends BaseClassWithoutId {
Member.findOneOrFail({
where: { id: user_id, guild_id },
relations: ["user", "roles"], // we don't want to load the role objects just the ids
//@ts-ignore
select: ["roles.id", "index"], // TODO: fix type
select: {
index: true,
roles: {
id: true,
},
},
}),
await Role.findOneOrFail({ where: { id: role_id, guild_id } }),
]);
@@ -327,7 +335,7 @@ export class Member extends BaseClassWithoutId {
guild_id,
user: {
sessions: {
status: Not("invisible" as "invisible"), // lol typescript?
status: Not("invisible" as const), // lol typescript?
},
},
},
@@ -506,8 +514,7 @@ export const PublicMemberProjection: PublicMemberKeys[] = [
"premium_since",
];
// @ts-ignore
export type PublicMember = Pick<Member, Omit<PublicMemberKeys, "roles">> & {
export type PublicMember = Omit<Pick<Member, PublicMemberKeys>, "roles"> & {
user: PublicUser;
roles: string[]; // only role ids not objects
};
-1
View File
@@ -26,7 +26,6 @@ import {
} from "typeorm";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
import { Message } from "./Message";
import { User } from "./User";
// for read receipts
-1
View File
@@ -22,7 +22,6 @@ import {
JoinColumn,
ManyToOne,
OneToMany,
OneToOne,
RelationId,
} from "typeorm";
import { Sticker } from ".";
-1
View File
@@ -20,7 +20,6 @@ import {
Column,
Entity,
JoinColumn,
ManyToMany,
ManyToOne,
OneToMany,
RelationId,
+11 -8
View File
@@ -17,8 +17,6 @@
*/
import {
BeforeInsert,
BeforeUpdate,
Column,
Entity,
FindOneOptions,
@@ -34,6 +32,7 @@ import { Member } from "./Member";
import { UserSettings } from "./UserSettings";
import { Session } from "./Session";
import { Config, FieldErrors, Snowflake, trimSpecial, adjustEmail } from "..";
import { Request } from "express";
export enum PublicUserEnum {
username,
@@ -80,7 +79,7 @@ export const PrivateUserProjection = [
// Private user data that should never get sent to the client
export type PublicUser = Pick<User, PublicUserKeys>;
export interface UserPublic extends Pick<User, PublicUserKeys> {}
export type UserPublic = Pick<User, PublicUserKeys>;
export interface UserPrivate extends Pick<User, PrivateUserKeys> {
locale: string;
@@ -266,6 +265,7 @@ export class User extends BaseClass {
}
toPublicUser() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const user: any = {};
PublicUserProjection.forEach((x) => {
user[x] = this[x];
@@ -277,6 +277,7 @@ export class User extends BaseClass {
return await User.findOneOrFail({
where: { id: user_id },
...opts,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
select: [...PublicUserProjection, ...(opts?.select || [])], // TODO: fix
});
@@ -328,7 +329,6 @@ export class User extends BaseClass {
email,
username,
password,
date_of_birth,
id,
req,
}: {
@@ -337,7 +337,7 @@ export class User extends BaseClass {
email?: string;
date_of_birth?: Date; // "2000-04-03"
id?: string;
req?: any;
req?: Request;
}) {
// trim special uf8 control characters -> Backspace, Newline, ...
username = trimSpecial(username);
@@ -348,7 +348,8 @@ export class User extends BaseClass {
throw FieldErrors({
username: {
code: "USERNAME_TOO_MANY_USERS",
message: req.t("auth:register.USERNAME_TOO_MANY_USERS"),
message:
req?.t("auth:register.USERNAME_TOO_MANY_USERS") || "",
},
});
}
@@ -357,7 +358,7 @@ export class User extends BaseClass {
// appearently discord doesn't save the date of birth and just calculate if nsfw is allowed
// if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false
const language =
req.language === "en" ? "en-US" : req.language || "en-US";
req?.language === "en" ? "en-US" : req?.language || "en-US";
const settings = UserSettings.create({
locale: language,
@@ -386,7 +387,9 @@ export class User extends BaseClass {
setImmediate(async () => {
if (Config.get().guild.autoJoin.enabled) {
for (const guild of Config.get().guild.autoJoin.guilds || []) {
await Member.addToGuild(user.id, guild).catch((e) => {});
await Member.addToGuild(user.id, guild).catch((e) =>
console.error("[Autojoin]", e),
);
}
}
});
+5 -20
View File
@@ -1,25 +1,10 @@
/*
Fosscord: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 Fosscord and Fosscord Contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
//source: https://github.com/typeorm/typeorm/blob/master/src/util/OrmUtils.ts
// source: https://github.com/typeorm/typeorm/blob/master/src/util/OrmUtils.ts
// Copyright (c) 2015-2022 TypeORM. http://typeorm.github.io
/* eslint-disable @typescript-eslint/no-explicit-any */
// @fc-license-skip
export class OrmUtils {
// Checks if it's an object made by Object.create(null), {} or new Object()
private static isPlainObject(item: any) {
private static isPlainObject(item: unknown) {
if (item === null || item === undefined) {
return false;
}
+7 -5
View File
@@ -39,6 +39,7 @@ import {
Presence,
UserSettings,
IReadyGuildDTO,
ReadState,
} from "@fosscord/util";
export interface Event {
@@ -47,6 +48,7 @@ export interface Event {
channel_id?: string;
created_at?: Date;
event: EVENT;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data?: any;
}
@@ -103,12 +105,12 @@ export interface ReadyEventData {
[number, [[number, [number, number]]]],
{ b: number; k: bigint[] }[],
][];
guild_join_requests?: any[]; // ? what is this? this is new
guild_join_requests?: unknown[]; // ? what is this? this is new
shard?: [number, number];
user_settings?: UserSettings;
relationships?: PublicRelationship[]; // TODO
read_state: {
entries: any[]; // TODO
entries: ReadState[]; // TODO
partial: boolean;
version: number;
};
@@ -124,7 +126,7 @@ export interface ReadyEventData {
merged_members?: PublicMember[][];
// probably all users who the user is in contact with
users?: PublicUser[];
sessions: any[];
sessions: unknown[];
}
export interface ReadyEvent extends Event {
@@ -178,7 +180,7 @@ export interface GuildCreateEvent extends Event {
joined_at: Date;
// TODO: add them to guild
guild_scheduled_events: never[];
guild_hashes: {};
guild_hashes: unknown;
presences: never[];
stage_instances: never[];
threads: never[];
@@ -408,7 +410,7 @@ export interface TypingStartEvent extends Event {
export interface UserUpdateEvent extends Event {
event: "USER_UPDATE";
data: User;
data: Omit<User, "data">;
}
export interface UserDeleteEvent extends Event {
+1 -1
View File
@@ -21,7 +21,7 @@ import { AllowedMentions, Embed } from "../entities/Message";
export interface Interaction {
id: string;
type: InteractionType;
data?: {};
data?: object; // TODO typing
guild_id: string;
channel_id: string;
member_id: string;
@@ -18,5 +18,4 @@
import { ChannelPermissionOverwrite } from "@fosscord/util";
export interface ChannelPermissionOverwriteSchema
extends ChannelPermissionOverwrite {}
export type ChannelPermissionOverwriteSchema = ChannelPermissionOverwrite;
+7 -5
View File
@@ -18,6 +18,8 @@
import { ActivitySchema } from "@fosscord/util";
// TODO: Need a way to allow camalCase and pascal_case without just duplicating the schema
export const IdentifySchema = {
token: String,
$intents: BigInt, // discord uses a Integer for bitfields we use bigints tho. | instanceOf will automatically convert the Number to a BigInt
@@ -98,7 +100,7 @@ export interface IdentifySchema {
referring_domain_current?: string;
release_channel?: "stable" | "dev" | "ptb" | "canary";
client_build_number?: number;
client_event_source?: any;
client_event_source?: string;
client_version?: string;
system_locale?: string;
};
@@ -111,23 +113,23 @@ export interface IdentifySchema {
guild_subscriptions?: boolean;
capabilities?: number;
client_state?: {
guild_hashes?: any;
guild_hashes?: unknown;
highest_last_message_id?: string | number;
read_state_version?: number;
user_guild_settings_version?: number;
user_settings_version?: number;
useruser_guild_settings_version?: number;
private_channels_version?: number;
guild_versions?: any;
guild_versions?: unknown;
api_code_version?: number;
};
clientState?: {
guildHashes?: any;
guildHashes?: unknown;
highestLastMessageId?: string | number;
readStateVersion?: number;
userGuildSettingsVersion?: number;
useruserGuildSettingsVersion?: number;
guildVersions?: any;
guildVersions?: unknown;
apiCodeVersion?: number;
};
v?: number;
+4 -4
View File
@@ -22,8 +22,8 @@ export interface LazyRequestSchema {
activities?: boolean;
threads?: boolean;
typing?: true;
members?: any[];
thread_member_lists?: any[];
members?: unknown[];
thread_member_lists?: unknown[];
}
export const LazyRequestSchema = {
@@ -32,6 +32,6 @@ export const LazyRequestSchema = {
$channels: Object,
$typing: Boolean,
$threads: Boolean,
$members: [] as any[],
$thread_member_lists: [] as any[],
$members: [] as unknown[],
$thread_member_lists: [] as unknown[],
};
+7 -2
View File
@@ -18,6 +18,11 @@
import { Embed } from "@fosscord/util";
type Attachment = {
id: string;
filename: string;
};
export interface MessageCreateSchema {
type?: number;
content?: string;
@@ -41,11 +46,11 @@ export interface MessageCreateSchema {
fail_if_not_exists?: boolean;
};
payload_json?: string;
file?: any;
file?: { filename: string };
/**
TODO: we should create an interface for attachments
TODO: OpenWAAO<-->attachment-style metadata conversion
**/
attachments?: any[];
attachments?: Attachment[];
sticker_ids?: string[];
}
+1 -1
View File
@@ -18,4 +18,4 @@
import { UserSettings } from "@fosscord/util";
export interface UserSettingsSchema extends Partial<UserSettings> {}
export type UserSettingsSchema = Partial<UserSettings>;
+6 -4
View File
@@ -45,7 +45,7 @@ export const ajv = new Ajv({
addFormats(ajv);
export function validateSchema<G>(schema: string, data: G): G {
export function validateSchema<G extends object>(schema: string, data: G): G {
const valid = ajv.validate(schema, normalizeBody(data));
if (!valid) throw ajv.errors;
return data;
@@ -55,13 +55,13 @@ export function validateSchema<G>(schema: string, data: G): G {
// this removes null values as ajv doesn't treat them as undefined
// normalizeBody allows to handle circular structures without issues
// taken from https://github.com/serverless/serverless/blob/master/lib/classes/ConfigSchemaHandler/index.js#L30 (MIT license)
export const normalizeBody = (body: any = {}) => {
export const normalizeBody = (body: object = {}) => {
const normalizedObjectsSet = new WeakSet();
const normalizeObject = (object: any) => {
const normalizeObject = (object: object) => {
if (normalizedObjectsSet.has(object)) return;
normalizedObjectsSet.add(object);
if (Array.isArray(object)) {
for (const [index, value] of object.entries()) {
for (const [, value] of object.entries()) {
if (typeof value === "object") normalizeObject(value);
}
} else {
@@ -75,6 +75,8 @@ export const normalizeBody = (body: any = {}) => {
key === "discovery_splash"
)
continue;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
delete object[key];
} else if (typeof value === "object") {
normalizeObject(value);
+3 -1
View File
@@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export function containsAll(arr: any[], target: any[]) {
// TODO: remove this function.
export function containsAll(arr: unknown[], target: unknown[]) {
return target.every((v) => arr.includes(v));
}
+3 -2
View File
@@ -36,7 +36,7 @@ export function enableAutoUpdate(opts: {
downloadType?: "zip";
}) {
if (!opts.checkInterval) return;
var interval = 1000 * 60 * 60 * 24;
const interval = 1000 * 60 * 60 * 24;
if (typeof opts.checkInterval === "number")
opts.checkInterval = 1000 * interval;
@@ -70,6 +70,7 @@ export function enableAutoUpdate(opts: {
});
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function download(url: string, dir: string) {
try {
// TODO: use file stream instead of buffer (to prevent crash because of high memory usage for big files)
@@ -99,7 +100,7 @@ async function getLatestVersion(url: string) {
try {
const agent = new ProxyAgent();
const response = await fetch(url, { agent });
const content = (await response.json()) as any; // TODO: types
const content = await response.json();
return content.version;
} catch (error) {
throw new Error("[Auto update] check failed for " + url);
+3 -1
View File
@@ -6,7 +6,7 @@
export type BitFieldResolvable =
| number
| BigInt
| bigint
| BitField
| string
| BitFieldResolvable[];
@@ -135,6 +135,7 @@ export class BitField {
* @returns {number}
*/
static resolve(bit: BitFieldResolvable = BigInt(0)): bigint {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const FLAGS = this.FLAGS || this.constructor?.FLAGS;
@@ -152,6 +153,7 @@ export class BitField {
if (bit instanceof BitField) return bit.bitfield;
if (Array.isArray(bit)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const resolve = this.constructor?.resolve || this.resolve;
return bit
+7 -3
View File
@@ -24,8 +24,8 @@ import { ConfigValue } from "../config";
// TODO: yaml instead of json
const overridePath = process.env.CONFIG_PATH ?? "";
var config: ConfigValue;
var pairs: ConfigEntity[];
let config: ConfigValue;
let pairs: ConfigEntity[];
// TODO: use events to inform about config updates
// Config keys are separated with _
@@ -84,6 +84,8 @@ export const Config = {
};
function applyConfig(val: ConfigValue) {
// TODO: typings
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function apply(obj: any, key = ""): Promise<any> {
if (typeof obj === "object" && obj !== null)
return Promise.all(
@@ -107,7 +109,9 @@ function applyConfig(val: ConfigValue) {
}
function pairsToConfig(pairs: ConfigEntity[]) {
var value: any = {};
// TODO: typings
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const value: any = {};
pairs.forEach((p) => {
const keys = p.key.split("_");
+1 -1
View File
@@ -1125,7 +1125,7 @@ export const MembershipStates = ["INSERTED", "INVITED", "ACCEPTED"];
export const WebhookTypes = ["Custom", "Incoming", "Channel Follower"];
function keyMirror(arr: string[]) {
let tmp = Object.create(null);
const tmp = Object.create(null);
for (const value of arr) tmp[value] = value;
return tmp;
}
+3 -2
View File
@@ -25,14 +25,14 @@ import path from "path";
// UUID extension option is only supported with postgres
// We want to generate all id's with Snowflakes that's why we have our own BaseEntity class
var dbConnection: DataSource | undefined;
let dbConnection: DataSource | undefined;
// For typeorm cli
if (!process.env) {
config();
}
let dbConnectionString =
const dbConnectionString =
process.env.DATABASE || path.join(process.cwd(), "database.db");
const DatabaseType = dbConnectionString.includes("://")
@@ -41,6 +41,7 @@ const DatabaseType = dbConnectionString.includes("://")
const isSqlite = DatabaseType.includes("sqlite");
const DataSourceOptions = new DataSource({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore type 'string' is not 'mysql' | 'sqlite' | 'mariadb' | etc etc
type: DatabaseType,
charset: "utf8mb4",
+17 -15
View File
@@ -17,27 +17,29 @@
*/
export const EMAIL_REGEX =
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
export function adjustEmail(email?: string): string | undefined {
if (!email) return email;
// body parser already checked if it is a valid email
const parts = <RegExpMatchArray>email.match(EMAIL_REGEX);
// @ts-ignore
if (!parts || parts.length < 5) return undefined;
const domain = parts[5];
const user = parts[1];
// TODO: check accounts with uncommon email domains
if (domain === "gmail.com" || domain === "googlemail.com") {
// replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator
let v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com";
}
if (domain === "google.com") {
// replace .dots and +alternatives -> Google Staff GMail Dot Trick
let v = user.replace(/[.]|(\+.*)/g, "") + "@google.com";
}
return email;
// // TODO: The below code doesn't actually do anything.
// const domain = parts[5];
// const user = parts[1];
// // TODO: check accounts with uncommon email domains
// if (domain === "gmail.com" || domain === "googlemail.com") {
// // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator
// const v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com";
// }
// if (domain === "google.com") {
// // replace .dots and +alternatives -> Google Staff GMail Dot Trick
// const v = user.replace(/[.]|(\+.*)/g, "") + "@google.com";
// }
// return email;
}
+16 -14
View File
@@ -55,6 +55,7 @@ export async function emitEvent(payload: Omit<Event, "created_at">) {
export async function initEvent() {
await RabbitMQ.init(); // does nothing if rabbitmq is not setup
if (RabbitMQ.connection) {
// empty on purpose?
} else {
// use event emitter
// use process messages
@@ -62,9 +63,9 @@ export async function initEvent() {
}
export interface EventOpts extends Event {
acknowledge?: Function;
acknowledge?: () => unknown;
channel?: Channel;
cancel: Function;
cancel: (id?: string) => unknown;
}
export interface ListenEventOpts {
@@ -80,17 +81,18 @@ export interface ProcessEvent {
export async function listenEvent(
event: string,
callback: (event: EventOpts) => any,
callback: (event: EventOpts) => unknown,
opts?: ListenEventOpts,
) {
if (RabbitMQ.connection) {
return await rabbitListen(
// @ts-ignore
opts?.channel || RabbitMQ.channel,
event,
callback,
{ acknowledge: opts?.acknowledge },
);
const channel = opts?.channel || RabbitMQ.channel;
if (!channel)
throw new Error(
"[Events] An event was sent without an associated channel",
);
return await rabbitListen(channel, event, callback, {
acknowledge: opts?.acknowledge,
});
} else if (process.env.EVENT_TRANSMISSION === "process") {
const cancel = async () => {
process.removeListener("message", listener);
@@ -103,13 +105,13 @@ export async function listenEvent(
callback({ ...msg.event, cancel });
};
//@ts-ignore apparently theres no function addListener with this signature
process.addListener("message", listener);
// TODO: assert the type is correct?
process.addListener("message", (msg) => listener(msg as ProcessEvent));
process.setMaxListeners(process.getMaxListeners() + 1);
return cancel;
} else {
const listener = (opts: any) => callback({ ...opts, cancel });
const listener = (opts: EventOpts) => callback({ ...opts, cancel });
const cancel = async () => {
events.removeListener(event, listener);
events.setMaxListeners(events.getMaxListeners() - 1);
@@ -124,7 +126,7 @@ export async function listenEvent(
async function rabbitListen(
channel: Channel,
id: string,
callback: (event: EventOpts) => any,
callback: (event: EventOpts) => unknown,
opts?: { acknowledge?: boolean },
) {
await channel.assertExchange(id, "fanout", { durable: false });
+1 -1
View File
@@ -42,7 +42,7 @@ export class FieldError extends Error {
constructor(
public code: string | number,
public message: string,
public errors?: any,
public errors?: object, // TODO: I don't like this typing.
) {
super(message);
}
+11 -26
View File
@@ -12,14 +12,7 @@ import {
import { BitField } from "./BitField";
import "missing-native-js-functions";
import { BitFieldResolvable, BitFlag } from "./BitField";
var HTTPError: any;
try {
HTTPError = require("lambert-server").HTTPError;
} catch (e) {
HTTPError = Error;
}
import { HTTPError } from "lambert-server";
export type PermissionResolvable =
| bigint
@@ -31,7 +24,7 @@ export type PermissionResolvable =
type PermissionString = keyof typeof Permissions.FLAGS;
// BigInt doesn't have a bit limit (https://stackoverflow.com/questions/53335545/whats-the-biggest-bigint-value-in-js-as-per-spec)
const CUSTOM_PERMISSION_OFFSET = BigInt(1) << BigInt(64); // 27 permission bits left for discord to add new ones
// const CUSTOM_PERMISSION_OFFSET = BigInt(1) << BigInt(64); // 27 permission bits left for discord to add new ones
export class Permissions extends BitField {
cache: PermissionCache = {};
@@ -114,7 +107,6 @@ export class Permissions extends BitField {
*/
hasThrow(permission: PermissionResolvable) {
if (this.has(permission) && this.has("VIEW_CHANNEL")) return true;
// @ts-ignore
throw new HTTPError(
`You are missing the following permissions ${permission}`,
403,
@@ -177,11 +169,11 @@ export class Permissions extends BitField {
}) {
if (user.id === "0") return new Permissions("ADMINISTRATOR"); // system user id
let roles = guild.roles.filter((x) => user.roles.includes(x.id));
const roles = guild.roles.filter((x) => user.roles.includes(x.id));
let permission = Permissions.rolePermission(roles);
if (channel?.overwrites) {
let overwrites = channel.overwrites.filter((x) => {
const overwrites = channel.overwrites.filter((x) => {
if (x.type === 0 && user.roles.includes(x.id)) return true;
if (x.type === 1 && x.id == user.id) return true;
return false;
@@ -244,9 +236,9 @@ export async function getPermission(
} = {},
) {
if (!user_id) throw new HTTPError("User not found");
var channel: Channel | undefined;
var member: Member | undefined;
var guild: Guild | undefined;
let channel: Channel | undefined;
let member: Member | undefined;
let guild: Guild | undefined;
if (channel_id) {
channel = await Channel.findOneOrFail({
@@ -258,7 +250,6 @@ export async function getPermission(
"permission_overwrites",
"owner_id",
"guild_id",
// @ts-ignore
...(opts.channel_select || []),
],
});
@@ -268,12 +259,7 @@ export async function getPermission(
if (guild_id) {
guild = await Guild.findOneOrFail({
where: { id: guild_id },
select: [
"id",
"owner_id",
// @ts-ignore
...(opts.guild_select || []),
],
select: ["id", "owner_id", ...(opts.guild_select || [])],
relations: opts.guild_relations,
});
if (guild.owner_id === user_id)
@@ -285,17 +271,16 @@ export async function getPermission(
// select: [
// "id", // TODO: Bug in typeorm? adding these selects breaks the query.
// "roles",
// @ts-ignore
// ...(opts.member_select || []),
// ],
});
}
let recipient_ids: any = channel?.recipients?.map((x) => x.user_id);
if (!recipient_ids?.length) recipient_ids = null;
let recipient_ids = channel?.recipients?.map((x) => x.user_id);
if (!recipient_ids?.length) recipient_ids = undefined;
// TODO: remove guild.roles and convert recipient_ids to recipients
var permission = Permissions.finalPermission({
const permission = Permissions.finalPermission({
user: {
id: user_id,
roles: member?.roles.map((x) => x.id) || [],
+2 -10
View File
@@ -20,14 +20,7 @@ import { BitField } from "./BitField";
import "missing-native-js-functions";
import { BitFieldResolvable, BitFlag } from "./BitField";
import { User } from "../entities";
var HTTPError: any;
try {
HTTPError = require("lambert-server").HTTPError;
} catch (e) {
HTTPError = Error;
}
import { HTTPError } from "lambert-server";
export type RightResolvable =
| bigint
@@ -118,7 +111,6 @@ export class Rights extends BitField {
hasThrow(permission: RightResolvable) {
if (this.has(permission)) return true;
// @ts-ignore
throw new HTTPError(
`You are missing the following rights ${permission}`,
403,
@@ -137,6 +129,6 @@ export async function getRights(
in_behalf?: (keyof User)[];
} = {} **/
) {
let user = await User.findOneOrFail({ where: { id: user_id } });
const user = await User.findOneOrFail({ where: { id: user_id } });
return new Rights(user.rights);
}
+4 -2
View File
@@ -34,7 +34,7 @@ export const Sentry = {
Config.get().sentry;
if (!enabled) return;
if (!!SentryNode.getCurrentHub().getClient()) return; // we've already initialised sentry
if (SentryNode.getCurrentHub().getClient()) return; // we've already initialised sentry
console.log("[Sentry] Enabling sentry...");
@@ -60,7 +60,7 @@ export const Sentry = {
environment,
});
SentryNode.addGlobalEventProcessor((event, hint) => {
SentryNode.addGlobalEventProcessor((event) => {
if (event.transaction) {
// Rewrite things that look like IDs to `:id` for sentry
event.transaction = event.transaction
@@ -112,6 +112,8 @@ export const Sentry = {
errorHandlersUsed = true;
app.use(SentryNode.Handlers.errorHandler());
// The typings for this are broken?
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars
app.use(function onError(err: any, req: any, res: any, next: any) {
res.statusCode = 500;
res.end(res.sentry + "\n");
+5 -4
View File
@@ -1,3 +1,4 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-nocheck
import * as cluster from "cluster";
@@ -87,10 +88,10 @@ export class Snowflake {
static generateWorkerProcess() {
// worker process - returns a number
var time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22);
var worker = Snowflake.workerId << 17n;
var process = Snowflake.processId << 12n;
var increment = Snowflake.INCREMENT++;
const time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22);
const worker = Snowflake.workerId << 17n;
const process = Snowflake.processId << 12n;
const increment = Snowflake.INCREMENT++;
return BigInt(time | worker | process | increment);
}
+1 -2
View File
@@ -19,7 +19,6 @@
import { SPECIAL_CHAR } from "./Regex";
export function trimSpecial(str?: string): string {
// @ts-ignore
if (!str) return;
if (!str) return "";
return str.replace(SPECIAL_CHAR, "").trim();
}
+22 -3
View File
@@ -22,7 +22,15 @@ import { User } from "../entities";
export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] };
export function checkToken(token: string, jwtSecret: string): Promise<any> {
export type UserTokenData = {
user: User;
decoded: { id: string; iat: number };
};
export function checkToken(
token: string,
jwtSecret: string,
): Promise<UserTokenData> {
return new Promise((res, rej) => {
token = token.replace("Bot ", "");
token = token.replace("Bearer ", "");
@@ -31,24 +39,35 @@ export function checkToken(token: string, jwtSecret: string): Promise<any> {
as we don't really have separate pathways for bots
**/
jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded: any) => {
jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded) => {
if (err || !decoded) return rej("Invalid Token");
if (
typeof decoded == "string" ||
!("id" in decoded) ||
!decoded.iat
)
return rej("Invalid Token"); // will never happen, just for typings.
const user = await User.findOne({
where: { id: decoded.id },
select: ["data", "bot", "disabled", "deleted", "rights"],
});
if (!user) return rej("Invalid Token");
// we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds
if (
decoded.iat * 1000 <
new Date(user.data.valid_tokens_since).setSeconds(0, 0)
)
return rej("Invalid Token");
if (user.disabled) return rej("User disabled");
if (user.deleted) return rej("User not found");
return res({ decoded, user });
// Using as here because we assert `id` and `iat` are in decoded.
// TS just doesn't want to assume its there, though.
return res({ decoded, user } as UserTokenData);
});
});
}
+1 -1
View File
@@ -22,7 +22,7 @@ import { Server, traverseDirectory } from "lambert-server";
const extension =
Symbol.for("ts-node.register.instance") in process ? "ts" : "js";
const DEFAULT_FILTER = new RegExp("^([^.].*)(?<!\.d).(" + extension + ")$");
const DEFAULT_FILTER = new RegExp("^([^.].*)(?<!\\.d).(" + extension + ")$");
export function registerRoutes(server: Server, root: string) {
return traverseDirectory(
+2 -2
View File
@@ -24,7 +24,8 @@ import { Config } from "./Config";
export async function uploadFile(
path: string,
file?: Express.Multer.File,
// These are the only props we use, don't need to enforce the full type.
file?: Pick<Express.Multer.File, "mimetype" | "originalname" | "buffer">,
): Promise<Attachment> {
if (!file?.buffer) throw new HTTPError("Missing file in body");
@@ -60,7 +61,6 @@ export async function handleFile(
const mimetype = body.split(":")[1].split(";")[0];
const buffer = Buffer.from(body.split(",")[1], "base64");
// @ts-ignore
const { id } = await uploadFile(path, {
buffer,
mimetype,