This commit is contained in:
Tiago de Paula 2026-02-11 15:46:12 +00:00 committed by GitHub
commit 8f8248691e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 4252 additions and 5041 deletions

View file

@ -54,7 +54,6 @@ export default defineComponent({
const hover = () => {
if (props.onHover) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return props.onHover(props.user as UserInMessage);
}

View file

@ -3,7 +3,5 @@
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
src:
url("../fonts/fa-solid-900.woff2") format("woff2"),
url("../fonts/fa-solid-900.woff") format("woff");
src: url("../fonts/fa-solid-900.woff2") format("woff2");
}

View file

@ -55,119 +55,115 @@
"./public/**"
],
"dependencies": {
"@fastify/busboy": "1.0.0",
"bcryptjs": "2.4.3",
"chalk": "4.1.2",
"cheerio": "1.0.0",
"commander": "9.0.0",
"content-disposition": "0.5.4",
"express": "4.20.0",
"file-type": "16.5.4",
"filenamify": "4.3.0",
"got": "11.8.6",
"irc-framework": "4.14.0",
"is-utf8": "0.2.1",
"ldapjs": "2.3.3",
"linkify-it": "3.0.3",
"lodash": "4.17.21",
"mime-types": "2.1.35",
"node-forge": "1.3.1",
"package-json": "7.0.0",
"read": "1.0.7",
"read-chunk": "3.2.0",
"semver": "7.5.2",
"socket.io": "4.6.2",
"tlds": "1.228.0",
"ua-parser-js": "1.0.39",
"uuid": "8.3.2",
"web-push": "3.4.5",
"yarn": "1.22.22"
"@fastify/busboy": "^3.2.0",
"bcryptjs": "^3.0.3",
"chalk": "^4.1.2",
"cheerio": "~1.0.0",
"commander": "^13.1.0",
"content-disposition": "^1.0.1",
"express": "^5.2.1",
"file-type": "^16.5.4",
"filenamify": "^4.3.0",
"got": "^11.8.6",
"irc-framework": "^4.14.0",
"is-utf8": "^0.2.1",
"ldapjs": "^2.3.3",
"linkify-it": "^5.0.0",
"lodash": "^4.17.21",
"mime-types": "^3.0.2",
"node-forge": "^1.3.1",
"package-json": "^10.0.1",
"read": "^1.0.7",
"read-chunk": "^3.2.0",
"semver": "^7.7.4",
"socket.io": "^4.8.3",
"tlds": "^1.261.0",
"ua-parser-js": "^2.0.8",
"uuid": "^11.1.0",
"web-push": "^3.6.7",
"yarn": "^1.22.22"
},
"optionalDependencies": {
"sqlite3": "5.1.7"
"sqlite3": "^5.1.7"
},
"devDependencies": {
"@babel/core": "7.17.10",
"@babel/plugin-transform-runtime": "7.18.2",
"@babel/preset-env": "7.17.10",
"@fortawesome/fontawesome-free": "5.15.4",
"@istanbuljs/nyc-config-typescript": "1.0.2",
"@textcomplete/core": "0.1.10",
"@textcomplete/textarea": "0.1.13",
"@types/bcryptjs": "2.4.6",
"@types/chai": "4.3.5",
"@types/cheerio": "0.22.35",
"@types/content-disposition": "0.5.8",
"@types/express": "4.17.21",
"@types/is-utf8": "0.2.3",
"@types/ldapjs": "2.2.5",
"@types/linkify-it": "3.0.5",
"@types/lodash": "4.14.202",
"@types/mime-types": "2.1.4",
"@types/mocha": "9.1.1",
"@types/mousetrap": "1.6.15",
"@types/node": "17.0.45",
"@types/read": "0.0.32",
"@types/semver": "7.3.9",
"@types/sortablejs": "1.15.8",
"@types/sqlite3": "3.1.11",
"@types/ua-parser-js": "0.7.39",
"@types/uuid": "8.3.4",
"@types/web-push": "3.3.2",
"@types/webpack-env": "1.16.4",
"@types/webpack-hot-middleware": "2.25.6",
"@types/ws": "8.5.12",
"@typescript-eslint/eslint-plugin": "7.8.0",
"@typescript-eslint/parser": "7.8.0",
"@vue/runtime-dom": "3.2.33",
"@vue/test-utils": "2.4.6",
"babel-loader": "8.2.5",
"babel-plugin-istanbul": "6.1.1",
"babel-preset-typescript-vue3": "2.0.17",
"chai": "4.3.7",
"copy-webpack-plugin": "10.2.4",
"cross-env": "7.0.3",
"css-loader": "6.5.1",
"cssnano": "5.0.17",
"dayjs": "1.10.8",
"emoji-regex": "10.2.1",
"eslint": "8.57.0",
"eslint-config-prettier": "9.1.0",
"eslint-define-config": "2.1.0",
"eslint-plugin-vue": "9.25.0",
"fork-ts-checker-webpack-plugin": "7.2.13",
"fuzzy": "0.1.3",
"mini-css-extract-plugin": "2.5.3",
"mocha": "9.2.2",
"mousetrap": "1.6.5",
"normalize.css": "8.0.1",
"npm-run-all": "4.1.5",
"nyc": "15.1.0",
"postcss": "8.4.47",
"postcss-import": "14.0.2",
"postcss-loader": "6.2.1",
"postcss-preset-env": "7.3.0",
"prettier": "2.5.1",
"pretty-quick": "3.1.3",
"primer-tooltips": "2.0.0",
"sinon": "13.0.2",
"socket.io-client": "4.5.0",
"sortablejs": "1.15.2",
"stylelint": "14.3.0",
"stylelint-config-standard": "24.0.0",
"ts-loader": "9.3.0",
"ts-node": "10.7.0",
"ts-sinon": "2.0.2",
"typescript": "5.4.5",
"undate": "0.3.0",
"vue": "3.2.35",
"vue-eslint-parser": "9.4.3",
"vue-loader": "17.0.1",
"vue-router": "4.0.15",
"vuex": "4.0.2",
"webpack": "5.94.0",
"webpack-cli": "4.9.2",
"webpack-dev-middleware": "5.3.4",
"webpack-hot-middleware": "2.25.4"
"@babel/core": "^7.29.0",
"@babel/plugin-transform-runtime": "^7.29.0",
"@babel/preset-env": "^7.29.0",
"@fortawesome/fontawesome-free": "^7.1.0",
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@textcomplete/core": "^0.1.13",
"@textcomplete/textarea": "^0.1.13",
"@types/chai": "^4.3.20",
"@types/content-disposition": "^0.5.9",
"@types/express": "^5.0.6",
"@types/is-utf8": "^0.2.3",
"@types/ldapjs": "^2.2.5",
"@types/linkify-it": "^5.0.0",
"@types/lodash": "^4.17.23",
"@types/mime-types": "^3.0.1",
"@types/mocha": "^10.0.10",
"@types/mousetrap": "^1.6.15",
"@types/node": "^18.19.130",
"@types/node-forge": "^1.3.14",
"@types/read": "^0.0.32",
"@types/semver": "^7.7.1",
"@types/sortablejs": "^1.15.9",
"@types/web-push": "^3.6.4",
"@types/webpack-env": "^1.18.8",
"@types/webpack-hot-middleware": "^2.25.12",
"@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@vue/runtime-dom": "^3.5.28",
"@vue/test-utils": "^2.4.6",
"babel-loader": "^9.2.1",
"babel-plugin-istanbul": "^7.0.1",
"babel-preset-typescript-vue3": "~2.0.17",
"chai": "^4.5.0",
"copy-webpack-plugin": "^13.0.1",
"cross-env": "^7.0.3",
"css-loader": "^7.1.3",
"cssnano": "^6.1.2",
"dayjs": "^1.11.19",
"emoji-regex": "^10.6.0",
"eslint": "^8.57.1",
"eslint-config-prettier": "^10.1.8",
"eslint-define-config": "^2.1.0",
"eslint-plugin-vue": "^9.33.0",
"fork-ts-checker-webpack-plugin": "^9.1.0",
"fuzzy": "^0.1.3",
"mini-css-extract-plugin": "^2.10.0",
"mocha": "^11.7.5",
"mousetrap": "^1.6.5",
"normalize.css": "^8.0.1",
"npm-run-all2": "^7.0.2",
"nyc": "^17.1.0",
"postcss": "^8.5.6",
"postcss-import": "^16.1.1",
"postcss-loader": "^8.2.0",
"postcss-preset-env": "^10.6.1",
"prettier": "~2.7.1",
"pretty-quick": "^3.3.1",
"primer-tooltips": "^2.0.0",
"sinon": "^21.0.1",
"socket.io-client": "^4.8.3",
"sortablejs": "^1.15.6",
"stylelint": "^14.16.1",
"stylelint-config-standard": "^25.0.0",
"ts-loader": "^9.5.4",
"ts-node": "^10.9.2",
"ts-sinon": "^2.0.2",
"typescript": "~5.4.5",
"undate": "^0.3.0",
"vue": "^3.5.28",
"vue-eslint-parser": "^10.2.0",
"vue-loader": "^17.4.2",
"vue-router": "^4.6.4",
"vuex": "^4.1.0",
"webpack": "^5.105.1",
"webpack-cli": "^6.0.1",
"webpack-dev-middleware": "^7.4.5",
"webpack-hot-middleware": "^2.26.1"
}
}

