mirror of
https://github.com/spacebarchat/server.git
synced 2026-05-24 08:05:29 +00:00
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:
@@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Config, Guild, Session } from "@fosscord/util";
|
||||
import { Session } from "@fosscord/util";
|
||||
|
||||
export async function initInstance() {
|
||||
// TODO: clean up database and delete tombstone data
|
||||
@@ -24,15 +24,14 @@ export async function initInstance() {
|
||||
|
||||
// create default guild and add it to auto join
|
||||
// TODO: check if any current user is not part of autoJoinGuilds
|
||||
const { autoJoin } = Config.get().guild;
|
||||
// const { autoJoin } = Config.get().guild;
|
||||
|
||||
if (autoJoin.enabled && !autoJoin.guilds?.length) {
|
||||
let guild = await Guild.findOne({ where: {}, select: ["id"] });
|
||||
if (guild) {
|
||||
// @ts-ignore
|
||||
await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
|
||||
}
|
||||
}
|
||||
// if (autoJoin.enabled && !autoJoin.guilds?.length) {
|
||||
// const guild = await Guild.findOne({ where: {}, select: ["id"] });
|
||||
// if (guild) {
|
||||
// await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } });
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO: do no clear sessions for instance cluster
|
||||
await Session.delete({});
|
||||
|
||||
@@ -51,7 +51,7 @@ const allow_empty = false;
|
||||
// TODO: embed gifs/videos/images
|
||||
|
||||
const LINK_REGEX =
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g;
|
||||
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/g;
|
||||
|
||||
export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
const channel = await Channel.findOneOrFail({
|
||||
@@ -129,7 +129,6 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
}
|
||||
/** Q: should be checked if the referenced message exists? ANSWER: NO
|
||||
otherwise backfilling won't work **/
|
||||
// @ts-ignore
|
||||
message.type = MessageType.REPLY;
|
||||
}
|
||||
|
||||
@@ -144,29 +143,29 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
throw new HTTPError("Empty messages are not allowed", 50006);
|
||||
}
|
||||
|
||||
var content = opts.content;
|
||||
var mention_channel_ids = [] as string[];
|
||||
var mention_role_ids = [] as string[];
|
||||
var mention_user_ids = [] as string[];
|
||||
var mention_everyone = false;
|
||||
let content = opts.content;
|
||||
const mention_channel_ids = [] as string[];
|
||||
const mention_role_ids = [] as string[];
|
||||
const mention_user_ids = [] as string[];
|
||||
let mention_everyone = false;
|
||||
|
||||
if (content) {
|
||||
// TODO: explicit-only mentions
|
||||
message.content = content.trim();
|
||||
content = content.replace(/ *\`[^)]*\` */g, ""); // remove codeblocks
|
||||
for (const [_, mention] of content.matchAll(CHANNEL_MENTION)) {
|
||||
content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks
|
||||
for (const [, mention] of content.matchAll(CHANNEL_MENTION)) {
|
||||
if (!mention_channel_ids.includes(mention))
|
||||
mention_channel_ids.push(mention);
|
||||
}
|
||||
|
||||
for (const [_, mention] of content.matchAll(USER_MENTION)) {
|
||||
for (const [, mention] of content.matchAll(USER_MENTION)) {
|
||||
if (!mention_user_ids.includes(mention))
|
||||
mention_user_ids.push(mention);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
Array.from(content.matchAll(ROLE_MENTION)).map(
|
||||
async ([_, mention]) => {
|
||||
async ([, mention]) => {
|
||||
const role = await Role.findOneOrFail({
|
||||
where: { id: mention, guild_id: channel.guild_id },
|
||||
});
|
||||
@@ -198,8 +197,8 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
|
||||
|
||||
// TODO: cache link result in db
|
||||
export async function postHandleMessage(message: Message) {
|
||||
const content = message.content?.replace(/ *\`[^)]*\` */g, ""); // remove markdown
|
||||
var links = content?.match(LINK_REGEX);
|
||||
const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown
|
||||
let links = content?.match(LINK_REGEX);
|
||||
if (!links) return;
|
||||
|
||||
const data = { ...message };
|
||||
@@ -232,8 +231,8 @@ export async function postHandleMessage(message: Message) {
|
||||
// tried to use shorthand but types didn't like me L
|
||||
if (!Array.isArray(res)) res = [res];
|
||||
|
||||
for (var embed of res) {
|
||||
var cache = EmbedCache.create({
|
||||
for (const embed of res) {
|
||||
const cache = EmbedCache.create({
|
||||
url: link,
|
||||
embed: embed,
|
||||
});
|
||||
@@ -279,7 +278,10 @@ export async function sendMessage(opts: MessageOptions) {
|
||||
} as MessageCreateEvent),
|
||||
]);
|
||||
|
||||
postHandleMessage(message).catch((e) => {}); // no await as it should catch error non-blockingly
|
||||
// no await as it should catch error non-blockingly
|
||||
postHandleMessage(message).catch((e) =>
|
||||
console.error("[Message] post-message handler failed", e),
|
||||
);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export async function getVoiceRegions(ipAddress: string, vip: boolean) {
|
||||
|
||||
let min = Number.POSITIVE_INFINITY;
|
||||
|
||||
for (let ar of availableRegions) {
|
||||
for (const ar of availableRegions) {
|
||||
//TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call
|
||||
const dist = distanceBetweenLocations(
|
||||
clientIpAnalysis,
|
||||
|
||||
@@ -34,6 +34,8 @@ import { NextFunction, Request, Response } from "express";
|
||||
import { AnyValidateFunction } from "ajv/dist/core";
|
||||
|
||||
declare global {
|
||||
// TODO: fix this
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace Express {
|
||||
interface Request {
|
||||
permission?: Permissions;
|
||||
@@ -53,7 +55,7 @@ export interface RouteOptions {
|
||||
body?: `${string}Schema`; // typescript interface name
|
||||
test?: {
|
||||
response?: RouteResponse;
|
||||
body?: any;
|
||||
body?: unknown;
|
||||
path?: string;
|
||||
event?: EVENT | EVENT[];
|
||||
headers?: Record<string, string>;
|
||||
@@ -61,7 +63,7 @@ export interface RouteOptions {
|
||||
}
|
||||
|
||||
export function route(opts: RouteOptions) {
|
||||
var validate: AnyValidateFunction<any> | undefined;
|
||||
let validate: AnyValidateFunction | undefined;
|
||||
if (opts.body) {
|
||||
validate = ajv.getSchema(opts.body);
|
||||
if (!validate) throw new Error(`Body schema ${opts.body} not found`);
|
||||
|
||||
@@ -17,13 +17,13 @@
|
||||
*/
|
||||
|
||||
import { Config, Embed, EmbedType } from "@fosscord/util";
|
||||
import fetch, { Response } from "node-fetch";
|
||||
import fetch, { RequestInit } from "node-fetch";
|
||||
import * as cheerio from "cheerio";
|
||||
import probe from "probe-image-size";
|
||||
import crypto from "crypto";
|
||||
import { yellow } from "picocolors";
|
||||
|
||||
export const DEFAULT_FETCH_OPTIONS: any = {
|
||||
export const DEFAULT_FETCH_OPTIONS: RequestInit = {
|
||||
redirect: "follow",
|
||||
follow: 1,
|
||||
headers: {
|
||||
@@ -50,7 +50,7 @@ export const getProxyUrl = (
|
||||
|
||||
// Imagor
|
||||
if (imagorServerUrl) {
|
||||
let path = `${width}x${height}/${url.host}${url.pathname}`;
|
||||
const path = `${width}x${height}/${url.host}${url.pathname}`;
|
||||
|
||||
const hash = crypto
|
||||
.createHmac("sha1", secret)
|
||||
@@ -92,8 +92,8 @@ export const getMetaDescriptions = (text: string) => {
|
||||
image: getMeta($, "og:image") || getMeta($, "twitter:image"),
|
||||
image_fallback: $(`image`).attr("src"),
|
||||
video_fallback: $(`video`).attr("src"),
|
||||
width: parseInt(getMeta($, "og:image:width")!) || 0,
|
||||
height: parseInt(getMeta($, "og:image:height")!) || 0,
|
||||
width: parseInt(getMeta($, "og:image:width") || "0"),
|
||||
height: parseInt(getMeta($, "og:image:height") || "0"),
|
||||
url: getMeta($, "og:url"),
|
||||
youtube_embed: getMeta($, "og:video:secure_url"),
|
||||
};
|
||||
@@ -192,8 +192,8 @@ export const EmbedHandlers: {
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(
|
||||
new URL(metas.image),
|
||||
metas.width!,
|
||||
metas.height!,
|
||||
metas.width,
|
||||
metas.height,
|
||||
)
|
||||
: undefined,
|
||||
},
|
||||
@@ -239,9 +239,9 @@ export const EmbedHandlers: {
|
||||
const text = json.data.text;
|
||||
const created_at = new Date(json.data.created_at);
|
||||
const metrics = json.data.public_metrics;
|
||||
let media = json.includes.media?.filter(
|
||||
(x: any) => x.type == "photo",
|
||||
) as any[]; // TODO: video
|
||||
const media = json.includes.media?.filter(
|
||||
(x: { type: string }) => x.type == "photo",
|
||||
);
|
||||
|
||||
const embed: Embed = {
|
||||
type: EmbedType.rich,
|
||||
@@ -334,7 +334,7 @@ export const EmbedHandlers: {
|
||||
width: 640,
|
||||
height: 640,
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(new URL(metas.image!), 640, 640)
|
||||
? getProxyUrl(new URL(metas.image), 640, 640)
|
||||
: undefined,
|
||||
url: metas.image,
|
||||
},
|
||||
@@ -365,9 +365,9 @@ export const EmbedHandlers: {
|
||||
url: url.href,
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(
|
||||
new URL(metas.image!),
|
||||
metas.width!,
|
||||
metas.height!,
|
||||
new URL(metas.image),
|
||||
metas.width,
|
||||
metas.height,
|
||||
)
|
||||
: undefined,
|
||||
},
|
||||
@@ -395,7 +395,7 @@ export const EmbedHandlers: {
|
||||
height: 215,
|
||||
url: metas.image,
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(new URL(metas.image!), 460, 215)
|
||||
? getProxyUrl(new URL(metas.image), 460, 215)
|
||||
: undefined,
|
||||
},
|
||||
provider: {
|
||||
@@ -436,7 +436,7 @@ export const EmbedHandlers: {
|
||||
// TODO: does this adjust with aspect ratio?
|
||||
width: metas.width,
|
||||
height: metas.height,
|
||||
url: metas.youtube_embed!,
|
||||
url: metas.youtube_embed,
|
||||
},
|
||||
url: url.href,
|
||||
type: EmbedType.video,
|
||||
@@ -447,9 +447,9 @@ export const EmbedHandlers: {
|
||||
url: metas.image,
|
||||
proxy_url: metas.image
|
||||
? getProxyUrl(
|
||||
new URL(metas.image!),
|
||||
metas.width!,
|
||||
metas.height!,
|
||||
new URL(metas.image),
|
||||
metas.width,
|
||||
metas.height,
|
||||
)
|
||||
: undefined,
|
||||
},
|
||||
|
||||
@@ -24,7 +24,7 @@ import crypto from "crypto";
|
||||
|
||||
export function random(length = 6) {
|
||||
// Declare all characters
|
||||
let chars =
|
||||
const chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
// Pick characers randomly
|
||||
@@ -38,14 +38,14 @@ export function random(length = 6) {
|
||||
|
||||
export function snowflakeBasedInvite() {
|
||||
// Declare all characters
|
||||
let chars =
|
||||
const chars =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let base = BigInt(chars.length);
|
||||
const base = BigInt(chars.length);
|
||||
let snowflake = Snowflake.generateWorkerProcess();
|
||||
|
||||
// snowflakes hold ~10.75 characters worth of entropy;
|
||||
// safe to generate a 8-char invite out of them
|
||||
let str = "";
|
||||
const str = "";
|
||||
for (let i = 0; i < 10; i++) {
|
||||
str.concat(chars.charAt(Number(snowflake % base)));
|
||||
snowflake = snowflake / base;
|
||||
|
||||
@@ -47,7 +47,10 @@ export async function verifyCaptcha(response: string, ip?: string) {
|
||||
const { security } = Config.get();
|
||||
const { service, secret, sitekey } = security.captcha;
|
||||
|
||||
if (!service) throw new Error("Cannot verify captcha without service");
|
||||
if (!service || !secret || !sitekey)
|
||||
throw new Error(
|
||||
"CAPTCHA is not configured correctly. https://docs.fosscord.com/setup/server/security/captcha/",
|
||||
);
|
||||
|
||||
const res = await fetch(verifyEndpoints[service], {
|
||||
method: "POST",
|
||||
@@ -56,9 +59,9 @@ export async function verifyCaptcha(response: string, ip?: string) {
|
||||
},
|
||||
body:
|
||||
`response=${encodeURIComponent(response)}` +
|
||||
`&secret=${encodeURIComponent(secret!)}` +
|
||||
`&sitekey=${encodeURIComponent(sitekey!)}` +
|
||||
(ip ? `&remoteip=${encodeURIComponent(ip!)}` : ""),
|
||||
`&secret=${encodeURIComponent(secret)}` +
|
||||
`&sitekey=${encodeURIComponent(sitekey)}` +
|
||||
(ip ? `&remoteip=${encodeURIComponent(ip)}` : ""),
|
||||
});
|
||||
|
||||
return (await res.json()) as hcaptchaResponse | recaptchaResponse;
|
||||
|
||||
@@ -85,7 +85,7 @@ export async function IPAnalysis(ip: string): Promise<typeof exampleData> {
|
||||
|
||||
return (
|
||||
await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)
|
||||
).json() as any; // TODO: types
|
||||
).json();
|
||||
}
|
||||
|
||||
export function isProxy(data: typeof exampleData) {
|
||||
@@ -97,14 +97,21 @@ export function isProxy(data: typeof exampleData) {
|
||||
}
|
||||
|
||||
export function getIpAdress(req: Request): string {
|
||||
// TODO: express can do this (trustProxies: true)?
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
req.headers[Config.get().security.forwadedFor] ||
|
||||
req.socket.remoteAddress
|
||||
);
|
||||
}
|
||||
|
||||
export function distanceBetweenLocations(loc1: any, loc2: any): number {
|
||||
type Location = { latitude: number; longitude: number };
|
||||
export function distanceBetweenLocations(
|
||||
loc1: Location,
|
||||
loc2: Location,
|
||||
): number {
|
||||
return distanceBetweenCoords(
|
||||
loc1.latitude,
|
||||
loc1.longitude,
|
||||
|
||||
@@ -23,7 +23,7 @@ const reNUMBER = /[0-9]/g;
|
||||
const reUPPERCASELETTER = /[A-Z]/g;
|
||||
const reSYMBOLS = /[A-Z,a-z,0-9]/g;
|
||||
|
||||
const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored in db
|
||||
// const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored in db
|
||||
/*
|
||||
* https://en.wikipedia.org/wiki/Password_policy
|
||||
* password must meet following criteria, to be perfect:
|
||||
@@ -38,7 +38,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored
|
||||
export function checkPassword(password: string): number {
|
||||
const { minLength, minNumbers, minUpperCase, minSymbols } =
|
||||
Config.get().register.password;
|
||||
var strength = 0;
|
||||
let strength = 0;
|
||||
|
||||
// checks for total password len
|
||||
if (password.length >= minLength - 1) {
|
||||
@@ -68,13 +68,13 @@ export function checkPassword(password: string): number {
|
||||
strength = 0;
|
||||
}
|
||||
|
||||
let entropyMap: { [key: string]: number } = {};
|
||||
const entropyMap: { [key: string]: number } = {};
|
||||
for (let i = 0; i < password.length; i++) {
|
||||
if (entropyMap[password[i]]) entropyMap[password[i]]++;
|
||||
else entropyMap[password[i]] = 1;
|
||||
}
|
||||
|
||||
let entropies = Object.values(entropyMap);
|
||||
const entropies = Object.values(entropyMap);
|
||||
|
||||
entropies.map((x) => x / entropyMap.length);
|
||||
strength +=
|
||||
|
||||
Reference in New Issue
Block a user