progress?

This commit is contained in:
Max Leiter 2022-05-02 18:32:20 -07:00
parent 42160354b8
commit 26cfa8d159
No known key found for this signature in database
GPG key ID: A3512F2F2F17EBDA
112 changed files with 1110 additions and 820 deletions

View file

@ -1,18 +1,18 @@
{
"extends": "../tsconfig.json",
"extends": "../tsconfig.base.json",
"include": ["./**/*.ts", "./**/*.js", "./**/*.d.ts", "./**/*/json"],
"exclude": ["./dist/*"],
"compilerOptions": {
// https://v2.vuejs.org/v2/guide/typescript.html?redirect=true#Recommended-Configuration
"target": "ES2020",
"target": "ES5",
"strict": true,
"module": "ES2020",
"module": "commonjs",
"moduleResolution": "node",
"lib": ["ES2020", "dom"],
"sourceMap": false,
"outDir": "./dist",
"allowJs": true,
"noImplicitAny": true,
"noImplicitAny": false,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"baseUrl": "./",

View file

@ -1,4 +1,4 @@
#!/usr/bin/env node
#!/usr/bin/env ts-node
"use strict";
@ -7,8 +7,7 @@ process.chdir(__dirname);
// Perform node version check before loading any other files or modules
// Doing this check as soon as possible allows us to
// avoid ES6 parser errors or other issues
const pkg = require("./package.json");
import pkg from "./package.json";
if (!require("semver").satisfies(process.version, pkg.engines.node)) {
/* eslint-disable no-console */
console.error(
@ -25,11 +24,10 @@ if (!require("semver").satisfies(process.version, pkg.engines.node)) {
process.exit(1);
}
const dns = require("dns");
import dns from "dns";
// Set DNS result order early before anything that may depend on it happens.
if (dns.setDefaultResultOrder) {
dns.setDefaultResultOrder("verbatim");
}
require("./src/command-line");
import "./src/command-line";

View file

@ -12,9 +12,9 @@
},
"homepage": "https://thelounge.chat/",
"scripts": {
"build": "tsc --build . && webpack",
"build": "tsc --resolveJsonModule --esModuleInterop --lib DOM webpack.config.ts && webpack --config webpack.config.js",
"coverage": "run-s test:* && nyc --nycrc-path=test/.nycrc-report.json report",
"dev": "ts-node index start --dev",
"dev": "TS_NODE_PROJECT='./src/tsconfig.json' ts-node index start --dev",
"format:prettier": "prettier --write \"**/*.*\"",
"lint:check-eslint": "eslint-config-prettier .eslintrc.cjs",
"lint:eslint": "eslint . --ext .js,.vue --report-unused-disable-directives --color",
@ -78,7 +78,10 @@
"@fortawesome/fontawesome-free": "5.15.4",
"@textcomplete/core": "0.1.11",
"@textcomplete/textarea": "0.1.10",
"@types/bcryptjs": "2.4.2",
"@types/content-disposition": "0.5.4",
"@types/express": "4.17.13",
"@types/is-utf8": "0.2.0",
"@types/ldapjs": "2.2.2",
"@types/linkify-it": "3.0.2",
"@types/lodash": "4.14.182",
@ -86,9 +89,11 @@
"@types/mousetrap": "1.6.9",
"@types/node": "17.0.31",
"@types/read": "0.0.29",
"@types/semver": "7.3.9",
"@types/sqlite3": "3.1.8",
"@types/ua-parser-js": "0.7.36",
"@types/uuid": "8.3.4",
"@types/web-push": "3.3.2",
"@types/ws": "8.5.3",
"@vue/runtime-dom": "3.2.33",
"@vue/server-test-utils": "1.3.0",

View file

@ -11,17 +11,15 @@ import log from "./log";
import Chan from "./models/chan";
import Msg from "./models/msg";
import Config from "./config";
import constants from "../client/js/constants.js";
import constants from "../client/js/constants";
import inputs from "./plugins/inputs";
import PublicClient from "./plugins/packages/publicClient";
import SqliteMessageStorage from "./plugins/messageStorage/sqlite";
import TextFileMessageStorage from "./plugins/messageStorage/text";
import {ClientConfig, Mention, PushSubscription} from "src/types/client";
import {ClientConfig, Mention, PushSubscription} from "./types/client";
import Network from "./models/network";
import ClientManager from "./clientManager";
import {MessageType} from "./types/models/message";
import {ChanType} from "./types/models/channel";
import {MessageStorage} from "./types/plugins/messageStorage";
const events = [
@ -52,27 +50,27 @@ const events = [
"whois",
];
class Client {
awayMessage: string;
lastActiveChannel: number;
attachedClients: {
awayMessage!: string;
lastActiveChannel!: number;
attachedClients!: {
[socketId: string]: {token: string; openChannel: number};
};
config: ClientConfig & {
networks: Network[];
config!: ClientConfig & {
networks?: Network[];
};
id: number;
idMsg: number;
idChan: number;
name: string;
networks: Network[];
mentions: Mention[];
manager: ClientManager;
messageStorage: MessageStorage[];
id!: number;
idMsg!: number;
idChan!: number;
name!: string;
networks!: Network[];
mentions!: Mention[];
manager!: ClientManager;
messageStorage!: MessageStorage[];
highlightRegex?: RegExp;
highlightExceptionRegex?: RegExp;
messageProvider?: SqliteMessageStorage;
fileHash: string;
fileHash!: string;
constructor(manager: ClientManager, name?: string, config = {} as ClientConfig) {
_.merge(this, {
@ -181,8 +179,8 @@ class Client {
}
find(channelId: number) {
let network = null;
let chan = null;
let network: Network | null = null;
let chan: Chan | null | undefined = null;
for (const i in this.networks) {
const n = this.networks[i];
@ -203,7 +201,7 @@ class Client {
connect(args: any, isStartup = false) {
const client = this;
let channels = [];
let channels: Chan[] = [];
// Get channel id for lobby before creating other channels for nicer ids
const lobbyChannelId = client.idChan++;
@ -275,6 +273,7 @@ class Client {
networks: [network.getFilteredClone(this.lastActiveChannel, -1)],
});
// @ts-ignore it complains because validate expects this to be NetworkWith
if (!network.validate(client)) {
return;
}
@ -294,7 +293,9 @@ class Client {
true
);
} else if (!isStartup) {
network.irc.connect();
// irc is created in createIrcFramework
// TODO; fix type
network.irc!.connect();
}
if (!isStartup) {
@ -427,7 +428,8 @@ class Client {
);
}
} else if (connected) {
irc.raw(text);
// TODO: fix
irc!.raw(text);
}
if (!connected) {
@ -444,7 +446,7 @@ class Client {
compileCustomHighlights() {
function compileHighlightRegex(customHighlightString) {
if (typeof customHighlightString !== "string") {
return null;
return undefined;
}
// Ensure we don't have empty strings in the list of highlights
@ -454,7 +456,7 @@ class Client {
.filter((highlight) => highlight.length > 0);
if (highlightsTokens.length === 0) {
return null;
return undefined;
}
return new RegExp(
@ -480,7 +482,7 @@ class Client {
}
const chan = target.chan;
let messages = [];
let messages: Msg[] = [];
let index = 0;
// If client requests -1, send last 100 messages
@ -669,8 +671,8 @@ class Client {
});
}
quit(signOut: boolean) {
const sockets = this.manager.sockets;
quit(signOut?: boolean) {
const sockets = this.manager.sockets.sockets;
const room = sockets.adapter.rooms.get(this.id.toString());
if (room) {
@ -766,12 +768,12 @@ class Client {
}
unregisterPushSubscription(token: string) {
this.config.sessions[token].pushSubscription = null;
this.config.sessions[token].pushSubscription = undefined;
this.save();
}
save = _.debounce(
function SaveClient() {
function SaveClient(this: Client) {
if (Config.values.public) {
return;
}

View file

@ -11,19 +11,19 @@ import Client from "./client";
import Config from "./config";
import WebPush from "./plugins/webpush";
import log from "./log";
import {Namespace, Server, Socket} from "socket.io";
import {Server} from "socket.io";
class ClientManager {
clients: Client[];
sockets: Namespace;
sockets!: Server;
identHandler: any;
webPush: WebPush;
webPush!: WebPush;
constructor() {
this.clients = [];
}
init(identHandler, sockets: Namespace) {
init(identHandler, sockets: Server) {
this.sockets = sockets;
this.identHandler = identHandler;
this.webPush = new WebPush();
@ -163,7 +163,7 @@ class ClientManager {
.map((file) => file.slice(0, -5));
};
addUser(name: string, password: string, enableLog: boolean) {
addUser(name: string, password: string | null, enableLog?: boolean) {
if (path.basename(name) !== name) {
throw new Error(`${name} is an invalid username.`);
}
@ -184,7 +184,7 @@ class ClientManager {
fs.writeFileSync(userPath, JSON.stringify(user, null, "\t"), {
mode: 0o600,
});
} catch (e) {
} catch (e: any) {
log.error(`Failed to create user ${colors.green(name)} (${e})`);
throw e;
}
@ -213,7 +213,7 @@ class ClientManager {
);
fs.chownSync(userPath, userFolderStat.uid, userFolderStat.gid);
}
} catch (e) {
} catch (e: any) {
// We're simply verifying file owner as a safe guard for users
// that run `thelounge add` as root, so we don't care if it fails
}
@ -231,7 +231,7 @@ class ClientManager {
return {newUser, newHash};
}
saveUser(client: Client, callback: (err?: Error) => void) {
saveUser(client: Client, callback?: (err?: any) => void) {
const {newUser, newHash} = this.getDataToSave(client);
// Do not write to disk if the exported data hasn't actually changed
@ -251,7 +251,7 @@ class ClientManager {
fs.renameSync(pathTemp, pathReal);
return callback ? callback() : true;
} catch (e) {
} catch (e: any) {
log.error(`Failed to update user ${colors.green(client.name)} (${e})`);
if (callback) {
@ -284,7 +284,7 @@ class ClientManager {
try {
const data = fs.readFileSync(userPath, "utf-8");
return JSON.parse(data);
} catch (e) {
} catch (e: any) {
log.error(`Failed to read user ${colors.bold(name)}: ${e}`);
}

View file

@ -9,7 +9,7 @@ import Helper from "../helper";
import Config from "../config";
import Utils from "./utils";
const program = new Command();
const program = new Command("thelounge");
program
.version(Helper.getVersion(), "-v, --version")
.option(
@ -27,7 +27,7 @@ Config.setHome(process.env.THELOUNGE_HOME || Utils.defaultHome());
// Check config file owner and warn if we're running under a different user
try {
verifyFileOwner();
} catch (e) {
} catch (e: any) {
// We do not care about failures of these checks
// fs.statSync will throw if config.js does not exist (e.g. first run)
}
@ -38,17 +38,16 @@ createPackagesFolder();
// Merge config key-values passed as CLI options into the main config
Config.merge(program.opts().config);
import("./start");
program.addCommand(require("./start").default);
program.addCommand(require("./install").default);
program.addCommand(require("./uninstall").default);
program.addCommand(require("./upgrade").default);
program.addCommand(require("./outdated").default);
if (!Config.values.public) {
import("./users");
require("./users").default.forEach((command) => {
if (command) program.addCommand(command);
});
}
import "./install";
import "./uninstall";
import "./upgrade";
import "./outdated";
// `parse` expects to be passed `process.argv`, but we need to remove to give it
// a version of `argv` that does not contain options already parsed by
// `parseOptions` above.

View file

@ -8,16 +8,16 @@ import Config from "../config";
import Utils from "./utils";
import {Command} from "commander";
const program = new Command();
const program = new Command("install");
program
.command("install <package>")
.usage("install <package>")
.description("Install a theme or a package")
.on("--help", Utils.extraHelp)
.action(function (packageName) {
const fs = require("fs");
.action(async function (packageName) {
const fs = await import("fs");
const fspromises = fs.promises;
const path = require("path");
const packageJson = require("package-json");
const path = await import("path");
const packageJson = await import("package-json");
if (!fs.existsSync(Config.getConfigPath())) {
log.error(`${Config.getConfigPath()} does not exist.`);
@ -25,7 +25,8 @@ program
}
log.info("Retrieving information about the package...");
let readFile = null;
// TODO: type
let readFile: any = null;
let isLocalFile = false;
if (packageName.startsWith("file:")) {
@ -38,12 +39,17 @@ program
packageName = split[0];
const packageVersion = split[1] || "latest";
readFile = packageJson(packageName, {
readFile = packageJson.default(packageName, {
fullMetadata: true,
version: packageVersion,
});
}
if (!readFile) {
// no-op, error should've been thrown before this point
return;
}
readFile
.then((json) => {
const humanVersion = isLocalFile ? packageName : `${json.name} v${json.version}`;
@ -93,3 +99,5 @@ program
process.exit(1);
});
});
export default program;

View file

@ -5,9 +5,9 @@ import Utils from "./utils";
import packageManager from "../plugins/packages";
import log from "../log";
const program = new Command();
const program = new Command("outdated");
program
.command("outdated")
.usage("outdated")
.description("Check for any outdated packages")
.on("--help", Utils.extraHelp)
.action(async () => {
@ -26,3 +26,5 @@ program
log.error("Error finding outdated packages.");
});
});
export default program;

View file

@ -8,10 +8,9 @@ import {Command} from "commander";
import Config from "../config";
import Utils from "./utils";
const program = new Command();
const program = new Command("start");
program
.command("start")
.usage("start")
.description("Start the server")
.option("--dev", "Development mode with hot module reloading")
.on("--help", Utils.extraHelp)
@ -19,7 +18,7 @@ program
initalizeConfig();
const server = require("../server");
server(options);
server.default(options);
});
function initalizeConfig() {
@ -35,3 +34,5 @@ function initalizeConfig() {
fs.mkdirSync(Config.getUsersPath(), {recursive: true, mode: 0o700});
}
export default program;

View file

@ -6,9 +6,9 @@ import {Command} from "commander";
import Config from "../config";
import Utils from "./utils";
const program = new Command();
const program = new Command("uninstall");
program
.command("uninstall <package>")
.usage("uninstall <package>")
.description("Uninstall a theme or a package")
.on("--help", Utils.extraHelp)
.action(function (packageName) {
@ -37,3 +37,5 @@ program
process.exit(1);
});
});
export default program;

View file

@ -6,9 +6,9 @@ import {Command} from "commander";
import Config from "../config";
import Utils from "./utils";
const program = new Command();
const program = new Command("upgrade");
program
.command("upgrade [packages...]")
.usage("upgrade [packages...]")
.description("Upgrade installed themes and packages to their latest versions")
.on("--help", Utils.extraHelp)
.action(function (packages) {
@ -58,3 +58,5 @@ program
process.exit(1);
});
});
export default program;

View file

@ -8,9 +8,9 @@ import Helper from "../../helper";
import Config from "../../config";
import Utils from "../utils";
const program = new Command();
const program = new Command("add");
program
.command("add <name>")
.usage("add <name>")
.description("Add a new user")
.on("--help", Utils.extraHelp)
.option("--password [password]", "new password, will be prompted if not specified")
@ -80,3 +80,5 @@ function add(manager, name, password, enableLog) {
log.info(`User ${colors.bold(name)} created.`);
log.info(`User file located at ${colors.green(Config.getUserConfigPath(name))}.`);
}
export default program;

View file

@ -8,9 +8,9 @@ import fs from "fs";
import Config from "../../config";
import Utils from "../utils";
const program = new Command();
const program = new Command("edit");
program
.command("edit <name>")
.usage("edit <name>")
.description(`Edit user file located at ${colors.green(Config.getUserConfigPath("<name>"))}`)
.on("--help", Utils.extraHelp)
.action(function (name) {
@ -45,3 +45,5 @@ program
);
});
});
export default program;

View file

@ -1,12 +1,17 @@
"use strict";
import config from "../../config";
import list from "./list";
import remove from "./remove";
import edit from "./edit";
if (!config.values.ldap.enable) {
import("./add");
import("./reset");
}
let add, reset;
import "./list";
import "./remove";
import "./edit";
(async () => {
if (config.values.ldap.enable) {
add = (await import("./add")).default;
reset = (await import("./reset")).default;
}
})();
export default [list, remove, edit, add, reset];

View file

@ -5,9 +5,9 @@ import colors from "chalk";
import {Command} from "commander";
import Utils from "../utils";
const program = new Command();
const program = new Command("list");
program
.command("list")
.usage("list")
.description("List all users")
.on("--help", Utils.extraHelp)
.action(function () {
@ -33,3 +33,5 @@ program
log.info(`${i + 1}. ${colors.bold(user)}`);
});
});
export default program;

View file

@ -7,9 +7,9 @@ import fs from "fs";
import Config from "../../config";
import Utils from "../utils";
const program = new Command();
const program = new Command("remove");
program
.command("remove <name>")
.usage("remove <name>")
.description("Remove an existing user")
.on("--help", Utils.extraHelp)
.action(function (name) {
@ -27,7 +27,9 @@ program
} else {
log.error(`User ${colors.bold(name)} does not exist.`);
}
} catch (e) {
} catch (e: any) {
// There was an error, already logged
}
});
export default program;

View file

@ -8,9 +8,9 @@ import Helper from "../../helper";
import Config from "../../config";
import Utils from "../utils";
const program = new Command();
const program = new Command("reset");
program
.command("reset <name>")
.usage("reset <name>")
.description("Reset user password")
.on("--help", Utils.extraHelp)
.option("--password [password]", "new password, will be prompted if not specified")
@ -72,3 +72,5 @@ function change(name, password) {
log.info(`Successfully reset password for ${colors.bold(name)}.`);
}
export default program;

View file

@ -138,7 +138,7 @@ class Utils {
if (line.type === "success") {
success = true;
}
} catch (e) {
} catch (e: any) {
// Stdout buffer has limitations and yarn may print
// big package trees, for example in the upgrade command
// See https://github.com/thelounge/thelounge/issues/3679

View file

@ -1,19 +1,19 @@
"use strict";
import path from "path";
import fs from "fs";
import fs, {Stats} from "fs";
import os from "os";
import _ from "lodash";
import colors from "chalk";
import log from "./log";
import Helper from "./helper";
import {Config as ConfigType} from "src/types/config";
import {Config as ConfigType} from "./types/config";
class Config {
values = require(path.resolve(
path.join(__dirname, "..", "defaults", "config.js")
)) as ConfigType;
#homePath: string;
#homePath: string = "";
getHomePath() {
return this.#homePath;
@ -127,8 +127,8 @@ class Config {
if (this.values.fileUpload.baseUrl) {
try {
new URL("test/file.png", this.values.fileUpload.baseUrl);
} catch (e) {
this.values.fileUpload.baseUrl = null;
} catch (e: any) {
this.values.fileUpload.baseUrl = undefined;
log.warn(`The ${colors.bold("fileUpload.baseUrl")} you specified is invalid: ${e}`);
}
@ -154,7 +154,7 @@ class Config {
// log dir probably shouldn't be world accessible.
// Create it with the desired permission bits if it doesn't exist yet.
let logsStat = undefined;
let logsStat: Stats | undefined = undefined;
const userLogsPath = this.getUserLogsPath();
@ -167,7 +167,7 @@ class Config {
if (!logsStat) {
try {
fs.mkdirSync(userLogsPath, {recursive: true, mode: 0o750});
} catch (e) {
} catch (e: any) {
log.error("Unable to create logs directory", e);
}
} else if (logsStat && logsStat.mode & 0o001) {

View file

@ -39,10 +39,10 @@ function getVersionNumber() {
return pkg.version;
}
let _gitCommit;
let _gitCommit: string | null = null;
function getGitCommit() {
if (_gitCommit !== undefined) {
if (_gitCommit) {
return _gitCommit;
}
@ -60,7 +60,7 @@ function getGitCommit() {
.toString()
.trim();
return _gitCommit;
} catch (e) {
} catch (e: any) {
// Not a git repository or git is not installed
_gitCommit = null;
return null;
@ -120,7 +120,7 @@ function parseHostmask(hostmask: string): Hostmask {
let nick = "";
let ident = "*";
let hostname = "*";
let parts = [];
let parts: string[] = [];
// Parse hostname first, then parse the rest
parts = hostmask.split("@");

View file

@ -2,15 +2,15 @@
import log from "./log";
import fs from "fs";
import net from "net";
import net, {Socket} from "net";
import colors from "chalk";
import Helper from "./helper";
import Config from "./config";
class Identification {
private connectionId: number;
private connections: Map<any, any>;
private oidentdFile: string;
private connectionId!: number;
private connections!: Map<number, any>;
private oidentdFile?: string;
constructor(startedCallback: Function) {
this.connectionId = 0;
@ -45,7 +45,7 @@ class Identification {
const address = server.address();
if (typeof address === "string") {
log.info(`Identd server available on ${colors.green(address)}`);
} else if (address.address) {
} else if (address?.address) {
log.info(
`Identd server available on ${colors.green(
address.address + ":" + address.port
@ -61,7 +61,7 @@ class Identification {
}
}
serverConnection(socket) {
serverConnection(socket: Socket) {
socket.on("error", (err) => log.error(`Identd socket error: ${err}`));
socket.on("data", (data) => {
this.respondToIdent(socket, data);
@ -69,8 +69,8 @@ class Identification {
});
}
respondToIdent(socket, data) {
data = data.toString().split(",");
respondToIdent(socket: Socket, buffer: Buffer) {
const data = buffer.toString().split(",");
const lport = parseInt(data[0], 10) || 0;
const fport = parseInt(data[1], 10) || 0;
@ -90,7 +90,7 @@ class Identification {
socket.write(`${lport}, ${fport} : ERROR : NO-USER\r\n`);
}
addSocket(socket, user) {
addSocket(socket: Socket, user: string) {
const id = ++this.connectionId;
this.connections.set(id, {socket, user});
@ -120,11 +120,13 @@ class Identification {
` { reply "${connection.user}" }\n`;
});
fs.writeFile(this.oidentdFile, file, {flag: "w+"}, function (err) {
if (err) {
log.error("Failed to update oidentd file!", err.message);
}
});
if (this.oidentdFile) {
fs.writeFile(this.oidentdFile, file, {flag: "w+"}, function (err) {
if (err) {
log.error("Failed to update oidentd file!", err.message);
}
});
}
}
}

View file

@ -9,7 +9,7 @@ function timestamp() {
return colors.dim(datetime);
}
export default {
const log = {
/* eslint-disable no-console */
error(...args: string[]) {
console.error(timestamp(), colors.red("[ERROR]"), ...args);
@ -36,3 +36,5 @@ export default {
read(options, callback);
},
};
export default log;

View file

@ -6,27 +6,31 @@ import Config from "../config";
import User from "./user";
import Msg from "./msg";
import storage from "../plugins/storage";
import {ChanState, ChanType, FilteredChannel} from "src/types/models/channel";
import Client from "src/client";
import Client from "@src/client";
import Network from "./network";
import {MessageType} from "src/types/models/message";
import Prefix from "./prefix";
class Chan {
id: number;
messages: Msg[];
name: string;
key: string;
topic: string;
firstUnread: number;
unread: number;
highlight: number;
users: Map<string, User>;
muted: boolean;
type: ChanType;
state: ChanState;
// TODO: don't force existence, figure out how to make TS infer it.
id!: number;
messages!: Msg[];
name!: string;
key!: string;
topic!: string;
firstUnread!: number;
unread!: number;
highlight!: number;
users!: Map<string, User>;
muted!: boolean;
type!: ChanType;
state!: ChanState;
// TODO: this only exists when it's a query... should be better typed
userAway: boolean;
userAway!: boolean;
special?: SpecialChanType;
data?: any;
closed?: boolean;
num_users?: number;
constructor(attr: Partial<Chan>) {
_.defaults(this, attr, {
@ -150,7 +154,7 @@ class Chan {
return this.users.get(nick.toLowerCase());
}
getUser(nick: string) {
return this.findUser(nick) || new User({nick});
return this.findUser(nick) || new User({nick}, new Prefix([]));
}
setUser(user: User) {
this.users.set(user.nick.toLowerCase(), user);
@ -174,7 +178,7 @@ class Chan {
newChannel[prop] = [];
} else if (prop === "messages") {
// If client is reconnecting, only send new messages that client has not seen yet
if (lastMessage > -1) {
if (lastMessage && lastMessage > -1) {
// When reconnecting, always send up to 100 messages to prevent message gaps on the client
// See https://github.com/thelounge/thelounge/issues/1883
newChannel[prop] = this[prop].filter((m) => m.id > lastMessage).slice(-100);
@ -210,7 +214,7 @@ class Chan {
// Because notices are nasty and can be shown in active channel on the client
// if there is no open query, we want to always log notices in the sender's name
if (msg.type === MessageType.NOTICE && msg.showInActive) {
targetChannel.name = msg.from.nick;
targetChannel.name = msg.from.nick || ""; // TODO: check if || works
} else {
return;
}
@ -254,7 +258,7 @@ class Chan {
.getMessages(network, this)
.then((messages) => {
if (messages.length === 0) {
if (network.irc.network.cap.isEnabled("znc.in/playback")) {
if (network.irc!.network.cap.isEnabled("znc.in/playback")) {
requestZncPlayback(this, network, 0);
}
@ -273,7 +277,7 @@ class Chan {
totalMessages: messages.length,
});
if (network.irc.network.cap.isEnabled("znc.in/playback")) {
if (network.irc!.network.cap.isEnabled("znc.in/playback")) {
const from = Math.floor(messages[messages.length - 1].time.getTime() / 1000);
requestZncPlayback(this, network, from);
@ -284,7 +288,7 @@ class Chan {
isLoggable() {
return this.type === ChanType.CHANNEL || this.type === ChanType.QUERY;
}
setMuteStatus(muted) {
setMuteStatus(muted: boolean) {
this.muted = !!muted;
}
}

View file

@ -1,44 +1,43 @@
"use strict";
import _ from "lodash";
import {UserInMessage, MessagePreview, MessageType} from "src/types/models/message";
class Msg {
from: UserInMessage;
id: number;
previews: MessagePreview[];
text: string;
type: MessageType;
self: boolean;
time: Date;
hostmask: string;
target: UserInMessage;
from!: UserInMessage;
id!: number;
previews!: MessagePreview[];
text!: string;
type!: MessageType;
self!: boolean;
time!: Date;
hostmask!: string;
target!: UserInMessage;
// TODO: new_nick is only on MessageType.NICK,
// we should probably make Msgs that extend this class and use those
// throughout. I'll leave any similar fields below.
new_nick: string;
highlight: boolean;
showInActive: boolean;
new_ident: string;
new_host: string;
ctcpMessage: string;
command: string;
invitedYou: boolean;
gecos: string;
account: boolean;
new_nick!: string;
highlight!: boolean;
showInActive?: boolean;
new_ident!: string;
new_host!: string;
ctcpMessage!: string;
command!: string;
invitedYou!: boolean;
gecos!: string;
account!: boolean;
// these are all just for error:
error: string;
nick: string;
channel: string;
reason: string;
error!: string;
nick!: string;
channel!: string;
reason!: string;
raw_modes: any;
when: Date;
whois: any;
users: UserInMessage[];
statusmsgGroup: string;
params: string[];
raw_modes!: any;
when!: Date;
whois!: any;
users!: UserInMessage[];
statusmsgGroup!: string;
params!: string[];
constructor(attr: Partial<Msg>) {
// Some properties need to be copied in the Msg object instead of referenced

View file

@ -10,11 +10,7 @@ import Helper from "../helper";
import Config from "../config";
import STSPolicies from "../plugins/sts";
import ClientCertificate from "../plugins/clientCertificate";
import {Channel, ChanType} from "src/types/models/channel";
import Client from "src/client";
import {IgnoreList, NetworkStatus} from "src/types/models/network";
import {MessageType} from "src/types/models/message";
import {WebIRC} from "src/types/config";
import Client from "@src/client";
/**
* @type {Object} List of keys which should be sent to the client by default.
@ -27,73 +23,49 @@ const fieldsForClient = {
};
class Network {
nick: string;
name: string;
host: string;
port: number;
tls: boolean;
userDisconnected: boolean;
rejectUnauthorized: boolean;
password: string;
awayMessage: string;
commands: any[];
username: string;
realname: string;
leaveMessage: string;
sasl: string;
saslAccount: string;
saslPassword: string;
channels: Chan[];
uuid: string;
proxyHost: string;
proxyPort: number;
proxyUsername: string;
proxyPassword: string;
proxyEnabled: boolean;
nick!: string;
name!: string;
host!: string;
port!: number;
tls!: boolean;
userDisconnected!: boolean;
rejectUnauthorized!: boolean;
password!: string;
awayMessage!: string;
commands!: any[];
username!: string;
realname!: string;
leaveMessage!: string;
sasl!: string;
saslAccount!: string;
saslPassword!: string;
channels!: Chan[];
uuid!: string;
proxyHost!: string;
proxyPort!: number;
proxyUsername!: string;
proxyPassword!: string;
proxyEnabled!: boolean;
highlightRegex?: RegExp;
irc?: IrcFramework.Client & {
options?: {
host: string;
port: number;
password: string;
nick: string;
username: string;
gecos: string;
tls: boolean;
rejectUnauthorized: boolean;
webirc: WebIRC;
client_certificate?: ClientCertificate;
socks: {
host: string;
port: number;
user: string;
pass: string;
};
sasl_mechanism: string;
account:
| {
account: string;
password: string;
}
| {};
};
options?: NetworkIrcOptions;
};
chanCache: Chan[];
ignoreList: IgnoreList;
keepNick?: string;
chanCache!: Chan[];
ignoreList!: IgnoreList;
keepNick!: string | null;
status: NetworkStatus;
status!: NetworkStatus;
serverOptions: {
serverOptions!: {
CHANTYPES: string[];
PREFIX: Prefix;
NETWORK: string;
};
// TODO: this is only available on export
hasSTSPolicy: boolean;
hasSTSPolicy!: boolean;
constructor(attr: Partial<Network>) {
_.defaults(this, attr, {
@ -158,7 +130,7 @@ class Network {
);
}
validate(client: Client) {
validate(this: NetworkWithIrcFramework, client: Client) {
// Remove !, :, @ and whitespace characters from nicknames and usernames
const cleanNick = (str: string) => str.replace(/[\x00\s:!@]/g, "_").substring(0, 100);
@ -251,7 +223,7 @@ class Network {
return true;
}
createIrcFramework(client: Client) {
createIrcFramework(this: Network, client: Client) {
this.irc = new IrcFramework.Client({
version: false, // We handle it ourselves
outgoing_addr: Config.values.bind,
@ -265,6 +237,7 @@ class Network {
auto_reconnect_max_retries: 30,
});
//@ts-ignore TODO: `this` should now be a NetworkWithIrcFramework
this.setIrcFrameworkOptions(client);
this.irc.requestCap([
@ -273,13 +246,13 @@ class Network {
]);
}
setIrcFrameworkOptions(client: Client) {
setIrcFrameworkOptions(this: NetworkWithIrcFramework, client: Client) {
this.irc.options.host = this.host;
this.irc.options.port = this.port;
this.irc.options.password = this.password;
this.irc.options.nick = this.nick;
this.irc.options.username = Config.values.useHexIp
? Helper.ip2hex(client.config.browser.ip)
? Helper.ip2hex(client.config.browser!.ip!)
: this.username;
this.irc.options.gecos = this.realname;
this.irc.options.tls = this.tls;
@ -325,12 +298,12 @@ class Network {
const webircObject = {
password: Config.values.webirc[this.host],
username: "thelounge",
address: client.config.browser.ip,
hostname: client.config.browser.hostname,
address: client.config.browser?.ip,
hostname: client.config.browser?.hostname,
} as any;
// https://ircv3.net/specs/extensions/webirc#options
if (client.config.browser.isSecure) {
if (client.config.browser?.isSecure) {
webircObject.options = {
secure: true,
};
@ -344,7 +317,7 @@ class Network {
return webircObject;
}
edit(client: Client, args: any) {
edit(this: NetworkWithIrcFramework, client: Client, args: any) {
const oldNetworkName = this.name;
const oldNick = this.nick;
const oldRealname = this.realname;
@ -418,9 +391,9 @@ class Network {
}
this.setIrcFrameworkOptions(client);
if (this.irc.options?.username) this.irc.user.username = this.irc.options.username;
this.irc.user.username = this.irc.options.username;
this.irc.user.gecos = this.irc.options.gecos;
if (this.irc.options?.gecos) this.irc.user.gecos = this.irc.options.gecos;
}
client.save();
@ -430,7 +403,7 @@ class Network {
this.channels.forEach((channel) => channel.destroy());
}
setNick(nick: string) {
setNick(this: NetworkWithIrcFramework, nick: string) {
this.nick = nick;
this.highlightRegex = new RegExp(
// Do not match characters and numbers (unless IRC color)
@ -448,9 +421,7 @@ class Network {
this.keepNick = null;
}
if (this.irc) {
this.irc.options.nick = nick;
}
this.irc.options.nick = nick;
}
getFilteredClone(lastActiveChannel: number, lastMessage: number) {

View file

@ -4,14 +4,14 @@ import _ from "lodash";
import Prefix from "./prefix";
class User {
modes: string[];
modes!: string[];
// Users in the channel have only one mode assigned
mode: string;
away: string;
nick: string;
lastMessage: number;
mode!: string;
away!: string;
nick!: string;
lastMessage!: number;
constructor(attr: Partial<User>, prefix?: Prefix) {
constructor(attr: Partial<User>, prefix: Prefix) {
_.defaults(this, attr, {
modes: [],
away: "",

View file

@ -5,7 +5,7 @@ import log from "../log";
// The order defines priority: the first available plugin is used.
// Always keep 'local' auth plugin at the end of the list; it should always be enabled.
const plugins = [require("./auth/ldap"), require("./auth/local")];
const plugins = [import("./auth/ldap"), import("./auth/local")];
function unimplemented(funcName) {
log.debug(
@ -34,20 +34,22 @@ export default toExport;
// local auth should always be enabled, but check here to verify
let somethingEnabled = false;
// Override default API stubs with exports from first enabled plugin found
for (const plugin of plugins) {
if (plugin.isEnabled()) {
somethingEnabled = true;
for (const name in plugin) {
toExport[name] = plugin[name];
Promise.all(plugins).then((plugins) => {
for (const plugin of plugins) {
if (plugin.default.isEnabled()) {
somethingEnabled = true;
for (const name in plugin) {
toExport[name] = plugin[name];
}
break;
}
break;
}
}
if (!somethingEnabled) {
log.error("None of the auth plugins is enabled");
}
if (!somethingEnabled) {
log.error("None of the auth plugins is enabled");
}
});

View file

@ -4,8 +4,9 @@ import log from "../../log";
import Config from "../../config";
import ldap, {SearchOptions} from "ldapjs";
import colors from "chalk";
import ClientManager from "src/clientManager";
import Client from "src/client";
import ClientManager from "@src/clientManager";
import Client from "@src/client";
import {AuthHandler} from "@src/types/plugins/auth";
function ldapAuthCommon(
user: string,
@ -104,7 +105,8 @@ function advancedLdapAuth(user: string, password: string, callback: (success: bo
log.info(`Auth against LDAP ${config.ldap.url} with found bindDN ${bindDN}`);
ldapclient.unbind();
ldapAuthCommon(user, bindDN, password, callback);
// TODO: Fix type !
ldapAuthCommon(user, bindDN!, password, callback);
});
res.on("error", function (err3) {
@ -116,7 +118,9 @@ function advancedLdapAuth(user: string, password: string, callback: (success: bo
ldapclient.unbind();
if (!found) {
log.warn(`LDAP Search did not find anything for: ${userDN} (${result.status})`);
log.warn(
`LDAP Search did not find anything for: ${userDN} (${result?.status})`
);
callback(false);
}
});
@ -124,13 +128,7 @@ function advancedLdapAuth(user: string, password: string, callback: (success: bo
});
}
function ldapAuth(
manager: ClientManager,
client: Client,
user: string,
password: string,
callback: (success: boolean) => void
) {
const ldapAuth: AuthHandler = (manager, client, user, password, callback) => {
// TODO: Enable the use of starttls() as an alternative to ldaps
// TODO: move this out of here and get rid of `manager` and `client` in
@ -152,14 +150,14 @@ function ldapAuth(
}
return auth(user, password, callbackWrapper);
}
};
/**
* Use the LDAP filter from config to check that users still exist before loading them
* via the supplied callback function.
*/
function advancedLdapLoadUsers(users, callbackLoadUser) {
function advancedLdapLoadUsers(users: string[], callbackLoadUser) {
const config = Config.values;
const ldapclient = ldap.createClient({
@ -226,7 +224,7 @@ function advancedLdapLoadUsers(users, callbackLoadUser) {
return true;
}
function ldapLoadUsers(users, callbackLoadUser) {
function ldapLoadUsers(users: string[], callbackLoadUser) {
if ("baseDN" in Config.values.ldap) {
// simple LDAP case can't test for user existence without access to the
// user's unhashed password, so indicate need to fallback to default

View file

@ -1,10 +1,11 @@
"use strict";
const log = require("../../log");
const Helper = require("../../helper");
const colors = require("chalk");
import colors from "chalk";
import log from "../../log";
import Helper from "../../helper";
import {AuthHandler} from "@src/types/plugins/auth";
function localAuth(manager, client, user, password, callback) {
const localAuth: AuthHandler = (manager, client, user, password, callback) => {
// If no user is found, or if the client has not provided a password,
// fail the authentication straight away
if (!client || !password) {
@ -43,9 +44,9 @@ function localAuth(manager, client, user, password, callback) {
.catch((error) => {
log.error(`Error while checking users password. Error: ${error}`);
});
}
};
module.exports = {
export default {
moduleName: "local",
auth: localAuth,
isEnabled: () => true,

View file

@ -4,7 +4,7 @@ import got, {Response} from "got";
import colors from "chalk";
import log from "../log";
import pkg from "../../package.json";
import ClientManager from "src/clientManager";
import ClientManager from "@src/clientManager";
const TIME_TO_LIVE = 15 * 60 * 1000; // 15 minutes, in milliseconds
@ -22,6 +22,18 @@ const versions = {
expiresAt: -1,
latest: undefined,
packages: undefined,
} as {
current: {
version: string;
changelog?: string;
};
expiresAt: number;
latest?: {
prerelease: boolean;
version: string;
url: string;
};
packages?: boolean;
};
async function fetch() {

View file

@ -12,7 +12,7 @@ export default {
remove,
};
function get(uuid: string): ClientCertificate {
function get(uuid: string): ClientCertificate | null {
if (Config.values.public) {
return null;
}
@ -29,7 +29,7 @@ function get(uuid: string): ClientCertificate {
private_key: fs.readFileSync(paths.privateKeyPath, "utf-8"),
certificate: fs.readFileSync(paths.certificatePath, "utf-8"),
} as ClientCertificate;
} catch (e) {
} catch (e: any) {
log.error("Unable to get certificate", e);
}
@ -51,7 +51,7 @@ function remove(uuid: string) {
if (fs.existsSync(paths.certificatePath)) {
fs.unlinkSync(paths.certificatePath);
}
} catch (e) {
} catch (e: any) {
log.error("Unable to remove certificate", e);
}
}
@ -70,8 +70,8 @@ function generateAndWrite(folderPath: string, paths: {privateKeyPath: any; certi
});
return certificate;
} catch (e) {
log.error("Unable to write certificate", e);
} catch (e: any) {
log.error("Unable to write certificate", e as string);
}
return null;

View file

@ -1,13 +1,9 @@
"use strict";
import Network from "src/models/network";
import {ChanType} from "../../types/models/channel";
import {MessageType} from "src/types/models/message";
import Chan from "../../models/chan";
import Msg from "../../models/msg";
const commands = ["slap", "me"];
const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function ({irc}, chan, cmd, args) {
if (chan.type !== ChanType.CHANNEL && chan.type !== ChanType.QUERY) {
chan.pushMessage(
this,

View file

@ -1,11 +1,8 @@
"use strict";
import Network from "src/models/network";
import {Channel} from "src/types/models/channel";
const commands = ["away", "back"];
const input = function (network: Network, chan: Channel, cmd: string, args: string[]) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
let reason = "";
if (cmd === "away") {

View file

@ -1,14 +1,10 @@
"use strict";
import Network from "src/models/network";
import Chan from "src/models/chan";
import Msg from "src/models/msg";
import {MessageType} from "src/types/models/message";
import {ChanType} from "src/types/models/channel";
import Msg from "@src/models/msg";
const commands = ["ban", "unban", "banlist", "kickban"];
const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function ({irc}, chan, cmd, args) {
if (chan.type !== ChanType.CHANNEL) {
chan.pushMessage(
this,

View file

@ -1,14 +1,12 @@
"use strict";
import Network from "src/models/network";
import {Channel} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
const commands = ["connect", "server"];
const allowDisconnected = true;
const input = function (network: Network, chan: Channel, cmd: string, args: string[]) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
if (args.length === 0) {
network.userDisconnected = false;
this.save();
@ -51,4 +49,5 @@ const input = function (network: Network, chan: Channel, cmd: string, args: stri
export default {
commands,
input,
allowDisconnected,
};

View file

@ -1,13 +1,10 @@
"use strict";
import Chan from "src/models/chan";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Msg from "../../models/msg";
const commands = ["ctcp"];
const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function ({irc}, chan, cmd, args) {
if (args.length < 2) {
chan.pushMessage(
this,
@ -29,7 +26,10 @@ const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[])
);
// TODO: check. Was ctcpRequest(...args)
irc.ctcpRequest(args.shift(), args.shift(), ...args);
const target = args.shift()!;
const type = args.shift()!;
irc.ctcpRequest(target, type, ...args);
};
export default {

View file

@ -1,13 +1,10 @@
"use strict";
import Chan from "src/models/chan";
import Network from "src/models/network";
const commands = ["disconnect"];
const allowDisconnected = true;
const input = function (network: Network, chan: Chan, cmd: string, args: string[]) {
const quitMessage = args[0] ? args.join(" ") : null;
const input: PluginInputHandler = function (network, chan, cmd, args) {
const quitMessage = args[0] ? args.join(" ") : undefined;
network.quit(quitMessage);
network.userDisconnected = true;

View file

@ -1,21 +1,15 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Chan from "src/models/chan";
import Msg from "src/models/msg";
import Helper from "src/helper";
import {IgnoreListItem} from "src/types/models/network";
import {ChanType, SpecialChanType} from "src/types/models/channel";
import Msg from "@src/models/msg";
import Helper from "@src/helper";
const commands = ["ignore", "unignore", "ignorelist"];
const input = function (network: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
const client = this;
let target: string;
let hostmask: IgnoreListItem;
// let hostmask: cmd === "ignoreList" ? string : undefined;
let hostmask: IgnoreListItem | undefined;
if (cmd !== "ignorelist" && (args.length === 0 || args[0].trim().length === 0)) {
chan.pushMessage(
client,
@ -37,7 +31,7 @@ const input = function (network: Network, chan: Chan, cmd: string, args: string[
switch (cmd) {
case "ignore": {
// IRC nicks are case insensitive
if (hostmask.nick.toLowerCase() === network.nick.toLowerCase()) {
if (hostmask!.nick.toLowerCase() === network.nick.toLowerCase()) {
chan.pushMessage(
client,
new Msg({
@ -47,18 +41,20 @@ const input = function (network: Network, chan: Chan, cmd: string, args: string[
);
} else if (
!network.ignoreList.some(function (entry) {
return Helper.compareHostmask(entry, hostmask);
return Helper.compareHostmask(entry, hostmask!);
})
) {
hostmask.when = Date.now();
network.ignoreList.push(hostmask);
hostmask!.when = Date.now();
network.ignoreList.push(hostmask!);
client.save();
chan.pushMessage(
client,
new Msg({
type: MessageType.ERROR,
text: `\u0002${hostmask.nick}!${hostmask.ident}@${hostmask.hostname}\u000f added to ignorelist`,
text: `\u0002${hostmask!.nick}!${hostmask!.ident}@${
hostmask!.hostname
}\u000f added to ignorelist`,
})
);
} else {
@ -76,7 +72,7 @@ const input = function (network: Network, chan: Chan, cmd: string, args: string[
case "unignore": {
const idx = network.ignoreList.findIndex(function (entry) {
return Helper.compareHostmask(entry, hostmask);
return Helper.compareHostmask(entry, hostmask!);
});
// Check if the entry exists before removing it, otherwise
@ -89,7 +85,9 @@ const input = function (network: Network, chan: Chan, cmd: string, args: string[
client,
new Msg({
type: MessageType.ERROR,
text: `Successfully removed \u0002${hostmask.nick}!${hostmask.ident}@${hostmask.hostname}\u000f from ignorelist`,
text: `Successfully removed \u0002${hostmask!.nick}!${hostmask!.ident}@${
hostmask!.hostname
}\u000f from ignorelist`,
})
);
} else {
@ -136,7 +134,6 @@ const input = function (network: Network, chan: Chan, cmd: string, args: string[
});
} else {
// TODO: add type for this chan/event
//@ts-expect-error
newChan.data = ignored;
client.emit("msg:special", {

View file

@ -1,5 +1,5 @@
import Chan from "src/models/chan";
import Network from "src/models/network";
import Chan from "@src/models/chan";
import Network from "@src/models/network";
const clientSideCommands = ["/collapse", "/expand", "/search"];
@ -40,12 +40,17 @@ const userInputs = [
"whois",
"mute",
].reduce(function (plugins, name) {
const plugin = require(`./${name}`) as {
commands: string[];
input: (network: Network, chan: Chan, cmd: string, args: string[]) => void;
allowDisconnected?: boolean;
};
plugin.commands.forEach((command: string) => plugins.set(command, plugin));
const plugin = import(`./${name}`).then(
(plugin: {
default: {
commands: string[];
input: (network: Network, chan: Chan, cmd: string, args: string[]) => void;
allowDisconnected?: boolean;
};
}) => {
plugin.default.commands.forEach((command: string) => plugins.set(command, plugin));
}
);
return plugins;
}, new Map());

View file

@ -1,14 +1,13 @@
"use strict";
import Network from "src/models/network";
import {ChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Chan from "../../models/chan";
import Msg from "../../models/msg";
const commands = ["invite", "invitelist"];
const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function ({irc}, chan, cmd, args) {
if (cmd === "invitelist") {
irc.inviteList(chan.name);
return;

View file

@ -1,14 +1,10 @@
"use strict";
import Network from "src/models/network";
import {ChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Chan from "../../models/chan";
import Msg from "../../models/msg";
const commands = ["kick"];
const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function ({irc}, chan, cmd, args) {
if (chan.type !== ChanType.CHANNEL) {
chan.pushMessage(
this,

View file

@ -1,11 +1,8 @@
"use strict";
import Chan from "src/models/chan";
import Network from "src/models/network";
const commands = ["kill"];
const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function ({irc}, chan, cmd, args) {
if (args.length !== 0) {
irc.raw("KILL", args[0], args.slice(1).join(" "));
}

View file

@ -1,11 +1,8 @@
"use strict";
import Chan from "src/models/chan";
import Network from "src/models/network";
const commands = ["list"];
const input = function (network: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
network.chanCache = [];
network.irc.list(...args);
return true;

View file

@ -1,13 +1,10 @@
"use strict";
import {ChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Chan from "../../models/chan";
import Msg from "../../models/msg";
const commands = ["mode", "umode", "op", "deop", "hop", "dehop", "voice", "devoice"];
const input = function ({irc, nick}, chan, cmd, args) {
const input: PluginInputHandler = function ({irc, nick}, chan, cmd, args) {
if (cmd === "umode") {
irc.raw("MODE", nick, ...args);
@ -52,7 +49,7 @@ const input = function ({irc, nick}, chan, cmd, args) {
for (let i = 0; i < target.length; i += limit) {
const targets = target.slice(i, i + limit);
const amode = `${mode[0]}${mode[1].repeat(targets.length)}`;
const amode = `${mode![0]}${mode![1].repeat(targets.length)}`;
irc.raw("MODE", chan.name, amode, ...targets);
}

View file

@ -1,10 +1,8 @@
"use strict";
import Network from "src/models/network";
import {ChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Chan from "src/models/chan";
import Msg from "src/models/msg";
import Network from "@src/models/network";
import Msg from "@src/models/msg";
const commands = ["query", "msg", "say"];
@ -18,7 +16,7 @@ function getTarget(cmd, args, chan) {
}
}
const input = function (network: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
let targetName = getTarget(cmd, args, chan);
if (cmd === "query") {

View file

@ -1,14 +1,16 @@
"use strict";
import Chan from "src/models/chan";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Chan from "@src/models/chan";
import Network from "@src/models/network";
import Msg from "../../models/msg";
import Client from "@src/client";
const commands = ["mute", "unmute"];
const allowDisconnected = true;
function args_to_channels(network, args) {
const targets = [];
function args_to_channels(network: Network, args: string[]) {
const targets: Chan[] = [];
for (const arg of args) {
const target = network.channels.find((c) => c.name === arg);
@ -21,7 +23,7 @@ function args_to_channels(network, args) {
return targets;
}
function change_mute_state(client, target, valueToSet) {
function change_mute_state(client: Client, target: Chan, valueToSet: boolean) {
if (target.type === "special") {
return;
}
@ -33,7 +35,7 @@ function change_mute_state(client, target, valueToSet) {
});
}
const input = function (network: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
const valueToSet = cmd === "mute" ? true : false;
const client = this;

View file

@ -1,14 +1,11 @@
"use strict";
import Chan from "src/models/chan";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Msg from "../../models/msg";
const commands = ["nick"];
const allowDisconnected = true;
const input = function (network: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
if (args.length === 0) {
chan.pushMessage(
this,

View file

@ -1,11 +1,8 @@
"use strict";
import Chan from "src/models/chan";
import Network from "src/models/network";
const commands = ["notice"];
const input = function (network: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
if (!args[1]) {
return;
}

View file

@ -1,16 +1,12 @@
"use strict";
import Msg from "src/models/msg";
import Chan from "src/models/chan";
import Config from "src/config";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import {ChanState, ChanType} from "src/types/models/channel";
import Msg from "@src/models/msg";
import Config from "@src/config";
const commands = ["close", "leave", "part"];
const allowDisconnected = true;
const input = function (network: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
let target = chan;
if (args.length > 0) {

View file

@ -1,14 +1,13 @@
"use strict";
import _ from "lodash";
import Chan from "src/models/chan";
import Network from "src/models/network";
import ClientCertificate from "../clientCertificate";
const commands = ["quit"];
const allowDisconnected = true;
const input = function (network: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function (network, chan, cmd, args) {
const client = this;
client.networks = _.without(client.networks, network);
@ -18,7 +17,7 @@ const input = function (network: Network, chan: Chan, cmd: string, args: string[
network: network.uuid,
});
const quitMessage = args[0] ? args.join(" ") : null;
const quitMessage = args[0] ? args.join(" ") : undefined;
network.quit(quitMessage);
ClientCertificate.remove(network.uuid);

View file

@ -1,11 +1,8 @@
"use strict";
import Chan from "src/models/chan";
import Network from "src/models/network";
const commands = ["raw", "send", "quote"];
const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function ({irc}, chan, cmd, args) {
if (args.length !== 0) {
irc.connection.write(args.join(" "));
}

View file

@ -1,14 +1,10 @@
"use strict";
import Msg from "../../models/msg";
import Chan from "../../models/chan";
import {ChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Network from "src/models/network";
const commands = ["cycle", "rejoin"];
const input = function ({irc}: Network, chan: Chan) {
const input: PluginInputHandler = function ({irc}, chan) {
if (chan.type !== ChanType.CHANNEL) {
chan.pushMessage(
this,

View file

@ -1,14 +1,10 @@
"use strict";
import Network from "src/models/network";
import Chan from "src/models/chan";
import Msg from "src/models/msg";
import {ChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Msg from "@src/models/msg";
const commands = ["topic"];
const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function ({irc}, chan, cmd, args) {
if (chan.type !== ChanType.CHANNEL) {
chan.pushMessage(
this,

View file

@ -1,11 +1,8 @@
"use strict";
import Chan from "src/models/chan";
import Network from "src/models/network";
const commands = ["whois"];
const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) {
const input: PluginInputHandler = function ({irc}, chan, cmd, args) {
if (args.length === 1) {
// This queries server of the other user and not of the current user, which
// does not know idle time.

View file

@ -1,11 +1,8 @@
"use strict";
import Network from "src/models/network";
import {ChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("away", (data) => handleAway(MessageType.AWAY, data));
@ -72,4 +69,4 @@ export default function (irc: Network["irc"], network: Network) {
}
});
}
}
};

View file

@ -1,10 +1,9 @@
"use strict";
import Network from "src/models/network";
import Msg from "../../models/msg";
import STSPolicies from "../sts";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("cap ls", (data) => {
@ -76,4 +75,4 @@ export default function (irc: Network["irc"], network: Network) {
client.save();
}
}
}
};

View file

@ -1,11 +1,8 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Msg from "@src/models/msg";
import Msg from "src/models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
// If server supports CHGHOST cap, then changing the hostname does not require
@ -30,4 +27,4 @@ export default function (irc: Network["irc"], network: Network) {
chan.pushMessage(client, msg);
});
});
}
};

View file

@ -5,11 +5,8 @@ import log from "../../log";
import Msg from "../../models/msg";
import Helper from "../../helper";
import Config from "../../config";
import Network from "src/models/network";
import {ChanState, ChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
network.channels[0].pushMessage(
@ -97,7 +94,7 @@ export default function (irc: Network["irc"], network: Network) {
let ident = client.name || network.username;
if (Config.values.useHexIp) {
ident = Helper.ip2hex(client.config.browser.ip);
ident = Helper.ip2hex(client.config.browser!.ip!);
}
identSocketId = client.manager.identHandler.addSocket(socket, ident);
@ -221,4 +218,4 @@ export default function (irc: Network["irc"], network: Network) {
client.emit("network:status", toSend);
}
}
};

View file

@ -1,7 +1,6 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import _ from "lodash";
import Helper from "../../helper";
@ -19,7 +18,7 @@ const ctcpResponses = {
VERSION: () => pkg.name + " " + Helper.getVersion() + " -- " + pkg.homepage,
};
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
const lobby = network.channels[0];
@ -91,4 +90,4 @@ export default function (irc: Network["irc"], network: Network) {
{trailing: false}
)
);
}
};

View file

@ -2,10 +2,9 @@
import Msg from "../../models/msg";
import Config from "../../config";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("irc error", function (data) {
@ -93,4 +92,4 @@ export default function (irc: Network["irc"], network: Network) {
nick: irc.user.nick,
});
});
}
};

View file

@ -1,10 +1,10 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("help", function (data) {
@ -19,4 +19,4 @@ export default function (irc: Network["irc"], network: Network) {
lobby.pushMessage(client, msg, true);
}
});
}
};

View file

@ -1,11 +1,10 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("info", function (data) {
@ -20,4 +19,4 @@ export default function (irc: Network["irc"], network: Network) {
lobby.pushMessage(client, msg, true);
}
});
}
};

View file

@ -1,10 +1,10 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("invite", function (data) {
@ -27,4 +27,4 @@ export default function (irc: Network["irc"], network: Network) {
});
chan.pushMessage(client, msg);
});
}
};

View file

@ -1,8 +1,8 @@
"use strict";
import {ChanState} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import {Network} from "src/types/models/network";
import {ChanState} from "@src/types/models/channel";
import {Network} from "@src/types/models/network";
import Chan from "../../models/chan";
import Msg from "../../models/msg";

View file

@ -1,13 +1,12 @@
"use strict";
import Network from "src/models/network";
import {ChanState} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import {ChanState} from "@src/types/models/channel";
import Chan from "../../models/chan";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("kick", function (data) {
@ -40,4 +39,4 @@ export default function (irc: Network["irc"], network: Network) {
chan.removeUser(msg.target);
}
});
}
};

View file

@ -6,12 +6,13 @@ import {URL} from "url";
import mime from "mime-types";
import Config from "../../config";
import {findLinksWithSchema} from "client/js/helpers/ircmessageparser/findLinks";
import {findLinksWithSchema} from "../../../client/js/helpers/ircmessageparser/findLinks";
import storage from "../storage";
import log from "src/log";
import Client from "src/client";
import Chan from "src/models/chan";
import Msg from "src/models/msg";
import log from "@src/log";
import Client from "@src/client";
import Chan from "@src/models/chan";
import Msg from "@src/models/msg";
import {Preview} from "@src/types/plugins/preview";
const currentFetchPromises = new Map();
const imageTypeRegex = /^image\/.+/;
@ -22,7 +23,7 @@ export default function (client: Client, chan: Chan, msg: Msg, cleanText: string
return;
}
msg.previews = findLinksWithSchema(cleanText).reduce((cleanLinks, link) => {
msg.previews = findLinksWithSchema(cleanText).reduce((cleanLinks: Preview[], link) => {
const url = normalizeURL(link.link);
// If the URL is invalid and cannot be normalized, don't fetch it
@ -40,7 +41,7 @@ export default function (client: Client, chan: Chan, msg: Msg, cleanText: string
return cleanLinks;
}
const preview = {
const preview: Preview = {
type: "loading",
head: "",
body: "",
@ -56,7 +57,7 @@ export default function (client: Client, chan: Chan, msg: Msg, cleanText: string
fetch(url, {
accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
language: client.config.browser.language,
language: client.config.browser?.language,
})
.then((res) => {
parse(msg, chan, preview, res, client);
@ -115,7 +116,7 @@ function parseHtml(preview, res, client: Client) {
// Verify that thumbnail pic exists and is under allowed size
if (thumb.length) {
fetch(thumb, {language: client.config.browser.language})
fetch(thumb, {language: client.config.browser?.language})
.then((resThumb) => {
if (
resThumb !== null &&
@ -164,10 +165,16 @@ function parseHtmlMedia($: cheerio.CheerioAPI, preview, client) {
$(`meta[property="og:${type}:type"]`).each(function (i) {
const mimeType = $(this).attr("content");
if (!mimeType) {
return;
}
if (mediaTypeRegex.test(mimeType)) {
// If we match a clean video or audio tag, parse that as a preview instead
let mediaUrl = $($(`meta[property="og:${type}"]`).get(i)).attr("content");
if (!mediaUrl) {
return;
}
// Make sure media is a valid url
mediaUrl = normalizeURL(mediaUrl, preview.link, true);
@ -184,7 +191,7 @@ function parseHtmlMedia($: cheerio.CheerioAPI, preview, client) {
type === "video"
? "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5"
: "audio/webm, audio/ogg, audio/wav, audio/*;q=0.9, application/ogg;q=0.7, video/*;q=0.6; */*;q=0.5",
language: client.config.browser.language,
language: client.config.browser?.language,
})
.then((resMedia) => {
if (resMedia === null || !mediaTypeRegex.test(resMedia.type)) {
@ -460,7 +467,7 @@ function fetch(uri, headers) {
resolve({data: buffer, type, size});
});
} catch (e) {
} catch (e: any) {
return reject(e);
}
});
@ -480,25 +487,25 @@ function normalizeURL(link: string, baseLink?: string, disallowHttp = false) {
// Only fetch http and https links
if (url.protocol !== "http:" && url.protocol !== "https:") {
return null;
return undefined;
}
if (disallowHttp && url.protocol === "http:") {
return null;
return undefined;
}
// Do not fetch links without hostname or ones that contain authorization
if (!url.hostname || url.username || url.password) {
return null;
return undefined;
}
// Drop hash from the url, if any
url.hash = "";
return url.toString();
} catch (e) {
} catch (e: any) {
// if an exception was thrown, the url is not valid
}
return null;
return undefined;
}

View file

@ -1,12 +1,10 @@
"use strict";
import Msg from "src/models/msg";
import {ChanType, SpecialChanType} from "src/types/models/channel";
import {Network} from "src/types/models/network";
import Msg from "@src/models/msg";
import Chan from "../../models/chan";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
const MAX_CHANS = 500;
@ -28,13 +26,19 @@ export default function (irc: Network["irc"], network: Network) {
irc.on("channel list end", function () {
updateListStatus(
network.chanCache.sort((a, b) => b.num_users - a.num_users).slice(0, MAX_CHANS)
network.chanCache.sort((a, b) => b.num_users! - a.num_users!).slice(0, MAX_CHANS)
);
network.chanCache = [];
});
function updateListStatus(msg: Msg) {
function updateListStatus(
msg:
| {
text: string;
}
| Chan[]
) {
let chan = network.getChannel("Channel List");
if (typeof chan === "undefined") {
@ -61,4 +65,4 @@ export default function (irc: Network["irc"], network: Network) {
});
}
}
}
};

View file

@ -4,12 +4,11 @@ import Msg from "../../models/msg";
import LinkPrefetch from "./link";
import cleanIrcMessage from "../../../client/js/helpers/ircmessageparser/cleanIrcMessage";
import Helper from "../../helper";
import Network from "src/models/network";
import {ChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
const nickRegExp = /(?:\x03[0-9]{1,2}(?:,[0-9]{1,2})?)?([\w[\]\\`^{|}-]+)/g;
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("notice", function (data) {
@ -215,4 +214,4 @@ export default function (irc: Network["irc"], network: Network) {
}
}
}
}
};

View file

@ -1,11 +1,11 @@
"use strict";
import _ from "lodash";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
// The following saves the channel key based on channel mode instead of
@ -146,4 +146,4 @@ export default function (irc: Network["irc"], network: Network) {
});
}
});
}
};

View file

@ -1,13 +1,12 @@
"use strict";
import Network from "src/models/network";
import {ChanType, SpecialChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import {ChanType, SpecialChanType} from "@src/types/models/channel";
import Chan from "../../models/chan";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("banlist", (list) => {
@ -84,4 +83,4 @@ export default function (irc: Network["irc"], network: Network) {
});
}
}
}
};

View file

@ -1,10 +1,10 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("motd", function (data) {
@ -28,4 +28,4 @@ export default function (irc: Network["irc"], network: Network) {
lobby.pushMessage(client, msg);
}
});
}
};

View file

@ -1,8 +1,8 @@
"use strict";
import Network from "src/models/network";
import Network from "@src/models/network";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("userlist", function (data) {
@ -27,4 +27,4 @@ export default function (irc: Network["irc"], network: Network) {
chan: chan.id,
});
});
}
};

View file

@ -1,10 +1,10 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("nick", function (data) {
@ -50,4 +50,4 @@ export default function (irc: Network["irc"], network: Network) {
});
});
});
}
};

View file

@ -1,11 +1,10 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("part", function (data) {
@ -32,4 +31,4 @@ export default function (irc: Network["irc"], network: Network) {
chan.removeUser(user);
}
});
}
};

View file

@ -1,10 +1,10 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("quit", function (data) {
@ -33,4 +33,4 @@ export default function (irc: Network["irc"], network: Network) {
network.keepNick = null;
}
});
}
};

View file

@ -1,11 +1,10 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("loggedin", (data) => {
@ -27,4 +26,4 @@ export default function (irc: Network["irc"], network: Network) {
});
lobby.pushMessage(client, msg, true);
});
}
};

View file

@ -1,10 +1,10 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("topic", function (data) {
@ -45,4 +45,4 @@ export default function (irc: Network["irc"], network: Network) {
});
chan.pushMessage(client, msg);
});
}
};

View file

@ -1,10 +1,10 @@
"use strict";
import Network from "src/models/network";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("unknown command", function (command) {
@ -36,4 +36,4 @@ export default function (irc: Network["irc"], network: Network) {
true
);
});
}
};

View file

@ -1,9 +1,9 @@
"use strict";
import Network from "src/models/network";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("registered", function (data) {
@ -21,4 +21,4 @@ export default function (irc: Network["irc"], network: Network) {
nick: data.nick,
});
});
}
};

View file

@ -1,11 +1,10 @@
"use strict";
import Network from "src/models/network";
import {ChanType} from "src/types/models/channel";
import {MessageType} from "src/types/models/message";
import Network from "@src/models/network";
import Msg from "../../models/msg";
export default function (irc: Network["irc"], network: Network) {
export default <IrcEventHandler>function (irc, network) {
const client = this;
irc.on("whois", handleWhois);
@ -60,4 +59,4 @@ export default function (irc: Network["irc"], network: Network) {
chan.pushMessage(client, msg);
}
}
};

View file

@ -6,17 +6,14 @@ import fs from "fs";
import Config from "../../config";
import Msg from "../../models/msg";
import type {Database} from "sqlite3";
import {Network} from "src/types/models/network";
import {Channel} from "src/types/models/channel";
import {Message} from "src/types/models/message";
import Client from "src/client";
import Chan from "src/models/chan";
import Client from "@src/client";
import Chan from "@src/models/chan";
let sqlite3;
try {
sqlite3 = require("sqlite3");
} catch (e) {
} catch (e: any) {
Config.values.messageStorage = Config.values.messageStorage.filter((item) => item !== "sqlite");
log.error(
@ -37,7 +34,7 @@ const schema = [
class SqliteMessageStorage implements SqliteMessageStorage {
client: Client;
isEnabled: boolean;
database: Database;
database!: Database;
constructor(client: Client) {
this.client = client;
@ -50,8 +47,8 @@ class SqliteMessageStorage implements SqliteMessageStorage {
try {
fs.mkdirSync(logsPath, {recursive: true});
} catch (e) {
log.error("Unable to create logs directory", e);
} catch (e: any) {
log.error("Unable to create logs directory", e as string);
return;
}
@ -108,7 +105,7 @@ class SqliteMessageStorage implements SqliteMessageStorage {
});
}
close(callback?: (error?: Error) => void) {
close(callback?: (error?: Error | null) => void) {
if (!this.isEnabled) {
return;
}
@ -265,7 +262,7 @@ class SqliteMessageStorage implements SqliteMessageStorage {
export default SqliteMessageStorage;
function parseSearchRowsToMessages(id, rows) {
const messages = [];
const messages: Msg[] = [];
for (const row of rows) {
const msg = JSON.parse(row.msg);

View file

@ -6,11 +6,8 @@ import path from "path";
import filenamify from "filenamify";
import Config from "../../config";
import Msg from "../../models/msg";
import {Network} from "src/types/models/network";
import {Channel} from "src/types/models/channel";
import {Message, MessageType} from "src/types/models/message";
import {MessageStorage} from "src/types/plugins/messageStorage";
import Client from "src/client";
import {MessageStorage} from "@src/types/plugins/messageStorage";
import Client from "@src/client";
class TextFileMessageStorage implements MessageStorage {
client: Client;
@ -46,8 +43,8 @@ class TextFileMessageStorage implements MessageStorage {
try {
fs.mkdirSync(logPath, {recursive: true});
} catch (e) {
log.error("Unable to create logs directory", e);
} catch (e: any) {
log.error("Unable to create logs directory", e as string);
return;
}

View file

@ -12,7 +12,7 @@ const packageMap = new Map();
import inputs from "../inputs";
import fs from "fs";
import Utils from "../../command-line/utils";
import Client from "src/client";
import Client from "@src/client";
const stylesheets: string[] = [];
const files: string[] = [];
@ -33,22 +33,26 @@ export default {
outdated,
};
// TODO: verify binds worked. Used to be 'this' instead of 'packageApis'
const packageApis = function (packageInfo) {
return {
Stylesheets: {
addFile: addStylesheet.bind(this, packageInfo.packageName),
addFile: addStylesheet.bind(packageApis, packageInfo.packageName),
},
PublicFiles: {
add: addFile.bind(this, packageInfo.packageName),
add: addFile.bind(packageApis, packageInfo.packageName),
},
Commands: {
add: inputs.addPluginCommand.bind(this, packageInfo),
add: inputs.addPluginCommand.bind(packageApis, packageInfo),
runAsUser: (command: string, targetId: number, client: Client) =>
client.inputLine({target: targetId, text: command}),
},
Config: {
getConfig: () => Config.values,
getPersistentStorageDir: getPersistentStorageDir.bind(this, packageInfo.packageName),
getPersistentStorageDir: getPersistentStorageDir.bind(
packageApis,
packageInfo.packageName
),
},
Logger: {
error: (...args) => log.error(`[${packageInfo.packageName}]`, ...args),
@ -83,7 +87,7 @@ function getEnabledPackages(packageJson: string) {
try {
const json = JSON.parse(fs.readFileSync(packageJson, "utf-8"));
return Object.keys(json.dependencies);
} catch (e) {
} catch (e: any) {
log.error(`Failed to read packages/package.json: ${colors.red(e)}`);
}
@ -120,9 +124,11 @@ function loadPackage(packageName: string) {
}
packageFile = require(packagePath);
} catch (e) {
} catch (e: any) {
log.error(`Package ${colors.bold(packageName)} could not be loaded: ${colors.red(e)}`);
log.debug(e.stack);
if (e instanceof Error) {
log.debug(e.stack ? e.stack : e.message);
}
return;
}
@ -136,6 +142,8 @@ function loadPackage(packageName: string) {
packageMap.set(packageName, packageFile);
if (packageInfo.type === "theme") {
// TODO: investigate
//@ts-ignore
themes.addTheme(packageName, packageInfo);
if (packageInfo.files) {

View file

@ -1,6 +1,5 @@
import Client from "src/client";
import Chan from "src/models/chan";
import {MessageType, UserInMessage} from "src/types/models/message";
import Client from "@src/client";
import Chan from "@src/models/chan";
import Msg from "../../models/msg";
export default class PublicClient {

View file

@ -26,7 +26,7 @@ function loadLocalThemes() {
.forEach((theme) => themes.set(theme.name, theme));
}
function addTheme(packageName: string, packageObject) {
function addTheme(packageName: string, packageObject: ThemeModule) {
const theme = makePackageThemeObject(packageName, packageObject);
if (theme) {
@ -35,7 +35,7 @@ function addTheme(packageName: string, packageObject) {
}
function getAll() {
const filteredThemes = [];
const filteredThemes: ThemeForClient[] = [];
for (const theme of themes.values()) {
filteredThemes.push(_.pick(theme, ["displayName", "name", "themeColor"]));
@ -44,7 +44,7 @@ function getAll() {
return _.sortBy(filteredThemes, "displayName");
}
function getByName(name) {
function getByName(name: string) {
return themes.get(name);
}
@ -57,7 +57,10 @@ function makeLocalThemeObject(css: string) {
};
}
function makePackageThemeObject(moduleName: string, module: ThemeModule) {
function makePackageThemeObject(
moduleName: string,
module: ThemeModule
): ThemeForClient | undefined {
if (!module || module.type !== "theme") {
return;
}

View file

@ -22,7 +22,7 @@ class Storage {
try {
items = fs.readdirSync(dir);
} catch (e) {
} catch (e: any) {
fs.mkdirSync(dir, {recursive: true});
return;
}

View file

@ -5,7 +5,7 @@ import fs from "fs";
import path from "path";
import log from "../log";
import Config from "../config";
import type {PolicyMap, PolicyOption} from "src/types/plugins/sts";
import type {PolicyMap, PolicyOption} from "@src/types/plugins/sts";
class STSPolicies {
private stsFile: string;
@ -76,7 +76,7 @@ class STSPolicies {
}
saveFile() {
const policiesToStore = [];
const policiesToStore: PolicyOption[] = [];
this.policies.forEach((value, key) => {
policiesToStore.push({
@ -91,7 +91,7 @@ class STSPolicies {
fs.writeFile(this.stsFile, file, {flag: "w+"}, (err) => {
if (err) {
log.error("Failed to update STS policies file!", err);
log.error("Failed to update STS policies file!", err.message);
}
});
}

View file

@ -1,16 +1,18 @@
"use strict";
const Config = require("../config");
const busboy = require("@fastify/busboy");
const {v4: uuidv4} = require("uuid");
const path = require("path");
const fs = require("fs");
const fileType = require("file-type");
const readChunk = require("read-chunk");
const crypto = require("crypto");
const isUtf8 = require("is-utf8");
const log = require("../log");
const contentDisposition = require("content-disposition");
import Config from "../config";
import busboy, {BusboyHeaders} from "@fastify/busboy";
import {v4 as uuidv4} from "uuid";
import path from "path";
import fs from "fs";
import fileType from "file-type";
import readChunk from "read-chunk";
import crypto from "crypto";
import isUtf8 from "is-utf8";
import log from "../log";
import contentDisposition from "content-disposition";
import type {Socket} from "socket.io";
import {Request, Response} from "express";
// Map of allowed mime types to their respecive default filenames
// that will be rendered in browser without forcing them to be downloaded
@ -38,7 +40,7 @@ const inlineContentDispositionTypes = {
const uploadTokens = new Map();
class Uploader {
constructor(socket) {
constructor(socket: Socket) {
socket.on("upload:auth", () => {
const token = uuidv4();
@ -67,16 +69,17 @@ class Uploader {
});
}
static createTokenTimeout(token) {
static createTokenTimeout(token: string) {
return setTimeout(() => uploadTokens.delete(token), 60 * 1000);
}
static router(express) {
// TODO: type
static router(express: any) {
express.get("/uploads/:name/:slug*?", Uploader.routeGetFile);
express.post("/uploads/new/:token", Uploader.routeUploadFile);
}
static async routeGetFile(req, res) {
static async routeGetFile(req: Request, res: Response) {
const name = req.params.name;
const nameRegex = /^[0-9a-f]{16}$/;
@ -130,13 +133,13 @@ class Uploader {
return res.sendFile(filePath);
}
static routeUploadFile(req, res) {
let busboyInstance;
let uploadUrl;
let randomName;
let destDir;
let destPath;
let streamWriter;
static routeUploadFile(req: Request, res: Response) {
let busboyInstance: NodeJS.WritableStream | busboy | null | undefined;
let uploadUrl: string | URL;
let randomName: string;
let destDir: fs.PathLike;
let destPath: fs.PathLike | null;
let streamWriter: fs.WriteStream | null;
const doneCallback = () => {
// detach the stream and drain any remaining data
@ -155,7 +158,7 @@ class Uploader {
}
};
const abortWithError = (err) => {
const abortWithError = (err: any) => {
doneCallback();
// if we ended up erroring out, delete the output file from disk
@ -173,12 +176,17 @@ class Uploader {
}
// if the request does not contain any body data, bail out
if (req.headers["content-length"] < 1) {
if (req.headers["content-length"] && parseInt(req.headers["content-length"]) < 1) {
return abortWithError(Error("Length Required"));
}
// Only allow multipart, as busboy can throw an error on unsupported types
if (!req.headers["content-type"].startsWith("multipart/form-data")) {
if (
!(
req.headers["content-type"] &&
req.headers["content-type"].startsWith("multipart/form-data")
)
) {
return abortWithError(Error("Unsupported Content Type"));
}
@ -186,7 +194,7 @@ class Uploader {
// because it can throw on malformed headers
try {
busboyInstance = new busboy({
headers: req.headers,
headers: req.headers as BusboyHeaders,
limits: {
files: 1, // only allow one file per upload
fileSize: Uploader.getMaxFileSize(),
@ -216,8 +224,8 @@ class Uploader {
// too many files on one folder
try {
fs.mkdirSync(destDir, {recursive: true});
} catch (err) {
log.err(`Error ensuring ${destDir} exists for uploads: ${err.message}`);
} catch (err: any) {
log.error(`Error ensuring ${destDir} exists for uploads: ${err.message}`);
return abortWithError(err);
}
@ -225,28 +233,47 @@ class Uploader {
streamWriter = fs.createWriteStream(destPath);
streamWriter.on("error", abortWithError);
busboyInstance.on("file", (fieldname, fileStream, filename) => {
uploadUrl = `${randomName}/${encodeURIComponent(filename)}`;
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
) => {
uploadUrl = `${randomName}/${encodeURIComponent(filename)}`;
if (Config.values.fileUpload.baseUrl) {
uploadUrl = new URL(uploadUrl, Config.values.fileUpload.baseUrl).toString();
} else {
uploadUrl = `uploads/${uploadUrl}`;
if (Config.values.fileUpload.baseUrl) {
uploadUrl = new URL(uploadUrl, Config.values.fileUpload.baseUrl).toString();
} else {
uploadUrl = `uploads/${uploadUrl}`;
}
// if the busboy data stream errors out or goes over the file size limit
// abort the processing with an error
// TODO: fix types
//@ts-ignore
fileStream.on("error", abortWithError);
//@ts-ignore
fileStream.on("limit", () => {
fileStream.unpipe(streamWriter);
fileStream.on("readable", fileStream.read.bind(fileStream));
abortWithError(Error("File size limit reached"));
});
// Attempt to write the stream to file
fileStream.pipe(streamWriter);
}
// if the busboy data stream errors out or goes over the file size limit
// abort the processing with an error
fileStream.on("error", abortWithError);
fileStream.on("limit", () => {
fileStream.unpipe(streamWriter);
fileStream.on("readable", fileStream.read.bind(fileStream));
abortWithError(Error("File size limit reached"));
});
// Attempt to write the stream to file
fileStream.pipe(streamWriter);
});
);
busboyInstance.on("finish", () => {
doneCallback();
@ -279,7 +306,7 @@ class Uploader {
// Returns null if an error occurred (e.g. file not found)
// Returns a string with the type otherwise
static async getFileType(filePath) {
static async getFileType(filePath: string) {
try {
const buffer = await readChunk(filePath, 0, 5120);
@ -298,7 +325,7 @@ class Uploader {
// otherwise assume it's random binary data
return "application/octet-stream";
} catch (e) {
} catch (e: any) {
if (e.code !== "ENOENT") {
log.warn(`Failed to read ${filePath}: ${e.message}`);
}
@ -308,4 +335,4 @@ class Uploader {
}
}
module.exports = Uploader;
export default Uploader;

View file

@ -1,17 +1,23 @@
"use strict";
const _ = require("lodash");
const log = require("../log");
const fs = require("fs");
const path = require("path");
const WebPushAPI = require("web-push");
const Config = require("../config");
import _ from "lodash";
import log from "../log";
import fs from "fs";
import path from "path";
import WebPushAPI from "web-push";
import Config from "../config";
import Client from "@src/client";
class WebPush {
vapidKeys?: {
publicKey: string;
privateKey: string;
};
constructor() {
const vapidPath = path.join(Config.getHomePath(), "vapid.json");
let vapidStat = undefined;
let vapidStat: fs.Stats | undefined = undefined;
try {
vapidStat = fs.statSync(vapidPath);
@ -64,7 +70,7 @@ class WebPush {
);
}
push(client, payload, onlyToOffline) {
push(client: Client, payload: any, onlyToOffline: boolean) {
_.forOwn(client.config.sessions, ({pushSubscription}, token) => {
if (pushSubscription) {
if (onlyToOffline && _.find(client.attachedClients, {token}) !== undefined) {
@ -76,7 +82,7 @@ class WebPush {
});
}
pushSingle(client, subscription, payload) {
pushSingle(client: Client, subscription: WebPushAPI.PushSubscription, payload: any) {
WebPushAPI.sendNotification(subscription, JSON.stringify(payload)).catch((error) => {
if (error.statusCode >= 400 && error.statusCode < 500) {
log.warn(
@ -97,4 +103,4 @@ class WebPush {
}
}
module.exports = WebPush;
export default WebPush;

View file

@ -8,7 +8,7 @@ import ClientManager from "./clientManager";
import express from "express";
import fs from "fs";
import path from "path";
import {Server} from "socket.io";
import {Server, Socket} from "socket.io";
import dns from "dns";
import Uploader from "./plugins/uploader";
import Helper from "./helper";
@ -33,14 +33,13 @@ import {
} from "./types/config";
import {Server as wsServer} from "ws";
import {ChanType} from "./types/models/channel";
// A random number that will force clients to reload the page if it differs
const serverHash = Math.floor(Date.now() * Math.random());
let manager = null;
let manager: ClientManager | null = null;
export default function (
export default async function (
options: ServerOptions = {
dev: false,
}
@ -59,7 +58,7 @@ export default function (
const app = express();
if (options.dev) {
require("./plugins/dev-server.js")(app);
(await import("./plugins/dev-server.js")).default(app);
}
app.set("env", "production")
@ -105,7 +104,8 @@ export default function (
return res.sendFile(path.join(packagePath, fileName));
});
let server = null;
// TODO; type to ReturnType<createServer
let server: any = null;
if (Config.values.public && (Config.values.ldap || {}).enable) {
log.warn(
@ -114,8 +114,8 @@ export default function (
}
if (!Config.values.https.enable) {
server = require("http");
server = server.createServer(app);
const createServer = (await import("http")).createServer;
server = createServer(app);
} else {
const keyPath = Helper.expandHome(Config.values.https.key);
const certPath = Helper.expandHome(Config.values.https.certificate);
@ -221,11 +221,11 @@ export default function (
process.exit(1);
}
manager.init(identHandler, sockets);
manager!.init(identHandler, sockets);
});
// Handle ctrl+c and kill gracefully
let suicideTimeout = null;
let suicideTimeout: NodeJS.Timeout | null = null;
const exitGracefully = function () {
if (suicideTimeout !== null) {
@ -235,7 +235,7 @@ export default function (
log.info("Exiting...");
// Close all client and IRC connections
manager.clients.forEach((client) => client.quit());
manager!.clients.forEach((client) => client.quit());
if (Config.values.prefetchStorage) {
log.info("Clearing prefetch storage folder, this might take a while...");
@ -248,7 +248,9 @@ export default function (
// Close http server
server.close(() => {
clearTimeout(suicideTimeout);
if (suicideTimeout !== null) {
clearTimeout(suicideTimeout);
}
process.exit(0);
});
};
@ -481,7 +483,10 @@ function initializeClient(socket, client, token, lastMessage, openChannel) {
const hash = Helper.password.hash(p1);
client.setPassword(hash, (success: boolean) => {
const obj = {success: false, error: undefined};
const obj = {success: false, error: undefined} as {
success: boolean;
error: string | undefined;
};
if (success) {
obj.success = true;
@ -701,7 +706,7 @@ function initializeClient(socket, client, token, lastMessage, openChannel) {
}
for (const attachedClient of Object.keys(client.attachedClients)) {
manager.sockets.in(attachedClient).emit("mute:changed", {
manager!.sockets.in(attachedClient).emit("mute:changed", {
target,
status: setMutedTo,
});
@ -730,10 +735,10 @@ function initializeClient(socket, client, token, lastMessage, openChannel) {
return;
}
const socketToRemove = manager.sockets.of("/").sockets.get(socketId);
const socketToRemove = manager!.sockets.of("/").sockets.get(socketId);
socketToRemove.emit("sign-out");
socketToRemove.disconnect();
socketToRemove!.emit("sign-out");
socketToRemove!.disconnect();
});
// Do not send updated session list if user simply logs out
@ -798,7 +803,7 @@ function getClientConfiguration(): ClientConfiguration {
}
config.isUpdateAvailable = changelog.isUpdateAvailable;
config.applicationServerKey = manager.webPush.vapidKeys.publicKey;
config.applicationServerKey = manager!.webPush.vapidKeys!.publicKey;
config.version = pkg.version;
config.gitCommit = Helper.getGitCommit();
config.themes = themes.getAll();
@ -823,7 +828,7 @@ function getServerConfiguration(): ServerConfiguration {
return config;
}
function performAuthentication(data) {
function performAuthentication(this: Socket, data) {
if (!_.isPlainObject(data)) {
return;
}
@ -858,19 +863,19 @@ function performAuthentication(data) {
return finalInit();
}
reverseDnsLookup(client.config.browser.ip, (hostname) => {
client.config.browser.hostname = hostname;
reverseDnsLookup(client.config.browser?.ip, (hostname) => {
client.config.browser!.hostname = hostname;
finalInit();
});
};
if (Config.values.public) {
client = new Client(manager);
manager.clients.push(client);
client = new Client(manager!);
manager!.clients.push(client);
socket.on("disconnect", function () {
manager.clients = _.without(manager.clients, client);
manager!.clients = _.without(manager!.clients, client);
client.quit();
});
@ -907,13 +912,13 @@ function performAuthentication(data) {
// If authorization succeeded but there is no loaded user,
// load it and find the user again (this happens with LDAP)
if (!client) {
client = manager.loadUser(data.user);
client = manager!.loadUser(data.user);
}
initClient();
};
client = manager.findClient(data.user);
client = manager!.findClient(data.user);
// We have found an existing user and client has provided a token
if (client && data.token) {

View file

@ -1,11 +1,17 @@
{
"extends": "../tsconfig.json",
"exclude": ["../client/*", "./dist/*"],
"extends": "../tsconfig.base.json",
"files": [
"../package.json",
"../client/js/constants.js",
"../client/js/helpers/ircmessageparser/findLinks.ts",
"./index.d.ts"
],
// "exclude": ["../client/*", "./dist/*"],
"exclude": ["./dist/*"],
"ts-node": {
"files": true
},
"include": ["**/*.ts", "**/*.js", "**/*.d.ts"],
"files": ["index.d.ts"],
"include": ["./**/*.ts", "./**/*.js", "./**/*.d.ts"],
"compilerOptions": {
"allowJs": true,
"checkJs": true,
@ -13,12 +19,12 @@
"baseUrl": ".",
"noImplicitAny": false,
"outDir": "dist",
"resolveJsonModule": true,
// "resolveJsonModule": true,
"esModuleInterop": true,
"moduleResolution": "node",
"paths": {
"src/*": ["./*"],
"client/*": ["../client/*"]
"@src/*": ["./*"],
"@client/*": ["../client/*"]
}
}
}

View file

@ -8,7 +8,7 @@ type ClientConfig = {
lastUse: number;
ip: string;
agent: string;
pushSubscription: PushSubscription;
pushSubscription?: PushSubscription;
};
};
clientSettings: {

View file

@ -37,9 +37,9 @@ type ClientConfiguration = Pick<
isUpdateAvailable: boolean;
applicationServerKey: string;
version: string;
gitCommit: string;
gitCommit: string | null;
defaultTheme: string;
themes: string[];
themes: ThemeForClient[];
defaults: Defaults & {
sasl?: string;
saslAccount?: string;
@ -99,6 +99,7 @@ export type Ldap = {
tlsOptions: any;
primaryKey: string;
searchDN: SearchDN;
baseDN?: string;
};
export type TlsOptions = any;

View file

@ -1,27 +1,28 @@
import Chan from "../../models/chan";
declare global {
export type Channel = Chan;
export type Channel = Chan;
export type FilteredChannel = Chan & {
users: [];
totalMessages: number;
};
export type FilteredChannel = Chan & {
users: [];
totalMessages: number;
};
export enum ChanType {
CHANNEL = "channel",
LOBBY = "lobby",
QUERY = "query",
SPECIAL = "special",
}
export enum ChanType {
CHANNEL = "channel",
LOBBY = "lobby",
QUERY = "query",
SPECIAL = "special",
}
export enum SpecialChanType {
BANLIST = "list_bans",
INVITELIST = "list_invites",
CHANNELLIST = "list_channels",
IGNORELIST = "list_ignored",
}
export enum ChanState {
PARTED = 0,
JOINED = 1,
export enum SpecialChanType {
BANLIST = "list_bans",
INVITELIST = "list_invites",
CHANNELLIST = "list_channels",
IGNORELIST = "list_ignored",
}
export enum ChanState {
PARTED = 0,
JOINED = 1,
}
}

View file

@ -1,3 +1,5 @@
/// <reference path="channel.d.ts" />
/// <reference path="prefix.d.ts" />
/// <reference path="message.d.ts" />
/// <reference path="user.d.ts" />
/// <reference path="network.d.ts" />

View file

@ -1,43 +1,44 @@
import Msg from "src/models/msg";
import User from "src/models/user";
import Msg from "@src/models/msg";
import User from "@src/models/user";
declare global {
type Message = Msg;
type Message = Msg;
type UserInMessage = Partial<User> & {
mode: string;
};
type UserInMessage = Partial<User> & {
mode: string;
};
type MessagePreview = {
link: string;
};
type MessagePreview = {
link: string;
};
export enum MessageType {
UNHANDLED = "unhandled",
ACTION = "action",
AWAY = "away",
BACK = "back",
ERROR = "error",
INVITE = "invite",
JOIN = "join",
KICK = "kick",
LOGIN = "login",
LOGOUT = "logout",
MESSAGE = "message",
MODE = "mode",
MODE_CHANNEL = "mode_channel",
MODE_USER = "mode_user", // RPL_UMODEIS
MONOSPACE_BLOCK = "monospace_block",
NICK = "nick",
NOTICE = "notice",
PART = "part",
QUIT = "quit",
CTCP = "ctcp",
CTCP_REQUEST = "ctcp_request",
CHGHOST = "chghost",
TOPIC = "topic",
TOPIC_SET_BY = "topic_set_by",
WHOIS = "whois",
RAW = "raw",
PLUGIN = "plugin",
WALLOPS = "wallops",
export enum MessageType {
UNHANDLED = "unhandled",
ACTION = "action",
AWAY = "away",
BACK = "back",
ERROR = "error",
INVITE = "invite",
JOIN = "join",
KICK = "kick",
LOGIN = "login",
LOGOUT = "logout",
MESSAGE = "message",
MODE = "mode",
MODE_CHANNEL = "mode_channel",
MODE_USER = "mode_user", // RPL_UMODEIS
MONOSPACE_BLOCK = "monospace_block",
NICK = "nick",
NOTICE = "notice",
PART = "part",
QUIT = "quit",
CTCP = "ctcp",
CTCP_REQUEST = "ctcp_request",
CHGHOST = "chghost",
TOPIC = "topic",
TOPIC_SET_BY = "topic_set_by",
WHOIS = "whois",
RAW = "raw",
PLUGIN = "plugin",
WALLOPS = "wallops",
}
}

View file

@ -1,14 +1,51 @@
import NetworkClass from "src/models/network";
import NetworkClass from "@src/models/network";
import {Client as IRCClient} from "irc-framework";
import {WebIRC} from "../config";
declare global {
export type Network = NetworkClass;
export type Network = NetworkClass;
type NetworkIrcOptions = {
host: string;
port: number;
password: string;
nick: string;
username: string;
gecos: string;
tls: boolean;
rejectUnauthorized: boolean;
webirc: WebIRC;
client_certificate: ClientCertificate | null;
socks?: {
host: string;
port: number;
user: string;
pass: string;
};
sasl_mechanism?: string;
account?:
| {
account: string;
password: string;
}
| {};
};
export type NetworkStatus = {
connected: boolean;
secure: boolean;
};
type NonNullableIRCWithOptions = NonNullable<IRCClient & {options: NetworkIrcOptions}>;
type IgnoreListItem = Hostmask & {
when?: number;
};
type NetworkWithIrcFramework = Network & {
irc: NonNullable<Network["irc"]> & {
options: NonNullableIRCWithOptions;
};
};
type IgnoreList = IgnoreListItem[];
type NetworkStatus = {
connected: boolean;
secure: boolean;
};
type IgnoreListItem = Hostmask & {
when?: number;
};
type IgnoreList = IgnoreListItem[];
}

View file

@ -1,3 +1,4 @@
import UserClass from "src/models/user";
export type User = UserClass;
import UserClass from "@src/models/user";
declare global {
export type User = UserClass;
}

View file

@ -37,6 +37,8 @@ declare module "irc-framework" {
enabled: string[];
};
extractTargetGroup: (target: string) => any;
supports(feature: "MODES"): string;
supports(feature: string): boolean;
};
// End of added by Max

View file

@ -8,3 +8,10 @@ type ThemeModule = Module & {
themeColor: string;
css: string;
};
type ThemeForClient = {
displayName: string;
filename?: string;
name: string;
themeColor: string | null;
};

Some files were not shown because too many files have changed in this diff Show more