View file

@ -1,5 +1,5 @@
"use strict";
module.exports = function () {
export default function () {
return function () {};
};
}

View file

@ -1,5 +1,5 @@
import _ from "lodash";
import UAParser from "ua-parser-js";
import {UAParser} from "ua-parser-js";
import {v4 as uuidv4} from "uuid";
import escapeRegExp from "lodash/escapeRegExp";
import crypto from "crypto";

View file

@ -87,7 +87,7 @@ class ClientManager {
autoloadUsers() {
fs.watch(Config.getUsersPath(), (_eventType, file) => {
if (!file.endsWith(".json")) {
if (!file?.endsWith(".json")) {
return;
}

View file

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import log from "../log";
import colors from "chalk";
import semver from "semver";
@ -6,11 +5,11 @@ import Helper from "../helper";
import Config from "../config";
import Utils from "./utils";
import {Command} from "commander";
import {FullMetadata} from "package-json";
import {FullVersion} from "package-json";
type CustomMetadata = FullMetadata & {
thelounge: {
supports: string;
type CustomMetadata = FullVersion & {
thelounge?: {
supports?: string;
};
};
@ -34,8 +33,7 @@ program
}
log.info("Retrieving information about the package...");
// TODO: type
let readFile: any = null;
let readFile: Promise<CustomMetadata> | null = null;
let isLocalFile = false;
if (packageName.startsWith("file:")) {
@ -45,7 +43,7 @@ program
packageName = expandTildeInLocalPath(packageName);
readFile = fspromises
.readFile(path.join(packageName.substring("file:".length), "package.json"), "utf-8")
.then((data) => JSON.parse(data) as typeof packageJson);
.then((data) => JSON.parse(data) as CustomMetadata);
} else {
// properly split scoped and non-scoped npm packages
// into their name and version
@ -63,13 +61,13 @@ program
});
}
if (!readFile) {
if (readFile === null) {
// no-op, error should've been thrown before this point
return;
}
readFile
.then((json: CustomMetadata) => {
.then((json) => {
const humanVersion = isLocalFile ? packageName : `${json.name} v${json.version}`;
if (!("thelounge" in json)) {
@ -79,7 +77,7 @@ program
}
if (
json.thelounge.supports &&
json.thelounge?.supports &&
!semver.satisfies(Helper.getVersionNumber(), json.thelounge.supports, {
includePrerelease: true,
})

View file

@ -28,15 +28,17 @@ export default (app: express.Application) => {
const compiler = webpack(webpackConfig);
if (!compiler) {
throw new Error("Webpack failed to create a compiler");
}
app.use(
// eslint-disable-next-line @typescript-eslint/no-misused-promises
webpackDevMiddleware(compiler, {
index: "/",
publicPath: webpackConfig.output?.publicPath,
})
).use(
// TODO: Fix compiler type
webpackHotMiddleware(compiler as any, {
webpackHotMiddleware(compiler, {
path: "/storage/__webpack_hmr",
})
);

View file

@ -139,8 +139,7 @@ function parseHtml(preview, res, client: Client) {
});
}
// TODO: type $
function parseHtmlMedia($: any, preview, client: Client): Promise<FetchRequest> {
function parseHtmlMedia($: cheerio.CheerioAPI, preview, client: Client): Promise<FetchRequest> {
return new Promise((resolve, reject) => {
if (Config.values.disableMediaPreview) {
reject();
@ -167,7 +166,7 @@ function parseHtmlMedia($: any, preview, client: Client): Promise<FetchRequest>
return;
}
$(`meta[property="og:${type}:type"]`).each(function (this: cheerio.Element, i: number) {
$(`meta[property="og:${type}:type"]`).each(function (i: number) {
const mimeType = $(this).attr("content");
if (!mimeType) {

View file

@ -1,5 +1,5 @@
import Config from "../config";
import busboy, {BusboyHeaders} from "@fastify/busboy";
import busboy, {BusboyFileStream, BusboyHeaders} from "@fastify/busboy";
import {v4 as uuidv4} from "uuid";
import path from "path";
import fs from "fs";
@ -11,6 +11,7 @@ import log from "../log";
import contentDisposition from "content-disposition";
import type {Socket} from "socket.io";
import {Request, Response} from "express";
import type {Express} from "express-serve-static-core";
// Map of allowed mime types to their respecive default filenames
// that will be rendered in browser without forcing them to be downloaded
@ -71,13 +72,16 @@ class Uploader {
return setTimeout(() => uploadTokens.delete(token), 60 * 1000);
}
// TODO: type
static router(this: void, express: any) {
express.get("/uploads/:name/:slug*?", Uploader.routeGetFile);
static router(this: void, express: Express) {
express.get("/uploads/:name{/*slug}", Uploader.routeGetFile);
express.post("/uploads/new/:token", Uploader.routeUploadFile);
}
static async routeGetFile(this: void, req: Request, res: Response) {
static async routeGetFile(
this: void,
req: Request<{name: string; slug?: string[]}>,
res: Response
) {
const name = req.params.name;
const nameRegex = /^[0-9a-f]{16}$/;
@ -97,7 +101,7 @@ class Uploader {
}
// Force a download in the browser if it's not an allowed type (binary or otherwise unknown)
let slug = req.params.slug;
let slug = req.params.slug?.filter(Boolean).join("/");
const isInline = detectedMimeType in inlineContentDispositionTypes;
let disposition = isInline ? "inline" : "attachment";
@ -128,11 +132,11 @@ class Uploader {
res.setHeader("Cache-Control", "max-age=86400");
res.contentType(detectedMimeType);
return res.sendFile(filePath);
return res.sendFile(filePath, {dotfiles: "allow"});
}
static routeUploadFile(this: void, req: Request, res: Response) {
let busboyInstance: NodeJS.WritableStream | busboy | null | undefined;
static routeUploadFile(this: void, req: Request<{token: string}>, res: Response) {
let busboyInstance: busboy | null | undefined;
let uploadUrl: string | URL;
let randomName: string;
let destDir: fs.PathLike;
@ -234,19 +238,7 @@ class Uploader {
busboyInstance.on(
"file",
(
fieldname: any,
fileStream: {
on: (
arg0: string,
arg1: {(err: any): Response<any, Record<string, any>>; (): void}
) => void;
unpipe: (arg0: any) => void;
read: {bind: (arg0: any) => any};
pipe: (arg0: any) => void;
},
filename: string | number | boolean
) => {
(fieldname: string, fileStream: BusboyFileStream, filename: string) => {
uploadUrl = `${randomName}/${encodeURIComponent(filename)}`;
if (Config.values.fileUpload.baseUrl) {
@ -257,17 +249,21 @@ class Uploader {
// if the busboy data stream errors out or goes over the file size limit
// abort the processing with an error
// @ts-expect-error Argument of type '(err: any) => Response<any, Record<string, any>>' is not assignable to parameter of type '{ (err: any): Response<any, Record<string, any>>; (): void; }'.ts(2345)
fileStream.on("error", abortWithError);
fileStream.on("limit", () => {
fileStream.unpipe(streamWriter);
if (streamWriter) {
fileStream.unpipe(streamWriter);
}
fileStream.on("readable", fileStream.read.bind(fileStream));
return abortWithError(Error("File size limit reached"));
});
// Attempt to write the stream to file
fileStream.pipe(streamWriter);
if (streamWriter) {
fileStream.pipe(streamWriter);
}
}
);

View file

@ -1,6 +1,7 @@
import _ from "lodash";
import {Server as wsServer} from "ws";
import express, {NextFunction, Request, Response} from "express";
import type {ServeStaticOptions} from "serve-static";
import fs from "fs";
import path from "path";
import {Server as ioServer, Socket as ioSocket} from "socket.io";
@ -76,9 +77,10 @@ export default async function (
})`);
log.info(`Configuration file: ${colors.green(Config.getConfigPath())}`);
const staticOptions = {
const staticOptions: ServeStaticOptions = {
redirect: false,
maxAge: 86400 * 1000,
dotfiles: "allow",
};
const app = express();

View file

@ -7,7 +7,7 @@ export type LinkPart = {
link: string;
};
const linkify = LinkifyIt().tlds(tlds).tlds("onion", true);
const linkify = new LinkifyIt().tlds(tlds).tlds("onion", true);
// Known schemes to detect in text
const commonSchemes = [
@ -32,7 +32,7 @@ for (const schema of commonSchemes) {
}
linkify.add("web+", {
validate(text: string, pos: number, self: LinkifyIt.LinkifyIt) {
validate(text: string, pos: number, self: LinkifyIt) {
const webSchemaRe = /^[a-z]+:/gi;
if (!webSchemaRe.test(text.slice(pos))) {

View file

@ -63,7 +63,9 @@ describe("Server", function () {
expect(response.statusCode).to.equal(200);
expect(body.name).to.equal("The Lounge");
expect(response.headers["content-type"]).to.equal("application/manifest+json");
expect(response.headers["content-type"]).to.equal(
"application/manifest+json; charset=utf-8"
);
});
});

View file

@ -6,7 +6,6 @@ describe("public folder", function () {
const publicFolder = path.join(__dirname, "..", "..", "public");
it("font awesome files are copied", function () {
expect(fs.existsSync(path.join(publicFolder, "fonts", "fa-solid-900.woff"))).to.be.true;
expect(fs.existsSync(path.join(publicFolder, "fonts", "fa-solid-900.woff2"))).to.be.true;
});

View file

@ -41,6 +41,7 @@ const config: webpack.Configuration = {
},
resolve: {
extensions: [".ts", ".js", ".vue"],
conditionNames: ["import", "require", "default", "node"],
},
module: {
rules: [
@ -167,11 +168,6 @@ const config: webpack.Configuration = {
},
],
}),
// socket.io uses debug, we don't need it
new webpack.NormalModuleReplacementPlugin(
/debug/,
path.resolve(__dirname, "scripts/noop.js")
),
],
};
@ -205,7 +201,7 @@ export default (env: any, argv: any) => {
// Client tests that require Vue may end up requireing socket.io
new webpack.NormalModuleReplacementPlugin(
/js(\/|\\)socket\.js/,
path.resolve(__dirname, "scripts/noop.js")
path.resolve(__dirname, "scripts/noop.mjs")
),
];
}

9008
yarn.lock

File diff suppressed because it is too large Load diff