From 01d640db47b1ba26775e2cd4ef99685d3ec06b47 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 1 May 2022 22:56:38 -0700 Subject: [PATCH 01/88] ts progress --- .eslintrc.yml | 81 ++ client/components/App.vue | 54 +- client/js/constants.js | 3 +- client/js/helpers/contextMenu.js | 2 +- .../js/{localStorage.js => localStorage.ts} | 6 +- client/js/{vue.js => vue.ts} | 26 +- client/tsconfig.json | 28 + client/types.d.ts | 4 + package.json | 17 +- {scripts => src}/changelog.js | 0 src/client.js | 771 ----------------- src/client.ts | 787 ++++++++++++++++++ src/clientManager.js | 284 ------- src/clientManager.ts | 295 +++++++ src/command-line/{index.js => index.ts} | 19 +- src/command-line/{install.js => install.ts} | 15 +- src/command-line/{outdated.js => outdated.ts} | 9 +- src/command-line/{start.js => start.ts} | 16 +- .../{uninstall.js => uninstall.ts} | 11 +- src/command-line/{upgrade.js => upgrade.ts} | 3 +- src/command-line/{utils.js => utils.ts} | 23 +- src/{config.js => config.ts} | 37 +- src/{helper.js => helper.ts} | 35 +- src/{identification.js => identification.ts} | 34 +- src/index.d.ts | 1 + src/models/chan.js | 304 ------- src/models/chan.ts | 293 +++++++ src/models/msg.js | 92 -- src/models/msg.ts | 80 ++ src/models/network.js | 558 ------------- src/models/network.ts | 627 ++++++++++++++ src/models/{prefix.js => prefix.ts} | 12 +- src/models/user.js | 35 - src/models/user.ts | 45 + src/plugins/{auth.js => auth.ts} | 15 +- src/plugins/auth/{ldap.js => ldap.ts} | 41 +- ...entCertificate.js => clientCertificate.ts} | 28 +- src/plugins/inputs/action.js | 2 +- src/plugins/inputs/ban.js | 2 +- src/plugins/inputs/ignore.js | 2 +- src/plugins/inputs/invite.js | 2 +- src/plugins/inputs/kick.js | 2 +- src/plugins/inputs/mode.js | 4 +- src/plugins/inputs/msg.js | 3 +- src/plugins/inputs/part.js | 4 +- src/plugins/inputs/rejoin.js | 2 +- src/plugins/inputs/topic.js | 2 +- src/plugins/irc-events/away.js | 6 +- .../{connection.js => connection.ts} | 38 +- src/plugins/irc-events/list.js | 2 +- src/plugins/irc-events/message.js | 14 +- src/plugins/irc-events/modelist.js | 2 +- src/plugins/irc-events/whois.js | 2 +- .../messageStorage/{sqlite.js => sqlite.ts} | 38 +- .../messageStorage/{text.js => text.ts} | 58 +- src/plugins/packages/{index.js => index.ts} | 57 +- src/plugins/packages/{themes.js => themes.ts} | 18 +- src/plugins/{sts.js => sts.ts} | 25 +- src/{server.js => server.ts} | 103 ++- src/tsconfig.json | 11 + src/types/client.d.ts | 40 + src/types/config.d.ts | 113 +++ src/types/helper.d.ts | 5 + src/types/index.d.ts | 5 + src/types/models/channel.d.ts | 27 + src/types/models/index.d.ts | 3 + src/types/models/message.d.ts | 43 + src/types/models/network.d.ts | 8 + src/types/models/prefix.d.ts | 6 + src/types/models/user.d.ts | 3 + src/types/modules/irc-framework.d.ts | 414 +++++++++ src/types/packages/index.d.ts | 9 + src/types/packages/themes.d.ts | 10 + src/types/plugins/clientCertificate.d.ts | 4 + src/types/plugins/index.d.ts | 3 + src/types/plugins/messageStorage/index.d.ts | 26 + src/types/plugins/sts.d.ts | 8 + src/types/server.d.ts | 3 + test/commands/mode.js | 2 +- test/models/network.js | 32 +- tsconfig.json | 14 + vetur.config.js | 3 + webpack.config.js | 49 +- yarn.lock | 300 ++++++- 84 files changed, 3818 insertions(+), 2402 deletions(-) create mode 100644 .eslintrc.yml rename client/js/{localStorage.js => localStorage.ts} (92%) rename client/js/{vue.js => vue.ts} (75%) create mode 100644 client/tsconfig.json create mode 100644 client/types.d.ts rename {scripts => src}/changelog.js (100%) delete mode 100644 src/client.js create mode 100644 src/client.ts delete mode 100644 src/clientManager.js create mode 100644 src/clientManager.ts rename src/command-line/{index.js => index.ts} (91%) rename src/command-line/{install.js => install.ts} (91%) rename src/command-line/{outdated.js => outdated.ts} (73%) rename src/command-line/{start.js => start.ts} (76%) rename src/command-line/{uninstall.js => uninstall.ts} (84%) rename src/command-line/{upgrade.js => upgrade.ts} (95%) rename src/command-line/{utils.js => utils.ts} (92%) rename src/{config.js => config.ts} (84%) rename src/{helper.js => helper.ts} (83%) rename src/{identification.js => identification.ts} (79%) create mode 100644 src/index.d.ts delete mode 100644 src/models/chan.js create mode 100644 src/models/chan.ts delete mode 100644 src/models/msg.js create mode 100644 src/models/msg.ts delete mode 100644 src/models/network.js create mode 100644 src/models/network.ts rename src/models/{prefix.js => prefix.ts} (66%) delete mode 100644 src/models/user.js create mode 100644 src/models/user.ts rename src/plugins/{auth.js => auth.ts} (88%) rename src/plugins/auth/{ldap.js => ldap.ts} (86%) rename src/plugins/{clientCertificate.js => clientCertificate.ts} (84%) rename src/plugins/irc-events/{connection.js => connection.ts} (85%) rename src/plugins/messageStorage/{sqlite.js => sqlite.ts} (87%) rename src/plugins/messageStorage/{text.js => text.ts} (77%) rename src/plugins/packages/{index.js => index.ts} (82%) rename src/plugins/packages/{themes.js => themes.ts} (81%) rename src/plugins/{sts.js => sts.ts} (77%) rename src/{server.js => server.ts} (92%) create mode 100644 src/tsconfig.json create mode 100644 src/types/client.d.ts create mode 100644 src/types/config.d.ts create mode 100644 src/types/helper.d.ts create mode 100644 src/types/index.d.ts create mode 100644 src/types/models/channel.d.ts create mode 100644 src/types/models/index.d.ts create mode 100644 src/types/models/message.d.ts create mode 100644 src/types/models/network.d.ts create mode 100644 src/types/models/prefix.d.ts create mode 100644 src/types/models/user.d.ts create mode 100644 src/types/modules/irc-framework.d.ts create mode 100644 src/types/packages/index.d.ts create mode 100644 src/types/packages/themes.d.ts create mode 100644 src/types/plugins/clientCertificate.d.ts create mode 100644 src/types/plugins/index.d.ts create mode 100644 src/types/plugins/messageStorage/index.d.ts create mode 100644 src/types/plugins/sts.d.ts create mode 100644 src/types/server.d.ts create mode 100644 tsconfig.json create mode 100644 vetur.config.js diff --git a/.eslintrc.yml b/.eslintrc.yml new file mode 100644 index 0000000000..7633ddbfbf --- /dev/null +++ b/.eslintrc.yml @@ -0,0 +1,81 @@ +--- +root: true + +parserOptions: + ecmaVersion: 2022 + parser: "babel-eslint" + +env: + es6: true + browser: true + mocha: true + node: true + +rules: + block-scoped-var: error + curly: [error, all] + dot-notation: error + eqeqeq: error + handle-callback-err: error + no-alert: error + no-catch-shadow: error + no-control-regex: off + no-console: error + no-duplicate-imports: error + no-else-return: error + no-implicit-globals: error + no-restricted-globals: + - error + - event + - fdescribe + no-shadow: error + no-template-curly-in-string: error + no-unsafe-negation: error + no-useless-computed-key: error + no-useless-constructor: error + no-useless-return: error + no-use-before-define: + - error + - functions: false + no-var: error + object-shorthand: + - error + - methods + - avoidExplicitReturnArrows: true + padding-line-between-statements: + - error + - blankLine: always + prev: + - block + - block-like + next: "*" + - blankLine: always + prev: "*" + next: + - block + - block-like + prefer-const: error + prefer-rest-params: error + prefer-spread: error + spaced-comment: [error, always] + strict: off + yoda: error + vue/component-tags-order: + - error + - order: + - template + - style + - script + vue/no-mutating-props: off + vue/no-v-html: off + vue/require-default-prop: off + vue/v-slot-style: [error, longform] + vue/multi-word-component-names: off + +plugins: + - vue + +extends: + - eslint:recommended + - plugin:vue/recommended + - prettier diff --git a/client/components/App.vue b/client/components/App.vue index e02a10e74a..b19df8a625 100644 --- a/client/components/App.vue +++ b/client/components/App.vue @@ -16,21 +16,30 @@ - diff --git a/client/js/constants.js b/client/js/constants.js index 59ad6e87c8..19ed21889e 100644 --- a/client/js/constants.js +++ b/client/js/constants.js @@ -28,8 +28,7 @@ const timeFormats = { msg12hWithSeconds: "hh:mm:ss A", }; -// This file is required by server, can't use es6 export -module.exports = { +export default { colorCodeMap, commands: [], condensedTypes, diff --git a/client/js/helpers/contextMenu.js b/client/js/helpers/contextMenu.js index e31968716f..c9062bd480 100644 --- a/client/js/helpers/contextMenu.js +++ b/client/js/helpers/contextMenu.js @@ -176,7 +176,7 @@ export function generateChannelContextMenu($root, channel, network) { query: "conversation", }; - // We don't allow the muting of Chan.Type.SPECIAL channels + // We don't allow the muting of ChanType.SPECIAL channels const mutableChanTypes = Object.keys(humanFriendlyChanTypeMap); if (mutableChanTypes.includes(channel.type)) { diff --git a/client/js/localStorage.js b/client/js/localStorage.ts similarity index 92% rename from client/js/localStorage.js rename to client/js/localStorage.ts index 709ada0ea5..0e94b981a9 100644 --- a/client/js/localStorage.js +++ b/client/js/localStorage.ts @@ -11,14 +11,14 @@ // https://www.chromium.org/for-testers/bug-reporting-guidelines/uncaught-securityerror-failed-to-read-the-localstorage-property-from-window-access-is-denied-for-this-document export default { - set(key, value) { + set(key: string, value: string) { try { window.localStorage.setItem(key, value); } catch (e) { // } }, - get(key) { + get(key: string) { try { return window.localStorage.getItem(key); } catch (e) { @@ -26,7 +26,7 @@ export default { return null; } }, - remove(key) { + remove(key: string) { try { window.localStorage.removeItem(key); } catch (e) { diff --git a/client/js/vue.js b/client/js/vue.ts similarity index 75% rename from client/js/vue.js rename to client/js/vue.ts index 18f913dab4..e508c5cd07 100644 --- a/client/js/vue.js +++ b/client/js/vue.ts @@ -15,9 +15,11 @@ import "./socket-events"; import "./webpush"; import "./keybinds"; +import type {Channel} from "@/backend/models/channel"; + const favicon = document.getElementById("favicon"); -const faviconNormal = favicon.getAttribute("href"); -const faviconAlerted = favicon.dataset.other; +const faviconNormal = favicon?.getAttribute("href") || ""; +const faviconAlerted = favicon?.dataset.other || ""; new Vue({ el: "#viewport", @@ -26,10 +28,10 @@ new Vue({ socket.open(); }, methods: { - switchToChannel(channel) { + switchToChannel(channel: Channel) { navigate("RoutedChat", {id: channel.id}); }, - closeChannel(channel) { + closeChannel(channel: Channel) { if (channel.type === "lobby") { eventbus.emit( "confirm-dialog", @@ -38,7 +40,7 @@ new Vue({ text: `Are you sure you want to quit and remove ${channel.name}? This cannot be undone.`, button: "Remove network", }, - (result) => { + (result: boolean) => { if (!result) { return; } @@ -75,7 +77,7 @@ store.watch( (state) => state.sidebarOpen, (sidebarOpen) => { if (window.innerWidth > constants.mobileViewportPixels) { - storage.set("thelounge.state.sidebar", sidebarOpen); + storage.set("thelounge.state.sidebar", sidebarOpen.toString()); eventbus.emit("resize"); } } @@ -84,7 +86,7 @@ store.watch( store.watch( (state) => state.userlistOpen, (userlistOpen) => { - storage.set("thelounge.state.userlist", userlistOpen); + storage.set("thelounge.state.userlist", userlistOpen.toString()); eventbus.emit("resize"); } ); @@ -100,13 +102,15 @@ store.watch( store.watch( (_, getters) => getters.highlightCount, (highlightCount) => { - favicon.setAttribute("href", highlightCount > 0 ? faviconAlerted : faviconNormal); + favicon?.setAttribute("href", highlightCount > 0 ? faviconAlerted : faviconNormal); - if (navigator.setAppBadge) { + // TODO: investigate types + const nav = navigate as any; + if (nav.setAppBadge) { if (highlightCount > 0) { - navigator.setAppBadge(highlightCount); + nav.setAppBadge(highlightCount); } else { - navigator.clearAppBadge(); + nav.clearAppBadge(); } } } diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000000..e06c49c4d3 --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + // https://v2.vuejs.org/v2/guide/typescript.html?redirect=true#Recommended-Configuration + // this aligns with Vue's browser support + "target": "es5", + // this enables stricter inference for data properties on `this` + "strict": true, + // if using webpack 2+ or rollup, to leverage tree shaking: + "module": "es2015", + "moduleResolution": "node", + "lib": ["es2019", "dom"], + "sourceMap": true, + "rootDir": "./", + "outDir": "./dist", + "allowJs": true, + "noImplicitAny": true, + "allowSyntheticDefaultImports": true, + "baseUrl": "./", + "paths": { + "@/js/*": ["./js/*"], + "@/css/*": ["./css/*"], + "@/img/*": ["./img/*"], + "@/components/*": ["./components/*"], + "@/backend/*": ["../src/types/*"] + }, + "jsx": "preserve" + } +} diff --git a/client/types.d.ts b/client/types.d.ts new file mode 100644 index 0000000000..8597c380b7 --- /dev/null +++ b/client/types.d.ts @@ -0,0 +1,4 @@ +declare module "*.vue" { + import Vue from "vue"; + export default Vue; +} diff --git a/package.json b/package.json index ce58b95004..94e81262a0 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "scripts": { "build": "webpack", "coverage": "run-s test:* && nyc --nycrc-path=test/.nycrc-report.json report", - "dev": "node index start --dev", + "dev": "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", @@ -41,6 +41,7 @@ }, "dependencies": { "@fastify/busboy": "1.0.0", + "@types/ldapjs": "2.2.2", "bcryptjs": "2.4.3", "chalk": "4.1.2", "cheerio": "1.0.0-rc.10", @@ -74,13 +75,23 @@ "devDependencies": { "@babel/core": "7.17.10", "@babel/preset-env": "7.17.10", + "@babel/preset-typescript": "7.16.7", "@fortawesome/fontawesome-free": "5.15.4", "@textcomplete/core": "0.1.11", "@textcomplete/textarea": "0.1.10", + "@types/express": "4.17.13", + "@types/lodash": "4.14.182", + "@types/mousetrap": "1.6.9", + "@types/sqlite3": "3.1.8", + "@types/ua-parser-js": "0.7.36", + "@types/uuid": "8.3.4", + "@types/ws": "8.5.3", + "@vue/runtime-dom": "3.2.33", "@vue/server-test-utils": "1.3.0", "@vue/test-utils": "1.3.0", "babel-loader": "8.2.5", "babel-plugin-istanbul": "6.1.1", + "babel-preset-typescript-vue": "1.1.1", "chai": "4.3.6", "copy-webpack-plugin": "10.2.4", "css-loader": "6.5.1", @@ -109,6 +120,10 @@ "socket.io-client": "4.4.1", "stylelint": "14.3.0", "stylelint-config-standard": "24.0.0", + "ts-loader": "9.3.0", + "ts-node": "10.7.0", + "tsconfig-paths-webpack-plugin": "3.5.2", + "typescript": "4.6.4", "undate": "0.3.0", "vue": "2.6.14", "vue-loader": "15.9.8", diff --git a/scripts/changelog.js b/src/changelog.js similarity index 100% rename from scripts/changelog.js rename to src/changelog.js diff --git a/src/client.js b/src/client.js deleted file mode 100644 index b19f9db604..0000000000 --- a/src/client.js +++ /dev/null @@ -1,771 +0,0 @@ -"use strict"; - -const _ = require("lodash"); -const log = require("./log"); -const colors = require("chalk"); -const Chan = require("./models/chan"); -const crypto = require("crypto"); -const Msg = require("./models/msg"); -const Network = require("./models/network"); -const Config = require("./config"); -const UAParser = require("ua-parser-js"); -const {v4: uuidv4} = require("uuid"); -const escapeRegExp = require("lodash/escapeRegExp"); -const constants = require("../client/js/constants.js"); -const inputs = require("./plugins/inputs"); -const PublicClient = require("./plugins/packages/publicClient"); - -const MessageStorage = require("./plugins/messageStorage/sqlite"); -const TextFileMessageStorage = require("./plugins/messageStorage/text"); - -module.exports = Client; - -const events = [ - "away", - "cap", - "connection", - "unhandled", - "ctcp", - "chghost", - "error", - "help", - "info", - "invite", - "join", - "kick", - "list", - "mode", - "modelist", - "motd", - "message", - "names", - "nick", - "part", - "quit", - "sasl", - "topic", - "welcome", - "whois", -]; - -function Client(manager, name, config = {}) { - _.merge(this, { - awayMessage: "", - lastActiveChannel: -1, - attachedClients: {}, - config: config, - id: uuidv4(), - idChan: 1, - idMsg: 1, - name: name, - networks: [], - mentions: [], - manager: manager, - messageStorage: [], - highlightRegex: null, - highlightExceptionRegex: null, - messageProvider: undefined, - }); - - const client = this; - - client.config.log = Boolean(client.config.log); - client.config.password = String(client.config.password); - - if (!Config.values.public && client.config.log) { - if (Config.values.messageStorage.includes("sqlite")) { - client.messageProvider = new MessageStorage(client); - client.messageStorage.push(client.messageProvider); - } - - if (Config.values.messageStorage.includes("text")) { - client.messageStorage.push(new TextFileMessageStorage(client)); - } - - for (const messageStorage of client.messageStorage) { - messageStorage.enable(); - } - } - - if (!_.isPlainObject(client.config.sessions)) { - client.config.sessions = {}; - } - - if (!_.isPlainObject(client.config.clientSettings)) { - client.config.clientSettings = {}; - } - - if (!_.isPlainObject(client.config.browser)) { - client.config.browser = {}; - } - - // TODO: Backwards compatibility with older versions, remove in a future release? - if (client.config.awayMessage) { - client.config.clientSettings.awayMessage = client.config.awayMessage; - delete client.config.awayMessage; - } - - if (client.config.clientSettings.awayMessage) { - client.awayMessage = client.config.clientSettings.awayMessage; - } - - client.config.clientSettings.searchEnabled = client.messageProvider !== undefined; - - client.compileCustomHighlights(); - - _.forOwn(client.config.sessions, (session) => { - if (session.pushSubscription) { - this.registerPushSubscription(session, session.pushSubscription, true); - } - }); - - (client.config.networks || []).forEach((network) => client.connect(network, true)); - - // Networks are stored directly in the client object - // We don't need to keep it in the config object - delete client.config.networks; - - if (client.name) { - log.info(`User ${colors.bold(client.name)} loaded`); - - // Networks are created instantly, but to reduce server load on startup - // We randomize the IRC connections and channel log loading - let delay = manager.clients.length * 500; - client.networks.forEach((network) => { - setTimeout(() => { - network.channels.forEach((channel) => channel.loadMessages(client, network)); - - if (!network.userDisconnected && network.irc) { - network.irc.connect(); - } - }, delay); - - delay += 1000 + Math.floor(Math.random() * 1000); - }); - - client.fileHash = manager.getDataToSave(client).newHash; - } -} - -Client.prototype.createChannel = function (attr) { - const chan = new Chan(attr); - chan.id = this.idChan++; - - return chan; -}; - -Client.prototype.emit = function (event, data) { - if (this.manager !== null) { - this.manager.sockets.in(this.id).emit(event, data); - } -}; - -Client.prototype.find = function (channelId) { - let network = null; - let chan = null; - - for (const i in this.networks) { - const n = this.networks[i]; - chan = _.find(n.channels, {id: channelId}); - - if (chan) { - network = n; - break; - } - } - - if (network && chan) { - return {network, chan}; - } - - return false; -}; - -Client.prototype.connect = function (args, isStartup = false) { - const client = this; - let channels = []; - - // Get channel id for lobby before creating other channels for nicer ids - const lobbyChannelId = client.idChan++; - - if (args.channels) { - let badName = false; - - args.channels.forEach((chan) => { - if (!chan.name) { - badName = true; - return; - } - - channels.push( - client.createChannel({ - name: chan.name, - key: chan.key || "", - type: chan.type, - muted: chan.muted, - }) - ); - }); - - if (badName && client.name) { - log.warn( - "User '" + - client.name + - "' on network '" + - args.name + - "' has an invalid channel which has been ignored" - ); - } - // `join` is kept for backwards compatibility when updating from versions <2.0 - // also used by the "connect" window - } else if (args.join) { - channels = args.join - .replace(/,/g, " ") - .split(/\s+/g) - .map((chan) => { - if (!chan.match(/^[#&!+]/)) { - chan = `#${chan}`; - } - - return client.createChannel({ - name: chan, - }); - }); - } - - const network = new Network({ - uuid: args.uuid, - name: String( - args.name || (Config.values.lockNetwork ? Config.values.defaults.name : "") || "" - ), - host: String(args.host || ""), - port: parseInt(args.port, 10), - tls: !!args.tls, - userDisconnected: !!args.userDisconnected, - rejectUnauthorized: !!args.rejectUnauthorized, - password: String(args.password || ""), - nick: String(args.nick || ""), - username: String(args.username || ""), - realname: String(args.realname || ""), - leaveMessage: String(args.leaveMessage || ""), - sasl: String(args.sasl || ""), - saslAccount: String(args.saslAccount || ""), - saslPassword: String(args.saslPassword || ""), - commands: args.commands || [], - channels: channels, - ignoreList: args.ignoreList ? args.ignoreList : [], - - proxyEnabled: !!args.proxyEnabled, - proxyHost: String(args.proxyHost || ""), - proxyPort: parseInt(args.proxyPort, 10), - proxyUsername: String(args.proxyUsername || ""), - proxyPassword: String(args.proxyPassword || ""), - }); - - // Set network lobby channel id - network.channels[0].id = lobbyChannelId; - - client.networks.push(network); - client.emit("network", { - networks: [network.getFilteredClone(this.lastActiveChannel, -1)], - }); - - if (!network.validate(client)) { - return; - } - - network.createIrcFramework(client); - - events.forEach((plugin) => { - require(`./plugins/irc-events/${plugin}`).apply(client, [network.irc, network]); - }); - - if (network.userDisconnected) { - network.channels[0].pushMessage( - client, - new Msg({ - text: "You have manually disconnected from this network before, use the /connect command to connect again.", - }), - true - ); - } else if (!isStartup) { - network.irc.connect(); - } - - if (!isStartup) { - client.save(); - channels.forEach((channel) => channel.loadMessages(client, network)); - } -}; - -Client.prototype.generateToken = function (callback) { - crypto.randomBytes(64, (err, buf) => { - if (err) { - throw err; - } - - callback(buf.toString("hex")); - }); -}; - -Client.prototype.calculateTokenHash = function (token) { - return crypto.createHash("sha512").update(token).digest("hex"); -}; - -Client.prototype.updateSession = function (token, ip, request) { - const client = this; - const agent = UAParser(request.headers["user-agent"] || ""); - let friendlyAgent = ""; - - if (agent.browser.name) { - friendlyAgent = `${agent.browser.name} ${agent.browser.major}`; - } else { - friendlyAgent = "Unknown browser"; - } - - if (agent.os.name) { - friendlyAgent += ` on ${agent.os.name}`; - - if (agent.os.version) { - friendlyAgent += ` ${agent.os.version}`; - } - } - - client.config.sessions[token] = _.assign(client.config.sessions[token], { - lastUse: Date.now(), - ip: ip, - agent: friendlyAgent, - }); - - client.save(); -}; - -Client.prototype.setPassword = function (hash, callback) { - const client = this; - - const oldHash = client.config.password; - client.config.password = hash; - client.manager.saveUser(client, function (err) { - if (err) { - // If user file fails to write, reset it back - client.config.password = oldHash; - return callback(false); - } - - return callback(true); - }); -}; - -Client.prototype.input = function (data) { - const client = this; - data.text.split("\n").forEach((line) => { - data.text = line; - client.inputLine(data); - }); -}; - -Client.prototype.inputLine = function (data) { - const client = this; - const target = client.find(data.target); - - if (!target) { - return; - } - - // Sending a message to a channel is higher priority than merely opening one - // so that reloading the page will open this channel - this.lastActiveChannel = target.chan.id; - - let text = data.text; - - // This is either a normal message or a command escaped with a leading '/' - if (text.charAt(0) !== "/" || text.charAt(1) === "/") { - if (target.chan.type === Chan.Type.LOBBY) { - target.chan.pushMessage( - this, - new Msg({ - type: Msg.Type.ERROR, - text: "Messages can not be sent to lobbies.", - }) - ); - return; - } - - text = "say " + text.replace(/^\//, ""); - } else { - text = text.substr(1); - } - - const args = text.split(" "); - const cmd = args.shift().toLowerCase(); - - const irc = target.network.irc; - let connected = irc && irc.connection && irc.connection.connected; - - if (inputs.userInputs.has(cmd)) { - const plugin = inputs.userInputs.get(cmd); - - if (typeof plugin.input === "function" && (connected || plugin.allowDisconnected)) { - connected = true; - plugin.input.apply(client, [target.network, target.chan, cmd, args]); - } - } else if (inputs.pluginCommands.has(cmd)) { - const plugin = inputs.pluginCommands.get(cmd); - - if (typeof plugin.input === "function" && (connected || plugin.allowDisconnected)) { - connected = true; - plugin.input( - new PublicClient(client, plugin.packageInfo), - {network: target.network, chan: target.chan}, - cmd, - args - ); - } - } else if (connected) { - irc.raw(text); - } - - if (!connected) { - target.chan.pushMessage( - this, - new Msg({ - type: Msg.Type.ERROR, - text: "You are not connected to the IRC network, unable to send your command.", - }) - ); - } -}; - -Client.prototype.compileCustomHighlights = function () { - this.highlightRegex = compileHighlightRegex(this.config.clientSettings.highlights); - this.highlightExceptionRegex = compileHighlightRegex( - this.config.clientSettings.highlightExceptions - ); -}; - -function compileHighlightRegex(customHighlightString) { - if (typeof customHighlightString !== "string") { - return null; - } - - // Ensure we don't have empty strings in the list of highlights - const highlightsTokens = customHighlightString - .split(",") - .map((highlight) => escapeRegExp(highlight.trim())) - .filter((highlight) => highlight.length > 0); - - if (highlightsTokens.length === 0) { - return null; - } - - return new RegExp( - `(?:^|[ .,+!?|/:<>(){}'"@&~-])(?:${highlightsTokens.join("|")})(?:$|[ .,+!?|/:<>(){}'"-])`, - "i" - ); -} - -Client.prototype.more = function (data) { - const client = this; - const target = client.find(data.target); - - if (!target) { - return null; - } - - const chan = target.chan; - let messages = []; - let index = 0; - - // If client requests -1, send last 100 messages - if (data.lastId < 0) { - index = chan.messages.length; - } else { - index = chan.messages.findIndex((val) => val.id === data.lastId); - } - - // If requested id is not found, an empty array will be sent - if (index > 0) { - let startIndex = index; - - if (data.condensed) { - // Limit to 1000 messages (that's 10x normal limit) - const indexToStop = Math.max(0, index - 1000); - let realMessagesLeft = 100; - - for (let i = index - 1; i >= indexToStop; i--) { - startIndex--; - - // Do not count condensed messages towards the 100 messages - if (constants.condensedTypes.has(chan.messages[i].type)) { - continue; - } - - // Count up actual 100 visible messages - if (--realMessagesLeft === 0) { - break; - } - } - } else { - startIndex = Math.max(0, index - 100); - } - - messages = chan.messages.slice(startIndex, index); - } - - return { - chan: chan.id, - messages: messages, - totalMessages: chan.messages.length, - }; -}; - -Client.prototype.clearHistory = function (data) { - const client = this; - const target = client.find(data.target); - - if (!target) { - return; - } - - target.chan.messages = []; - target.chan.unread = 0; - target.chan.highlight = 0; - target.chan.firstUnread = 0; - - client.emit("history:clear", { - target: target.chan.id, - }); - - if (!target.chan.isLoggable()) { - return; - } - - for (const messageStorage of this.messageStorage) { - messageStorage.deleteChannel(target.network, target.chan); - } -}; - -Client.prototype.search = function (query) { - if (this.messageProvider === undefined) { - return Promise.resolve([]); - } - - return this.messageProvider.search(query); -}; - -Client.prototype.open = function (socketId, target) { - // Due to how socket.io works internally, normal events may arrive later than - // the disconnect event, and because we can't control this timing precisely, - // process this event normally even if there is no attached client anymore. - const attachedClient = this.attachedClients[socketId] || {}; - - // Opening a window like settings - if (target === null) { - attachedClient.openChannel = -1; - return; - } - - target = this.find(target); - - if (!target) { - return; - } - - target.chan.unread = 0; - target.chan.highlight = 0; - - if (target.chan.messages.length > 0) { - target.chan.firstUnread = target.chan.messages[target.chan.messages.length - 1].id; - } - - attachedClient.openChannel = target.chan.id; - this.lastActiveChannel = target.chan.id; - - this.emit("open", target.chan.id); -}; - -Client.prototype.sort = function (data) { - const order = data.order; - - if (!_.isArray(order)) { - return; - } - - switch (data.type) { - case "networks": - this.networks.sort((a, b) => order.indexOf(a.uuid) - order.indexOf(b.uuid)); - - // Sync order to connected clients - this.emit("sync_sort", { - order: this.networks.map((obj) => obj.uuid), - type: data.type, - }); - - break; - - case "channels": { - const network = _.find(this.networks, {uuid: data.target}); - - if (!network) { - return; - } - - network.channels.sort((a, b) => { - // Always sort lobby to the top regardless of what the client has sent - // Because there's a lot of code that presumes channels[0] is the lobby - if (a.type === Chan.Type.LOBBY) { - return -1; - } else if (b.type === Chan.Type.LOBBY) { - return 1; - } - - return order.indexOf(a.id) - order.indexOf(b.id); - }); - - // Sync order to connected clients - this.emit("sync_sort", { - order: network.channels.map((obj) => obj.id), - type: data.type, - target: network.uuid, - }); - - break; - } - } - - this.save(); -}; - -Client.prototype.names = function (data) { - const client = this; - const target = client.find(data.target); - - if (!target) { - return; - } - - client.emit("names", { - id: target.chan.id, - users: target.chan.getSortedUsers(target.network.irc), - }); -}; - -Client.prototype.part = function (network, chan) { - const client = this; - network.channels = _.without(network.channels, chan); - client.mentions = client.mentions.filter((msg) => !(msg.chanId === chan.id)); - chan.destroy(); - client.save(); - client.emit("part", { - chan: chan.id, - }); -}; - -Client.prototype.quit = function (signOut) { - const sockets = this.manager.sockets.sockets; - const room = sockets.adapter.rooms.get(this.id); - - if (room) { - for (const user of room) { - const socket = sockets.sockets.get(user); - - if (socket) { - if (signOut) { - socket.emit("sign-out"); - } - - socket.disconnect(); - } - } - } - - this.networks.forEach((network) => { - network.quit(); - network.destroy(); - }); - - for (const messageStorage of this.messageStorage) { - messageStorage.close(); - } -}; - -Client.prototype.clientAttach = function (socketId, token) { - const client = this; - - if (client.awayMessage && _.size(client.attachedClients) === 0) { - client.networks.forEach(function (network) { - // Only remove away on client attachment if - // there is no away message on this network - if (network.irc && !network.awayMessage) { - network.irc.raw("AWAY"); - } - }); - } - - const openChannel = client.lastActiveChannel; - client.attachedClients[socketId] = {token, openChannel}; -}; - -Client.prototype.clientDetach = function (socketId) { - const client = this; - - delete this.attachedClients[socketId]; - - if (client.awayMessage && _.size(client.attachedClients) === 0) { - client.networks.forEach(function (network) { - // Only set away on client deattachment if - // there is no away message on this network - if (network.irc && !network.awayMessage) { - network.irc.raw("AWAY", client.awayMessage); - } - }); - } -}; - -Client.prototype.registerPushSubscription = function (session, subscription, noSave) { - if ( - !_.isPlainObject(subscription) || - !_.isPlainObject(subscription.keys) || - typeof subscription.endpoint !== "string" || - !/^https?:\/\//.test(subscription.endpoint) || - typeof subscription.keys.p256dh !== "string" || - typeof subscription.keys.auth !== "string" - ) { - session.pushSubscription = null; - return; - } - - const data = { - endpoint: subscription.endpoint, - keys: { - p256dh: subscription.keys.p256dh, - auth: subscription.keys.auth, - }, - }; - - session.pushSubscription = data; - - if (!noSave) { - this.save(); - } - - return data; -}; - -Client.prototype.unregisterPushSubscription = function (token) { - this.config.sessions[token].pushSubscription = null; - this.save(); -}; - -Client.prototype.save = _.debounce( - function SaveClient() { - if (Config.values.public) { - return; - } - - const client = this; - client.manager.saveUser(client); - }, - 5000, - {maxWait: 20000} -); diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000000..ed5f8b50ef --- /dev/null +++ b/src/client.ts @@ -0,0 +1,787 @@ +"use strict"; + +import _ from "lodash"; +import UAParser from "ua-parser-js"; +import {v4 as uuidv4} from "uuid"; +import escapeRegExp from "lodash/escapeRegExp"; +import crypto from "crypto"; +import colors from "chalk"; + +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 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 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 = [ + "away", + "cap", + "connection", + "unhandled", + "ctcp", + "chghost", + "error", + "help", + "info", + "invite", + "join", + "kick", + "list", + "mode", + "modelist", + "motd", + "message", + "names", + "nick", + "part", + "quit", + "sasl", + "topic", + "welcome", + "whois", +]; +class Client { + awayMessage: string; + lastActiveChannel: number; + attachedClients: { + [socketId: string]: {token: string; openChannel: number}; + }; + config: ClientConfig & { + networks: Network[]; + }; + id: number; + idMsg: number; + idChan: number; + name: string; + networks: Network[]; + mentions: Mention[]; + manager: ClientManager; + messageStorage: MessageStorage[]; + highlightRegex?: RegExp; + highlightExceptionRegex?: RegExp; + messageProvider?: SqliteMessageStorage; + + fileHash: string; + + constructor(manager: ClientManager, name?: string, config = {} as ClientConfig) { + _.merge(this, { + awayMessage: "", + lastActiveChannel: -1, + attachedClients: {}, + config: config, + id: uuidv4(), + idChan: 1, + idMsg: 1, + name: name, + networks: [], + mentions: [], + manager: manager, + messageStorage: [], + highlightRegex: null, + highlightExceptionRegex: null, + messageProvider: undefined, + }); + + const client = this; + + client.config.log = Boolean(client.config.log); + client.config.password = String(client.config.password); + + if (!Config.values.public && client.config.log) { + if (Config.values.messageStorage.includes("sqlite")) { + client.messageProvider = new SqliteMessageStorage(client); + client.messageStorage.push(client.messageProvider); + } + + if (Config.values.messageStorage.includes("text")) { + client.messageStorage.push(new TextFileMessageStorage(client)); + } + + for (const messageStorage of client.messageStorage) { + messageStorage.enable(); + } + } + + if (!_.isPlainObject(client.config.sessions)) { + client.config.sessions = {}; + } + + if (!_.isPlainObject(client.config.clientSettings)) { + client.config.clientSettings = {}; + } + + if (!_.isPlainObject(client.config.browser)) { + client.config.browser = {}; + } + + if (client.config.clientSettings.awayMessage) { + client.awayMessage = client.config.clientSettings.awayMessage; + } + + client.config.clientSettings.searchEnabled = client.messageProvider !== undefined; + + client.compileCustomHighlights(); + + _.forOwn(client.config.sessions, (session) => { + if (session.pushSubscription) { + this.registerPushSubscription(session, session.pushSubscription, true); + } + }); + + (client.config.networks || []).forEach((network) => client.connect(network, true)); + + // Networks are stored directly in the client object + // We don't need to keep it in the config object + delete client.config.networks; + + if (client.name) { + log.info(`User ${colors.bold(client.name)} loaded`); + + // Networks are created instantly, but to reduce server load on startup + // We randomize the IRC connections and channel log loading + let delay = manager.clients.length * 500; + client.networks.forEach((network) => { + setTimeout(() => { + network.channels.forEach((channel) => channel.loadMessages(client, network)); + + if (!network.userDisconnected && network.irc) { + network.irc.connect(); + } + }, delay); + + delay += 1000 + Math.floor(Math.random() * 1000); + }); + + client.fileHash = manager.getDataToSave(client).newHash; + } + } + + createChannel(attr: Partial) { + const chan = new Chan(attr); + chan.id = this.idChan++; + + return chan; + } + + emit(event: string, data: any) { + if (this.manager !== null) { + this.manager.sockets.in(this.id.toString()).emit(event, data); + } + } + + find(channelId: number) { + let network = null; + let chan = null; + + for (const i in this.networks) { + const n = this.networks[i]; + chan = _.find(n.channels, {id: channelId}); + + if (chan) { + network = n; + break; + } + } + + if (network && chan) { + return {network, chan}; + } + + return false; + } + + connect(args: any, isStartup = false) { + const client = this; + let channels = []; + + // Get channel id for lobby before creating other channels for nicer ids + const lobbyChannelId = client.idChan++; + + if (args.channels) { + let badName = false; + + args.channels.forEach((chan: Chan) => { + if (!chan.name) { + badName = true; + return; + } + + channels.push( + client.createChannel({ + name: chan.name, + key: chan.key || "", + type: chan.type, + muted: chan.muted, + }) + ); + }); + + if (badName && client.name) { + log.warn( + "User '" + + client.name + + "' on network '" + + args.name + + "' has an invalid channel which has been ignored" + ); + } + } + + const network = new Network({ + uuid: args.uuid, + name: String( + args.name || (Config.values.lockNetwork ? Config.values.defaults.name : "") || "" + ), + host: String(args.host || ""), + port: parseInt(args.port, 10), + tls: !!args.tls, + userDisconnected: !!args.userDisconnected, + rejectUnauthorized: !!args.rejectUnauthorized, + password: String(args.password || ""), + nick: String(args.nick || ""), + username: String(args.username || ""), + realname: String(args.realname || ""), + leaveMessage: String(args.leaveMessage || ""), + sasl: String(args.sasl || ""), + saslAccount: String(args.saslAccount || ""), + saslPassword: String(args.saslPassword || ""), + commands: args.commands || [], + channels: channels, + ignoreList: args.ignoreList ? args.ignoreList : [], + + proxyEnabled: !!args.proxyEnabled, + proxyHost: String(args.proxyHost || ""), + proxyPort: parseInt(args.proxyPort, 10), + proxyUsername: String(args.proxyUsername || ""), + proxyPassword: String(args.proxyPassword || ""), + }); + + // Set network lobby channel id + network.channels[0].id = lobbyChannelId; + + client.networks.push(network); + client.emit("network", { + networks: [network.getFilteredClone(this.lastActiveChannel, -1)], + }); + + if (!network.validate(client)) { + return; + } + + network.createIrcFramework(client); + + events.forEach((plugin) => { + require(`./plugins/irc-events/${plugin}`).apply(client, [network.irc, network]); + }); + + if (network.userDisconnected) { + network.channels[0].pushMessage( + client, + new Msg({ + text: "You have manually disconnected from this network before, use the /connect command to connect again.", + }), + true + ); + } else if (!isStartup) { + network.irc.connect(); + } + + if (!isStartup) { + client.save(); + channels.forEach((channel) => channel.loadMessages(client, network)); + } + } + + generateToken(callback: (token: string) => void) { + crypto.randomBytes(64, (err, buf) => { + if (err) { + throw err; + } + + callback(buf.toString("hex")); + }); + } + + calculateTokenHash(token: string) { + return crypto.createHash("sha512").update(token).digest("hex"); + } + + updateSession(token: string, ip: string, request: any) { + const client = this; + const agent = UAParser(request.headers["user-agent"] || ""); + let friendlyAgent = ""; + + if (agent.browser.name) { + friendlyAgent = `${agent.browser.name} ${agent.browser.major}`; + } else { + friendlyAgent = "Unknown browser"; + } + + if (agent.os.name) { + friendlyAgent += ` on ${agent.os.name}`; + + if (agent.os.version) { + friendlyAgent += ` ${agent.os.version}`; + } + } + + client.config.sessions[token] = _.assign(client.config.sessions[token], { + lastUse: Date.now(), + ip: ip, + agent: friendlyAgent, + }); + + client.save(); + } + + setPassword(hash: string, callback: (success: boolean) => void) { + const client = this; + + const oldHash = client.config.password; + client.config.password = hash; + client.manager.saveUser(client, function (err) { + if (err) { + // If user file fails to write, reset it back + client.config.password = oldHash; + return callback(false); + } + + return callback(true); + }); + } + + input(data) { + const client = this; + data.text.split("\n").forEach((line) => { + data.text = line; + client.inputLine(data); + }); + } + + inputLine(data) { + const client = this; + const target = client.find(data.target); + + if (!target) { + return; + } + + // Sending a message to a channel is higher priority than merely opening one + // so that reloading the page will open this channel + this.lastActiveChannel = target.chan.id; + + let text = data.text; + + // This is either a normal message or a command escaped with a leading '/' + if (text.charAt(0) !== "/" || text.charAt(1) === "/") { + if (target.chan.type === ChanType.LOBBY) { + target.chan.pushMessage( + this, + new Msg({ + type: MessageType.ERROR, + text: "Messages can not be sent to lobbies.", + }) + ); + return; + } + + text = "say " + text.replace(/^\//, ""); + } else { + text = text.substr(1); + } + + const args = text.split(" "); + const cmd = args.shift().toLowerCase(); + + const irc = target.network.irc; + let connected = irc && irc.connection && irc.connection.connected; + + if (inputs.userInputs.has(cmd)) { + const plugin = inputs.userInputs.get(cmd); + + if (typeof plugin.input === "function" && (connected || plugin.allowDisconnected)) { + connected = true; + plugin.input.apply(client, [target.network, target.chan, cmd, args]); + } + } else if (inputs.pluginCommands.has(cmd)) { + const plugin = inputs.pluginCommands.get(cmd); + + if (typeof plugin.input === "function" && (connected || plugin.allowDisconnected)) { + connected = true; + plugin.input( + new PublicClient(client, plugin.packageInfo), + {network: target.network, chan: target.chan}, + cmd, + args + ); + } + } else if (connected) { + irc.raw(text); + } + + if (!connected) { + target.chan.pushMessage( + this, + new Msg({ + type: MessageType.ERROR, + text: "You are not connected to the IRC network, unable to send your command.", + }) + ); + } + } + + compileCustomHighlights() { + function compileHighlightRegex(customHighlightString) { + if (typeof customHighlightString !== "string") { + return null; + } + + // Ensure we don't have empty strings in the list of highlights + const highlightsTokens = customHighlightString + .split(",") + .map((highlight) => escapeRegExp(highlight.trim())) + .filter((highlight) => highlight.length > 0); + + if (highlightsTokens.length === 0) { + return null; + } + + return new RegExp( + `(?:^|[ .,+!?|/:<>(){}'"@&~-])(?:${highlightsTokens.join( + "|" + )})(?:$|[ .,+!?|/:<>(){}'"-])`, + "i" + ); + } + + this.highlightRegex = compileHighlightRegex(this.config.clientSettings.highlights); + this.highlightExceptionRegex = compileHighlightRegex( + this.config.clientSettings.highlightExceptions + ); + } + + more(data) { + const client = this; + const target = client.find(data.target); + + if (!target) { + return null; + } + + const chan = target.chan; + let messages = []; + let index = 0; + + // If client requests -1, send last 100 messages + if (data.lastId < 0) { + index = chan.messages.length; + } else { + index = chan.messages.findIndex((val) => val.id === data.lastId); + } + + // If requested id is not found, an empty array will be sent + if (index > 0) { + let startIndex = index; + + if (data.condensed) { + // Limit to 1000 messages (that's 10x normal limit) + const indexToStop = Math.max(0, index - 1000); + let realMessagesLeft = 100; + + for (let i = index - 1; i >= indexToStop; i--) { + startIndex--; + + // Do not count condensed messages towards the 100 messages + if (constants.condensedTypes.has(chan.messages[i].type)) { + continue; + } + + // Count up actual 100 visible messages + if (--realMessagesLeft === 0) { + break; + } + } + } else { + startIndex = Math.max(0, index - 100); + } + + messages = chan.messages.slice(startIndex, index); + } + + return { + chan: chan.id, + messages: messages, + totalMessages: chan.messages.length, + }; + } + + clearHistory(data) { + const client = this; + const target = client.find(data.target); + + if (!target) { + return; + } + + target.chan.messages = []; + target.chan.unread = 0; + target.chan.highlight = 0; + target.chan.firstUnread = 0; + + client.emit("history:clear", { + target: target.chan.id, + }); + + if (!target.chan.isLoggable()) { + return; + } + + for (const messageStorage of this.messageStorage) { + messageStorage.deleteChannel(target.network, target.chan); + } + } + + search(query: string) { + if (this.messageProvider === undefined) { + return Promise.resolve([]); + } + + return this.messageProvider.search(query); + } + + open(socketId: string, target: number) { + // Due to how socket.io works internally, normal events may arrive later than + // the disconnect event, and because we can't control this timing precisely, + // process this event normally even if there is no attached client anymore. + const attachedClient = this.attachedClients[socketId] || ({} as any); + + // Opening a window like settings + if (target === null) { + attachedClient.openChannel = -1; + return; + } + + const targetNetChan = this.find(target); + + if (!targetNetChan) { + return; + } + + targetNetChan.chan.unread = 0; + targetNetChan.chan.highlight = 0; + + if (targetNetChan.chan.messages.length > 0) { + targetNetChan.chan.firstUnread = + targetNetChan.chan.messages[targetNetChan.chan.messages.length - 1].id; + } + + attachedClient.openChannel = targetNetChan.chan.id; + this.lastActiveChannel = targetNetChan.chan.id; + + this.emit("open", targetNetChan.chan.id); + } + + sort(data) { + const order = data.order; + + if (!_.isArray(order)) { + return; + } + + switch (data.type) { + case "networks": + this.networks.sort((a, b) => order.indexOf(a.uuid) - order.indexOf(b.uuid)); + + // Sync order to connected clients + this.emit("sync_sort", { + order: this.networks.map((obj) => obj.uuid), + type: data.type, + }); + + break; + + case "channels": { + const network = _.find(this.networks, {uuid: data.target}); + + if (!network) { + return; + } + + network.channels.sort((a, b) => { + // Always sort lobby to the top regardless of what the client has sent + // Because there's a lot of code that presumes channels[0] is the lobby + if (a.type === ChanType.LOBBY) { + return -1; + } else if (b.type === ChanType.LOBBY) { + return 1; + } + + return order.indexOf(a.id) - order.indexOf(b.id); + }); + + // Sync order to connected clients + this.emit("sync_sort", { + order: network.channels.map((obj) => obj.id), + type: data.type, + target: network.uuid, + }); + + break; + } + } + + this.save(); + } + + names(data) { + const client = this; + const target = client.find(data.target); + + if (!target) { + return; + } + + client.emit("names", { + id: target.chan.id, + users: target.chan.getSortedUsers(target.network.irc), + }); + } + + part(network: Network, chan: Chan) { + const client = this; + network.channels = _.without(network.channels, chan); + client.mentions = client.mentions.filter((msg) => !(msg.chanId === chan.id)); + chan.destroy(); + client.save(); + client.emit("part", { + chan: chan.id, + }); + } + + quit(signOut: boolean) { + const sockets = this.manager.sockets; + const room = sockets.adapter.rooms.get(this.id.toString()); + + if (room) { + for (const user of room) { + const socket = sockets.sockets.get(user); + + if (socket) { + if (signOut) { + socket.emit("sign-out"); + } + + socket.disconnect(); + } + } + } + + this.networks.forEach((network) => { + network.quit(); + network.destroy(); + }); + + for (const messageStorage of this.messageStorage) { + messageStorage.close(); + } + } + + clientAttach(socketId: string, token: string) { + const client = this; + + if (client.awayMessage && _.size(client.attachedClients) === 0) { + client.networks.forEach(function (network) { + // Only remove away on client attachment if + // there is no away message on this network + if (network.irc && !network.awayMessage) { + network.irc.raw("AWAY"); + } + }); + } + + const openChannel = client.lastActiveChannel; + client.attachedClients[socketId] = {token, openChannel}; + } + + clientDetach(socketId: string) { + const client = this; + + delete this.attachedClients[socketId]; + + if (client.awayMessage && _.size(client.attachedClients) === 0) { + client.networks.forEach(function (network) { + // Only set away on client deattachment if + // there is no away message on this network + if (network.irc && !network.awayMessage) { + network.irc.raw("AWAY", client.awayMessage); + } + }); + } + } + + // TODO: type session to this.attachedClients + registerPushSubscription( + session: any, + subscription: PushSubscription, + noSave: boolean = false + ) { + if ( + !_.isPlainObject(subscription) || + !_.isPlainObject(subscription.keys) || + typeof subscription.endpoint !== "string" || + !/^https?:\/\//.test(subscription.endpoint) || + typeof subscription.keys.p256dh !== "string" || + typeof subscription.keys.auth !== "string" + ) { + session.pushSubscription = null; + return; + } + + const data = { + endpoint: subscription.endpoint, + keys: { + p256dh: subscription.keys.p256dh, + auth: subscription.keys.auth, + }, + }; + + session.pushSubscription = data; + + if (!noSave) { + this.save(); + } + + return data; + } + + unregisterPushSubscription(token: string) { + this.config.sessions[token].pushSubscription = null; + this.save(); + } + + save = _.debounce( + function SaveClient() { + if (Config.values.public) { + return; + } + + const client = this; + client.manager.saveUser(client); + }, + 5000, + {maxWait: 20000} + ); +} + +export default Client; diff --git a/src/clientManager.js b/src/clientManager.js deleted file mode 100644 index dc31317cd5..0000000000 --- a/src/clientManager.js +++ /dev/null @@ -1,284 +0,0 @@ -"use strict"; - -const _ = require("lodash"); -const log = require("./log"); -const colors = require("chalk"); -const crypto = require("crypto"); -const fs = require("fs"); -const path = require("path"); -const Auth = require("./plugins/auth"); -const Client = require("./client"); -const Config = require("./config"); -const WebPush = require("./plugins/webpush"); - -module.exports = ClientManager; - -function ClientManager() { - this.clients = []; -} - -ClientManager.prototype.init = function (identHandler, sockets) { - this.sockets = sockets; - this.identHandler = identHandler; - this.webPush = new WebPush(); - - if (!Config.values.public) { - this.loadUsers(); - - // LDAP does not have user commands, and users are dynamically - // created upon logon, so we don't need to watch for new files - if (!Config.values.ldap.enable) { - this.autoloadUsers(); - } - } -}; - -ClientManager.prototype.findClient = function (name) { - name = name.toLowerCase(); - return this.clients.find((u) => u.name.toLowerCase() === name); -}; - -ClientManager.prototype.loadUsers = function () { - let users = this.getUsers(); - - if (users.length === 0) { - log.info( - `There are currently no users. Create one with ${colors.bold("thelounge add ")}.` - ); - - return; - } - - const alreadySeenUsers = new Set(); - users = users.filter((user) => { - user = user.toLowerCase(); - - if (alreadySeenUsers.has(user)) { - log.error( - `There is more than one user named "${colors.bold( - user - )}". Usernames are now case insensitive, duplicate users will not load.` - ); - - return false; - } - - alreadySeenUsers.add(user); - - return true; - }); - - // This callback is used by Auth plugins to load users they deem acceptable - const callbackLoadUser = (user) => { - this.loadUser(user); - }; - - if (!Auth.loadUsers(users, callbackLoadUser)) { - // Fallback to loading all users - users.forEach((name) => this.loadUser(name)); - } -}; - -ClientManager.prototype.autoloadUsers = function () { - fs.watch( - Config.getUsersPath(), - _.debounce( - () => { - const loaded = this.clients.map((c) => c.name); - const updatedUsers = this.getUsers(); - - if (updatedUsers.length === 0) { - log.info( - `There are currently no users. Create one with ${colors.bold( - "thelounge add " - )}.` - ); - } - - // Reload all users. Existing users will only have their passwords reloaded. - updatedUsers.forEach((name) => this.loadUser(name)); - - // Existing users removed since last time users were loaded - _.difference(loaded, updatedUsers).forEach((name) => { - const client = _.find(this.clients, {name}); - - if (client) { - client.quit(true); - this.clients = _.without(this.clients, client); - log.info(`User ${colors.bold(name)} disconnected and removed.`); - } - }); - }, - 1000, - {maxWait: 10000} - ) - ); -}; - -ClientManager.prototype.loadUser = function (name) { - const userConfig = readUserConfig(name); - - if (!userConfig) { - return; - } - - let client = this.findClient(name); - - if (client) { - if (userConfig.password !== client.config.password) { - /** - * If we happen to reload an existing client, make super duper sure we - * have their latest password. We're not replacing the entire config - * object, because that could have undesired consequences. - * - * @see https://github.com/thelounge/thelounge/issues/598 - */ - client.config.password = userConfig.password; - log.info(`Password for user ${colors.bold(name)} was reset.`); - } - } else { - client = new Client(this, name, userConfig); - this.clients.push(client); - } - - return client; -}; - -ClientManager.prototype.getUsers = function () { - if (!fs.existsSync(Config.getUsersPath())) { - return []; - } - - return fs - .readdirSync(Config.getUsersPath()) - .filter((file) => file.endsWith(".json")) - .map((file) => file.slice(0, -5)); -}; - -ClientManager.prototype.addUser = function (name, password, enableLog) { - if (path.basename(name) !== name) { - throw new Error(`${name} is an invalid username.`); - } - - const userPath = Config.getUserConfigPath(name); - - if (fs.existsSync(userPath)) { - log.error(`User ${colors.green(name)} already exists.`); - return false; - } - - const user = { - password: password || "", - log: enableLog, - }; - - try { - fs.writeFileSync(userPath, JSON.stringify(user, null, "\t"), { - mode: 0o600, - }); - } catch (e) { - log.error(`Failed to create user ${colors.green(name)} (${e})`); - throw e; - } - - try { - const userFolderStat = fs.statSync(Config.getUsersPath()); - const userFileStat = fs.statSync(userPath); - - if ( - userFolderStat && - userFileStat && - (userFolderStat.uid !== userFileStat.uid || userFolderStat.gid !== userFileStat.gid) - ) { - log.warn( - `User ${colors.green( - name - )} has been created, but with a different uid (or gid) than expected.` - ); - log.warn( - "The file owner has been changed to the expected user. " + - "To prevent any issues, please run thelounge commands " + - "as the correct user that owns the config folder." - ); - log.warn( - "See https://thelounge.chat/docs/usage#using-the-correct-system-user for more information." - ); - fs.chownSync(userPath, userFolderStat.uid, userFolderStat.gid); - } - } catch (e) { - // 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 - } - - return true; -}; - -ClientManager.prototype.getDataToSave = function (client) { - const json = Object.assign({}, client.config, { - networks: client.networks.map((n) => n.export()), - }); - const newUser = JSON.stringify(json, null, "\t"); - const newHash = crypto.createHash("sha256").update(newUser).digest("hex"); - - return {newUser, newHash}; -}; - -ClientManager.prototype.saveUser = function (client, callback) { - const {newUser, newHash} = this.getDataToSave(client); - - // Do not write to disk if the exported data hasn't actually changed - if (client.fileHash === newHash) { - return; - } - - const pathReal = Config.getUserConfigPath(client.name); - const pathTemp = pathReal + ".tmp"; - - try { - // Write to a temp file first, in case the write fails - // we do not lose the original file (for example when disk is full) - fs.writeFileSync(pathTemp, newUser, { - mode: 0o600, - }); - fs.renameSync(pathTemp, pathReal); - - return callback ? callback() : true; - } catch (e) { - log.error(`Failed to update user ${colors.green(client.name)} (${e})`); - - if (callback) { - callback(e); - } - } -}; - -ClientManager.prototype.removeUser = function (name) { - const userPath = Config.getUserConfigPath(name); - - if (!fs.existsSync(userPath)) { - log.error(`Tried to remove non-existing user ${colors.green(name)}.`); - return false; - } - - fs.unlinkSync(userPath); - - return true; -}; - -function readUserConfig(name) { - const userPath = Config.getUserConfigPath(name); - - if (!fs.existsSync(userPath)) { - log.error(`Tried to read non-existing user ${colors.green(name)}`); - return false; - } - - try { - const data = fs.readFileSync(userPath, "utf-8"); - return JSON.parse(data); - } catch (e) { - log.error(`Failed to read user ${colors.bold(name)}: ${e}`); - } - - return false; -} diff --git a/src/clientManager.ts b/src/clientManager.ts new file mode 100644 index 0000000000..b6eae9a1e8 --- /dev/null +++ b/src/clientManager.ts @@ -0,0 +1,295 @@ +"use strict"; + +import _ from "lodash"; +import colors from "chalk"; +import crypto from "crypto"; +import fs from "fs"; +import path from "path"; + +import Auth from "./plugins/auth"; +import Client from "./client"; +import Config from "./config"; +import WebPush from "./plugins/webpush"; +import log from "./log"; +import {Namespace, Server, Socket} from "socket.io"; + +class ClientManager { + clients: Client[]; + sockets: Namespace; + identHandler: any; + webPush: WebPush; + + constructor() { + this.clients = []; + } + + init(identHandler, sockets: Namespace) { + this.sockets = sockets; + this.identHandler = identHandler; + this.webPush = new WebPush(); + + if (!Config.values.public) { + this.loadUsers(); + + // LDAP does not have user commands, and users are dynamically + // created upon logon, so we don't need to watch for new files + if (!Config.values.ldap.enable) { + this.autoloadUsers(); + } + } + } + + findClient(name: string) { + name = name.toLowerCase(); + return this.clients.find((u) => u.name.toLowerCase() === name); + } + + loadUsers() { + let users = this.getUsers(); + + if (users.length === 0) { + log.info( + `There are currently no users. Create one with ${colors.bold( + "thelounge add " + )}.` + ); + + return; + } + + const alreadySeenUsers = new Set(); + users = users.filter((user) => { + user = user.toLowerCase(); + + if (alreadySeenUsers.has(user)) { + log.error( + `There is more than one user named "${colors.bold( + user + )}". Usernames are now case insensitive, duplicate users will not load.` + ); + + return false; + } + + alreadySeenUsers.add(user); + + return true; + }); + + // This callback is used by Auth plugins to load users they deem acceptable + const callbackLoadUser = (user) => { + this.loadUser(user); + }; + + if (!Auth.loadUsers(users, callbackLoadUser)) { + // Fallback to loading all users + users.forEach((name) => this.loadUser(name)); + } + } + + autoloadUsers() { + fs.watch( + Config.getUsersPath(), + _.debounce( + () => { + const loaded = this.clients.map((c) => c.name); + const updatedUsers = this.getUsers(); + + if (updatedUsers.length === 0) { + log.info( + `There are currently no users. Create one with ${colors.bold( + "thelounge add " + )}.` + ); + } + + // Reload all users. Existing users will only have their passwords reloaded. + updatedUsers.forEach((name) => this.loadUser(name)); + + // Existing users removed since last time users were loaded + _.difference(loaded, updatedUsers).forEach((name) => { + const client = _.find(this.clients, {name}); + + if (client) { + client.quit(true); + this.clients = _.without(this.clients, client); + log.info(`User ${colors.bold(name)} disconnected and removed.`); + } + }); + }, + 1000, + {maxWait: 10000} + ) + ); + } + + loadUser(name: string) { + const userConfig = this.readUserConfig(name); + + if (!userConfig) { + return; + } + + let client = this.findClient(name); + + if (client) { + if (userConfig.password !== client.config.password) { + /** + * If we happen to reload an existing client, make super duper sure we + * have their latest password. We're not replacing the entire config + * object, because that could have undesired consequences. + * + * @see https://github.com/thelounge/thelounge/issues/598 + */ + client.config.password = userConfig.password; + log.info(`Password for user ${colors.bold(name)} was reset.`); + } + } else { + client = new Client(this, name, userConfig); + this.clients.push(client); + } + + return client; + } + + getUsers = function () { + if (!fs.existsSync(Config.getUsersPath())) { + return []; + } + + return fs + .readdirSync(Config.getUsersPath()) + .filter((file) => file.endsWith(".json")) + .map((file) => file.slice(0, -5)); + }; + + addUser(name: string, password: string, enableLog: boolean) { + if (path.basename(name) !== name) { + throw new Error(`${name} is an invalid username.`); + } + + const userPath = Config.getUserConfigPath(name); + + if (fs.existsSync(userPath)) { + log.error(`User ${colors.green(name)} already exists.`); + return false; + } + + const user = { + password: password || "", + log: enableLog, + }; + + try { + fs.writeFileSync(userPath, JSON.stringify(user, null, "\t"), { + mode: 0o600, + }); + } catch (e) { + log.error(`Failed to create user ${colors.green(name)} (${e})`); + throw e; + } + + try { + const userFolderStat = fs.statSync(Config.getUsersPath()); + const userFileStat = fs.statSync(userPath); + + if ( + userFolderStat && + userFileStat && + (userFolderStat.uid !== userFileStat.uid || userFolderStat.gid !== userFileStat.gid) + ) { + log.warn( + `User ${colors.green( + name + )} has been created, but with a different uid (or gid) than expected.` + ); + log.warn( + "The file owner has been changed to the expected user. " + + "To prevent any issues, please run thelounge commands " + + "as the correct user that owns the config folder." + ); + log.warn( + "See https://thelounge.chat/docs/usage#using-the-correct-system-user for more information." + ); + fs.chownSync(userPath, userFolderStat.uid, userFolderStat.gid); + } + } catch (e) { + // 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 + } + + return true; + } + + getDataToSave(client: Client) { + const json = Object.assign({}, client.config, { + networks: client.networks.map((n) => n.export()), + }); + const newUser = JSON.stringify(json, null, "\t"); + const newHash = crypto.createHash("sha256").update(newUser).digest("hex"); + + return {newUser, newHash}; + } + + saveUser(client: Client, callback: (err?: Error) => void) { + const {newUser, newHash} = this.getDataToSave(client); + + // Do not write to disk if the exported data hasn't actually changed + if (client.fileHash === newHash) { + return; + } + + const pathReal = Config.getUserConfigPath(client.name); + const pathTemp = pathReal + ".tmp"; + + try { + // Write to a temp file first, in case the write fails + // we do not lose the original file (for example when disk is full) + fs.writeFileSync(pathTemp, newUser, { + mode: 0o600, + }); + fs.renameSync(pathTemp, pathReal); + + return callback ? callback() : true; + } catch (e) { + log.error(`Failed to update user ${colors.green(client.name)} (${e})`); + + if (callback) { + callback(e); + } + } + } + + removeUser(name) { + const userPath = Config.getUserConfigPath(name); + + if (!fs.existsSync(userPath)) { + log.error(`Tried to remove non-existing user ${colors.green(name)}.`); + return false; + } + + fs.unlinkSync(userPath); + + return true; + } + + private readUserConfig(name: string) { + const userPath = Config.getUserConfigPath(name); + + if (!fs.existsSync(userPath)) { + log.error(`Tried to read non-existing user ${colors.green(name)}`); + return false; + } + + try { + const data = fs.readFileSync(userPath, "utf-8"); + return JSON.parse(data); + } catch (e) { + log.error(`Failed to read user ${colors.bold(name)}: ${e}`); + } + + return false; + } +} + +export default ClientManager; diff --git a/src/command-line/index.js b/src/command-line/index.ts similarity index 91% rename from src/command-line/index.js rename to src/command-line/index.ts index cfea187e65..355eca96f4 100644 --- a/src/command-line/index.js +++ b/src/command-line/index.ts @@ -1,14 +1,15 @@ "use strict"; -const log = require("../log"); -const fs = require("fs"); -const path = require("path"); -const colors = require("chalk"); -const program = require("commander"); -const Helper = require("../helper"); -const Config = require("../config"); -const Utils = require("./utils"); - +import log from "../log"; +import fs from "fs"; +import path from "path"; +import colors from "chalk"; +import {Command} from "commander"; +import Helper from "../helper"; +import Config from "../config"; +import Utils from "./utils"; + +const program = new Command(); program .version(Helper.getVersion(), "-v, --version") .option( diff --git a/src/command-line/install.js b/src/command-line/install.ts similarity index 91% rename from src/command-line/install.js rename to src/command-line/install.ts index e85e8fede3..95afaef949 100644 --- a/src/command-line/install.js +++ b/src/command-line/install.ts @@ -1,13 +1,14 @@ "use strict"; -const log = require("../log"); -const colors = require("chalk"); -const semver = require("semver"); -const program = require("commander"); -const Helper = require("../helper"); -const Config = require("../config"); -const Utils = require("./utils"); +import log from "../log"; +import colors from "chalk"; +import semver from "semver"; +import Helper from "../helper"; +import Config from "../config"; +import Utils from "./utils"; +import {Command} from "commander"; +const program = new Command(); program .command("install ") .description("Install a theme or a package") diff --git a/src/command-line/outdated.js b/src/command-line/outdated.ts similarity index 73% rename from src/command-line/outdated.js rename to src/command-line/outdated.ts index 72c561d456..7c2f963bf0 100644 --- a/src/command-line/outdated.js +++ b/src/command-line/outdated.ts @@ -1,10 +1,11 @@ "use strict"; -const program = require("commander"); -const Utils = require("./utils"); -const packageManager = require("../plugins/packages"); -const log = require("../log"); +import {Command} from "commander"; +import Utils from "./utils"; +import packageManager from "../plugins/packages"; +import log from "../log"; +const program = new Command(); program .command("outdated") .description("Check for any outdated packages") diff --git a/src/command-line/start.js b/src/command-line/start.ts similarity index 76% rename from src/command-line/start.js rename to src/command-line/start.ts index 2ccb3a3a5b..1ff8ac0a2a 100644 --- a/src/command-line/start.js +++ b/src/command-line/start.ts @@ -1,12 +1,14 @@ "use strict"; -const log = require("../log"); -const colors = require("chalk"); -const fs = require("fs"); -const path = require("path"); -const program = require("commander"); -const Config = require("../config"); -const Utils = require("./utils"); +import log from "../log"; +import colors from "chalk"; +import fs from "fs"; +import path from "path"; +import {Command} from "commander"; +import Config from "../config"; +import Utils from "./utils"; + +const program = new Command(); program .command("start") diff --git a/src/command-line/uninstall.js b/src/command-line/uninstall.ts similarity index 84% rename from src/command-line/uninstall.js rename to src/command-line/uninstall.ts index a7565bc96f..563c16fc74 100644 --- a/src/command-line/uninstall.js +++ b/src/command-line/uninstall.ts @@ -1,11 +1,12 @@ "use strict"; -const log = require("../log"); -const colors = require("chalk"); -const program = require("commander"); -const Config = require("../config"); -const Utils = require("./utils"); +import log from "../log"; +import colors from "chalk"; +import {Command} from "commander"; +import Config from "../config"; +import Utils from "./utils"; +const program = new Command(); program .command("uninstall ") .description("Uninstall a theme or a package") diff --git a/src/command-line/upgrade.js b/src/command-line/upgrade.ts similarity index 95% rename from src/command-line/upgrade.js rename to src/command-line/upgrade.ts index 57cc65b462..e856b5bf92 100644 --- a/src/command-line/upgrade.js +++ b/src/command-line/upgrade.ts @@ -2,10 +2,11 @@ const log = require("../log"); const colors = require("chalk"); -const program = require("commander"); +const {Command} = require("commander"); const Config = require("../config"); const Utils = require("./utils"); +const program = new Command(); program .command("upgrade [packages...]") .description("Upgrade installed themes and packages to their latest versions") diff --git a/src/command-line/utils.js b/src/command-line/utils.ts similarity index 92% rename from src/command-line/utils.js rename to src/command-line/utils.ts index c6cec2e3ce..1751d7c56f 100644 --- a/src/command-line/utils.js +++ b/src/command-line/utils.ts @@ -1,14 +1,15 @@ "use strict"; -const _ = require("lodash"); -const log = require("../log"); -const colors = require("chalk"); -const fs = require("fs"); -const Helper = require("../helper"); -const Config = require("../config"); -const path = require("path"); +import _ from "lodash"; +import log from "../log"; +import colors from "chalk"; +import fs from "fs"; +import Helper from "../helper"; +import Config from "../config"; +import path from "path"; +import {spawn} from "child_process"; -let home; +let home: string; class Utils { static extraHelp() { @@ -120,7 +121,7 @@ class Utils { return new Promise((resolve, reject) => { let success = false; - const add = require("child_process").spawn( + const add = spawn( process.execPath, [yarn, command, ...staticParameters, ...parameters], {env: env} @@ -168,10 +169,10 @@ class Utils { return reject(code); } - resolve(); + resolve(true); }); }); } } -module.exports = Utils; +export default Utils; diff --git a/src/config.js b/src/config.ts similarity index 84% rename from src/config.js rename to src/config.ts index e7860f8fc9..6f134c9769 100644 --- a/src/config.js +++ b/src/config.ts @@ -1,16 +1,19 @@ "use strict"; -const path = require("path"); -const fs = require("fs"); -const os = require("os"); -const _ = require("lodash"); -const colors = require("chalk"); -const log = require("./log"); -const Helper = require("./helper"); +import path from "path"; +import fs 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"; class Config { - values = require(path.resolve(path.join(__dirname, "..", "defaults", "config.js"))); - #homePath; + values = require(path.resolve( + path.join(__dirname, "..", "defaults", "config.js") + )) as ConfigType; + #homePath: string; getHomePath() { return this.#homePath; @@ -36,7 +39,7 @@ class Config { return path.join(this.#homePath, "users"); } - getUserConfigPath(name) { + getUserConfigPath(name: string) { return path.join(this.getUsersPath(), `${name}.json`); } @@ -48,7 +51,7 @@ class Config { return path.join(this.#homePath, "packages"); } - getPackageModulePath(packageName) { + getPackageModulePath(packageName: string) { return path.join(this.getPackagesPath(), "node_modules", packageName); } @@ -57,14 +60,16 @@ class Config { return "thelounge"; } - return this.values.defaults.nick.replace(/%/g, () => Math.floor(Math.random() * 10)); + return this.values.defaults.nick.replace(/%/g, () => + Math.floor(Math.random() * 10).toString() + ); } - merge(newConfig) { + merge(newConfig: ConfigType) { this._merge_config_objects(this.values, newConfig); } - _merge_config_objects(oldConfig, newConfig) { + _merge_config_objects(oldConfig: ConfigType, newConfig: ConfigType) { // semi exposed function so that we can test it // it mutates the oldConfig, but returns it as a convenience for testing @@ -93,7 +98,7 @@ class Config { }); } - setHome(newPath) { + setHome(newPath: string) { this.#homePath = Helper.expandHome(newPath); // Reload config from new home location @@ -179,4 +184,4 @@ class Config { } } -module.exports = new Config(); +export default new Config(); diff --git a/src/helper.js b/src/helper.ts similarity index 83% rename from src/helper.js rename to src/helper.ts index 4777a10c46..037e5126cc 100644 --- a/src/helper.js +++ b/src/helper.ts @@ -1,13 +1,14 @@ "use strict"; -const pkg = require("../package.json"); -const _ = require("lodash"); -const path = require("path"); -const os = require("os"); -const fs = require("fs"); -const net = require("net"); -const bcrypt = require("bcryptjs"); -const crypto = require("crypto"); +import pkg from "../package.json"; +import _ from "lodash"; +import path from "path"; +import os from "os"; +import fs from "fs"; +import net from "net"; +import bcrypt from "bcryptjs"; +import crypto from "crypto"; +import User from "./models/user"; const Helper = { expandHome, @@ -27,7 +28,7 @@ const Helper = { }, }; -module.exports = Helper; +export default Helper; function getVersion() { const gitCommit = getGitCommit(); @@ -73,7 +74,7 @@ function getVersionCacheBust() { return hash.substring(0, 10); } -function ip2hex(address) { +function ip2hex(address: string) { // no ipv6 support if (!net.isIPv4(address)) { return "00000000"; @@ -95,7 +96,7 @@ function ip2hex(address) { // Expand ~ into the current user home dir. // This does *not* support `~other_user/tmp` => `/home/other_user/tmp`. -function expandHome(shortenedPath) { +function expandHome(shortenedPath: string) { if (!shortenedPath) { return ""; } @@ -104,19 +105,19 @@ function expandHome(shortenedPath) { return path.resolve(shortenedPath.replace(/^~($|\/|\\)/, home + "$1")); } -function passwordRequiresUpdate(password) { +function passwordRequiresUpdate(password: string) { return bcrypt.getRounds(password) !== 11; } -function passwordHash(password) { +function passwordHash(password: string) { return bcrypt.hashSync(password, bcrypt.genSaltSync(11)); } -function passwordCompare(password, expected) { +function passwordCompare(password: string, expected: string) { return bcrypt.compare(password, expected); } -function parseHostmask(hostmask) { +function parseHostmask(hostmask: string): Hostmask { let nick = ""; let ident = "*"; let hostname = "*"; @@ -152,7 +153,7 @@ function parseHostmask(hostmask) { return result; } -function compareHostmask(a, b) { +function compareHostmask(a: Hostmask, b: Hostmask) { return ( compareWithWildcard(a.nick, b.nick) && compareWithWildcard(a.ident, b.ident) && @@ -160,7 +161,7 @@ function compareHostmask(a, b) { ); } -function compareWithWildcard(a, b) { +function compareWithWildcard(a: string, b: string) { // we allow '*' and '?' wildcards in our comparison. // this is mostly aligned with https://modern.ircdocs.horse/#wildcard-expressions // but we do not support the escaping. The ABNF does not seem to be clear as to diff --git a/src/identification.js b/src/identification.ts similarity index 79% rename from src/identification.js rename to src/identification.ts index 59ebc5903f..63644f464f 100644 --- a/src/identification.js +++ b/src/identification.ts @@ -1,14 +1,18 @@ "use strict"; -const log = require("./log"); -const fs = require("fs"); -const net = require("net"); -const colors = require("chalk"); -const Helper = require("./helper"); -const Config = require("./config"); +import log from "./log"; +import fs from "fs"; +import net from "net"; +import colors from "chalk"; +import Helper from "./helper"; +import Config from "./config"; class Identification { - constructor(startedCallback) { + private connectionId: number; + private connections: Map; + private oidentdFile: string; + + constructor(startedCallback: Function) { this.connectionId = 0; this.connections = new Map(); @@ -39,11 +43,15 @@ class Identification { }, () => { const address = server.address(); - log.info( - `Identd server available on ${colors.green( - address.address + ":" + address.port - )}` - ); + if (typeof address === "string") { + log.info(`Identd server available on ${colors.green(address)}`); + } else if (address.address) { + log.info( + `Identd server available on ${colors.green( + address.address + ":" + address.port + )}` + ); + } startedCallback(this); } @@ -120,4 +128,4 @@ class Identification { } } -module.exports = Identification; +export default Identification; diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000000..9720c98aa1 --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1 @@ +/// diff --git a/src/models/chan.js b/src/models/chan.js deleted file mode 100644 index 451cdc6a9e..0000000000 --- a/src/models/chan.js +++ /dev/null @@ -1,304 +0,0 @@ -"use strict"; - -const _ = require("lodash"); -const log = require("../log"); -const Config = require("../config"); -const User = require("./user"); -const Msg = require("./msg"); -const storage = require("../plugins/storage"); - -module.exports = Chan; - -Chan.Type = { - CHANNEL: "channel", - LOBBY: "lobby", - QUERY: "query", - SPECIAL: "special", -}; - -Chan.SpecialType = { - BANLIST: "list_bans", - INVITELIST: "list_invites", - CHANNELLIST: "list_channels", - IGNORELIST: "list_ignored", -}; - -Chan.State = { - PARTED: 0, - JOINED: 1, -}; - -function Chan(attr) { - _.defaults(this, attr, { - id: 0, - messages: [], - name: "", - key: "", - topic: "", - type: Chan.Type.CHANNEL, - state: Chan.State.PARTED, - firstUnread: 0, - unread: 0, - highlight: 0, - users: new Map(), - muted: false, - }); -} - -Chan.prototype.destroy = function () { - this.dereferencePreviews(this.messages); -}; - -Chan.prototype.pushMessage = function (client, msg, increasesUnread) { - const chan = this.id; - const obj = {chan, msg}; - - msg.id = client.idMsg++; - - // If this channel is open in any of the clients, do not increase unread counter - const isOpen = _.find(client.attachedClients, {openChannel: chan}) !== undefined; - - if (msg.self) { - // reset counters/markers when receiving self-/echo-message - this.unread = 0; - this.firstUnread = msg.id; - this.highlight = 0; - } else if (!isOpen) { - if (!this.firstUnread) { - this.firstUnread = msg.id; - } - - if (increasesUnread || msg.highlight) { - obj.unread = ++this.unread; - } - - if (msg.highlight) { - obj.highlight = ++this.highlight; - } - } - - client.emit("msg", obj); - - // Never store messages in public mode as the session - // is completely destroyed when the page gets closed - if (Config.values.public) { - return; - } - - // showInActive is only processed on "msg", don't need it on page reload - if (msg.showInActive) { - delete msg.showInActive; - } - - this.writeUserLog(client, msg); - - if (Config.values.maxHistory >= 0 && this.messages.length > Config.values.maxHistory) { - const deleted = this.messages.splice(0, this.messages.length - Config.values.maxHistory); - - // If maxHistory is 0, image would be dereferenced before client had a chance to retrieve it, - // so for now, just don't implement dereferencing for this edge case. - if (Config.values.maxHistory > 0) { - this.dereferencePreviews(deleted); - } - } -}; - -Chan.prototype.dereferencePreviews = function (messages) { - if (!Config.values.prefetch || !Config.values.prefetchStorage) { - return; - } - - messages.forEach((message) => { - if (message.previews) { - message.previews.forEach((preview) => { - if (preview.thumb) { - storage.dereference(preview.thumb); - preview.thumb = ""; - } - }); - } - }); -}; - -Chan.prototype.getSortedUsers = function (irc) { - const users = Array.from(this.users.values()); - - if (!irc || !irc.network || !irc.network.options || !irc.network.options.PREFIX) { - return users; - } - - const userModeSortPriority = {}; - irc.network.options.PREFIX.forEach((prefix, index) => { - userModeSortPriority[prefix.symbol] = index; - }); - - userModeSortPriority[""] = 99; // No mode is lowest - - return users.sort(function (a, b) { - if (a.mode === b.mode) { - return a.nick.toLowerCase() < b.nick.toLowerCase() ? -1 : 1; - } - - return userModeSortPriority[a.mode] - userModeSortPriority[b.mode]; - }); -}; - -Chan.prototype.findMessage = function (msgId) { - return this.messages.find((message) => message.id === msgId); -}; - -Chan.prototype.findUser = function (nick) { - return this.users.get(nick.toLowerCase()); -}; - -Chan.prototype.getUser = function (nick) { - return this.findUser(nick) || new User({nick}); -}; - -Chan.prototype.setUser = function (user) { - this.users.set(user.nick.toLowerCase(), user); -}; - -Chan.prototype.removeUser = function (user) { - this.users.delete(user.nick.toLowerCase()); -}; - -/** - * Get a clean clone of this channel that will be sent to the client. - * This function performs manual cloning of channel object for - * better control of performance and memory usage. - * - * @param {(int|bool)} lastActiveChannel - Last known active user channel id (needed to control how many messages are sent) - * If true, channel is assumed active. - * @param {int} lastMessage - Last message id seen by active client to avoid sending duplicates. - */ -Chan.prototype.getFilteredClone = function (lastActiveChannel, lastMessage) { - return Object.keys(this).reduce((newChannel, prop) => { - if (prop === "users") { - // Do not send users, client requests updated user list whenever needed - newChannel[prop] = []; - } else if (prop === "messages") { - // If client is reconnecting, only send new messages that client has not seen yet - if (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); - } else { - // If channel is active, send up to 100 last messages, for all others send just 1 - // Client will automatically load more messages whenever needed based on last seen messages - const messagesToSend = - lastActiveChannel === true || this.id === lastActiveChannel ? 100 : 1; - - newChannel[prop] = this[prop].slice(-messagesToSend); - } - - newChannel.totalMessages = this[prop].length; - } else { - newChannel[prop] = this[prop]; - } - - return newChannel; - }, {}); -}; - -Chan.prototype.writeUserLog = function (client, msg) { - this.messages.push(msg); - - // Are there any logs enabled - if (client.messageStorage.length === 0) { - return; - } - - let targetChannel = this; - - // Is this particular message or channel loggable - if (!msg.isLoggable() || !this.isLoggable()) { - // 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 === Msg.Type.NOTICE && msg.showInActive) { - targetChannel = { - name: msg.from.nick, - }; - } else { - return; - } - } - - // Find the parent network where this channel is in - const target = client.find(this.id); - - if (!target) { - return; - } - - for (const messageStorage of client.messageStorage) { - messageStorage.index(target.network, targetChannel, msg); - } -}; - -Chan.prototype.loadMessages = function (client, network) { - if (!this.isLoggable()) { - return; - } - - if (!network.irc) { - // Network created, but misconfigured - log.warn( - `Failed to load messages for ${client.name}, network ${network.name} is not initialized.` - ); - return; - } - - if (!client.messageProvider) { - if (network.irc.network.cap.isEnabled("znc.in/playback")) { - // if we do have a message provider we might be able to only fetch partial history, - // so delay the cap in this case. - requestZncPlayback(this, network, 0); - } - - return; - } - - client.messageProvider - .getMessages(network, this) - .then((messages) => { - if (messages.length === 0) { - if (network.irc.network.cap.isEnabled("znc.in/playback")) { - requestZncPlayback(this, network, 0); - } - - return; - } - - this.messages.unshift(...messages); - - if (!this.firstUnread) { - this.firstUnread = messages[messages.length - 1].id; - } - - client.emit("more", { - chan: this.id, - messages: messages.slice(-100), - totalMessages: messages.length, - }); - - if (network.irc.network.cap.isEnabled("znc.in/playback")) { - const from = Math.floor(messages[messages.length - 1].time.getTime() / 1000); - - requestZncPlayback(this, network, from); - } - }) - .catch((err) => log.error(`Failed to load messages for ${client.name}: ${err}`)); -}; - -Chan.prototype.isLoggable = function () { - return this.type === Chan.Type.CHANNEL || this.type === Chan.Type.QUERY; -}; - -Chan.prototype.setMuteStatus = function (muted) { - this.muted = !!muted; -}; - -function requestZncPlayback(channel, network, from) { - network.irc.raw("ZNC", "*playback", "PLAY", channel.name, from.toString()); -} diff --git a/src/models/chan.ts b/src/models/chan.ts new file mode 100644 index 0000000000..695211628f --- /dev/null +++ b/src/models/chan.ts @@ -0,0 +1,293 @@ +"use strict"; + +import _ from "lodash"; +import log from "../log"; +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 Network from "./network"; +import {MessageType} from "src/types/models/message"; + +class Chan { + id: number; + messages: Msg[]; + name: string; + key: string; + topic: string; + firstUnread: number; + unread: number; + highlight: number; + users: Map; + muted: boolean; + type: ChanType; + state: ChanState; + + constructor(attr: Partial) { + _.defaults(this, attr, { + id: 0, + messages: [], + name: "", + key: "", + topic: "", + type: ChanType.CHANNEL, + state: ChanState.PARTED, + firstUnread: 0, + unread: 0, + highlight: 0, + users: new Map(), + muted: false, + }); + } + + destroy() { + this.dereferencePreviews(this.messages); + } + + pushMessage(client: Client, msg: Msg, increasesUnread: boolean) { + const chan = this.id; + const obj = {chan, msg} as any; + + msg.id = client.idMsg++; + + // If this channel is open in any of the clients, do not increase unread counter + const isOpen = _.find(client.attachedClients, {openChannel: chan}) !== undefined; + + if (msg.self) { + // reset counters/markers when receiving self-/echo-message + this.unread = 0; + this.firstUnread = msg.id; + this.highlight = 0; + } else if (!isOpen) { + if (!this.firstUnread) { + this.firstUnread = msg.id; + } + + if (increasesUnread || msg.highlight) { + obj.unread = ++this.unread; + } + + if (msg.highlight) { + obj.highlight = ++this.highlight; + } + } + + client.emit("msg", obj); + + // Never store messages in public mode as the session + // is completely destroyed when the page gets closed + if (Config.values.public) { + return; + } + + // showInActive is only processed on "msg", don't need it on page reload + if (msg.showInActive) { + delete msg.showInActive; + } + + this.writeUserLog(client, msg); + + if (Config.values.maxHistory >= 0 && this.messages.length > Config.values.maxHistory) { + const deleted = this.messages.splice( + 0, + this.messages.length - Config.values.maxHistory + ); + + // If maxHistory is 0, image would be dereferenced before client had a chance to retrieve it, + // so for now, just don't implement dereferencing for this edge case. + if (Config.values.maxHistory > 0) { + this.dereferencePreviews(deleted); + } + } + } + dereferencePreviews(messages) { + if (!Config.values.prefetch || !Config.values.prefetchStorage) { + return; + } + + messages.forEach((message) => { + if (message.previews) { + message.previews.forEach((preview) => { + if (preview.thumb) { + storage.dereference(preview.thumb); + preview.thumb = ""; + } + }); + } + }); + } + getSortedUsers(irc) { + const users = Array.from(this.users.values()); + + if (!irc || !irc.network || !irc.network.options || !irc.network.options.PREFIX) { + return users; + } + + const userModeSortPriority = {}; + irc.network.options.PREFIX.forEach((prefix, index) => { + userModeSortPriority[prefix.symbol] = index; + }); + + userModeSortPriority[""] = 99; // No mode is lowest + + return users.sort(function (a, b) { + if (a.mode === b.mode) { + return a.nick.toLowerCase() < b.nick.toLowerCase() ? -1 : 1; + } + + return userModeSortPriority[a.mode] - userModeSortPriority[b.mode]; + }); + } + findMessage(msgId: number) { + return this.messages.find((message) => message.id === msgId); + } + findUser(nick: string) { + return this.users.get(nick.toLowerCase()); + } + getUser(nick: string) { + return this.findUser(nick) || new User({nick}); + } + setUser(user: User) { + this.users.set(user.nick.toLowerCase(), user); + } + removeUser(user: User) { + this.users.delete(user.nick.toLowerCase()); + } + /** + * Get a clean clone of this channel that will be sent to the client. + * This function performs manual cloning of channel object for + * better control of performance and memory usage. + * + * @param {(int|bool)} lastActiveChannel - Last known active user channel id (needed to control how many messages are sent) + * If true, channel is assumed active. + * @param {int} lastMessage - Last message id seen by active client to avoid sending duplicates. + */ + getFilteredClone(lastActiveChannel: number | boolean, lastMessage: number): FilteredChannel { + return Object.keys(this).reduce((newChannel, prop) => { + if (prop === "users") { + // Do not send users, client requests updated user list whenever needed + newChannel[prop] = []; + } else if (prop === "messages") { + // If client is reconnecting, only send new messages that client has not seen yet + if (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); + } else { + // If channel is active, send up to 100 last messages, for all others send just 1 + // Client will automatically load more messages whenever needed based on last seen messages + const messagesToSend = + lastActiveChannel === true || this.id === lastActiveChannel ? 100 : 1; + + newChannel[prop] = this[prop].slice(-messagesToSend); + } + + (newChannel as FilteredChannel).totalMessages = this[prop].length; + } else { + newChannel[prop] = this[prop]; + } + + return newChannel; + }, {}) as FilteredChannel; + } + writeUserLog(client: Client, msg: Msg) { + this.messages.push(msg); + + // Are there any logs enabled + if (client.messageStorage.length === 0) { + return; + } + + let targetChannel: Chan = this; + + // Is this particular message or channel loggable + if (!msg.isLoggable() || !this.isLoggable()) { + // 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; + } else { + return; + } + } + + // Find the parent network where this channel is in + const target = client.find(this.id); + + if (!target) { + return; + } + + for (const messageStorage of client.messageStorage) { + messageStorage.index(target.network, targetChannel, msg); + } + } + loadMessages(client: Client, network: Network) { + if (!this.isLoggable()) { + return; + } + + if (!network.irc) { + // Network created, but misconfigured + log.warn( + `Failed to load messages for ${client.name}, network ${network.name} is not initialized.` + ); + return; + } + + if (!client.messageProvider) { + if (network.irc.network.cap.isEnabled("znc.in/playback")) { + // if we do have a message provider we might be able to only fetch partial history, + // so delay the cap in this case. + requestZncPlayback(this, network, 0); + } + + return; + } + + client.messageProvider + .getMessages(network, this) + .then((messages) => { + if (messages.length === 0) { + if (network.irc.network.cap.isEnabled("znc.in/playback")) { + requestZncPlayback(this, network, 0); + } + + return; + } + + this.messages.unshift(...messages); + + if (!this.firstUnread) { + this.firstUnread = messages[messages.length - 1].id; + } + + client.emit("more", { + chan: this.id, + messages: messages.slice(-100), + totalMessages: messages.length, + }); + + if (network.irc.network.cap.isEnabled("znc.in/playback")) { + const from = Math.floor(messages[messages.length - 1].time.getTime() / 1000); + + requestZncPlayback(this, network, from); + } + }) + .catch((err) => log.error(`Failed to load messages for ${client.name}: ${err}`)); + } + isLoggable() { + return this.type === ChanType.CHANNEL || this.type === ChanType.QUERY; + } + setMuteStatus(muted) { + this.muted = !!muted; + } +} + +function requestZncPlayback(channel, network, from) { + network.irc.raw("ZNC", "*playback", "PLAY", channel.name, from.toString()); +} + +export default Chan; diff --git a/src/models/msg.js b/src/models/msg.js deleted file mode 100644 index 0d61daceb7..0000000000 --- a/src/models/msg.js +++ /dev/null @@ -1,92 +0,0 @@ -"use strict"; - -const _ = require("lodash"); - -class Msg { - constructor(attr) { - // Some properties need to be copied in the Msg object instead of referenced - if (attr) { - ["from", "target"].forEach((prop) => { - if (attr[prop]) { - this[prop] = { - mode: attr[prop].mode, - nick: attr[prop].nick, - }; - } - }); - } - - _.defaults(this, attr, { - from: {}, - id: 0, - previews: [], - text: "", - type: Msg.Type.MESSAGE, - self: false, - }); - - if (this.time > 0) { - this.time = new Date(this.time); - } else { - this.time = new Date(); - } - } - - findPreview(link) { - return this.previews.find((preview) => preview.link === link); - } - - isLoggable() { - if (this.type === Msg.Type.TOPIC) { - // Do not log topic that is sent on channel join - return !!this.from.nick; - } - - switch (this.type) { - case Msg.Type.MONOSPACE_BLOCK: - case Msg.Type.ERROR: - case Msg.Type.TOPIC_SET_BY: - case Msg.Type.MODE_CHANNEL: - case Msg.Type.MODE_USER: - case Msg.Type.RAW: - case Msg.Type.WHOIS: - case Msg.Type.PLUGIN: - return false; - default: - return true; - } - } -} - -Msg.Type = { - 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", -}; - -module.exports = Msg; diff --git a/src/models/msg.ts b/src/models/msg.ts new file mode 100644 index 0000000000..5c22816ff2 --- /dev/null +++ b/src/models/msg.ts @@ -0,0 +1,80 @@ +"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; + // 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; + + constructor(attr: Partial) { + // Some properties need to be copied in the Msg object instead of referenced + if (attr) { + ["from", "target"].forEach((prop) => { + if (attr[prop]) { + this[prop] = { + mode: attr[prop].mode, + nick: attr[prop].nick, + }; + } + }); + } + + _.defaults(this, attr, { + from: {}, + id: 0, + previews: [], + text: "", + type: MessageType.MESSAGE, + self: false, + }); + + if (this.time.getTime() > 0) { + this.time = new Date(this.time); + } else { + this.time = new Date(); + } + } + + findPreview(link: string) { + return this.previews.find((preview) => preview.link === link); + } + + isLoggable() { + if (this.type === MessageType.TOPIC) { + // Do not log topic that is sent on channel join + return !!this.from.nick; + } + + switch (this.type) { + case MessageType.MONOSPACE_BLOCK: + case MessageType.ERROR: + case MessageType.TOPIC_SET_BY: + case MessageType.MODE_CHANNEL: + case MessageType.MODE_USER: + case MessageType.RAW: + case MessageType.WHOIS: + case MessageType.PLUGIN: + return false; + default: + return true; + } + } +} + +export default Msg; diff --git a/src/models/network.js b/src/models/network.js deleted file mode 100644 index 1ceee86701..0000000000 --- a/src/models/network.js +++ /dev/null @@ -1,558 +0,0 @@ -"use strict"; - -const _ = require("lodash"); -const {v4: uuidv4} = require("uuid"); -const IrcFramework = require("irc-framework"); -const Chan = require("./chan"); -const Msg = require("./msg"); -const Prefix = require("./prefix"); -const Helper = require("../helper"); -const Config = require("../config"); -const STSPolicies = require("../plugins/sts"); -const ClientCertificate = require("../plugins/clientCertificate"); - -module.exports = Network; - -/** - * @type {Object} List of keys which should be sent to the client by default. - */ -const fieldsForClient = { - uuid: true, - name: true, - nick: true, - serverOptions: true, -}; - -function Network(attr) { - _.defaults(this, attr, { - name: "", - nick: "", - host: "", - port: 6667, - tls: false, - userDisconnected: false, - rejectUnauthorized: false, - password: "", - awayMessage: "", - commands: [], - username: "", - realname: "", - leaveMessage: "", - sasl: "", - saslAccount: "", - saslPassword: "", - channels: [], - irc: null, - serverOptions: { - CHANTYPES: ["#", "&"], - PREFIX: new Prefix([ - {symbol: "!", mode: "Y"}, - {symbol: "@", mode: "o"}, - {symbol: "%", mode: "h"}, - {symbol: "+", mode: "v"}, - ]), - NETWORK: "", - }, - - proxyHost: "", - proxyPort: 1080, - proxyUsername: "", - proxyPassword: "", - proxyEnabled: false, - - chanCache: [], - ignoreList: [], - keepNick: null, - }); - - if (!this.uuid) { - this.uuid = uuidv4(); - } - - if (!this.name) { - this.name = this.host; - } - - this.channels.unshift( - new Chan({ - name: this.name, - type: Chan.Type.LOBBY, - // The lobby only starts as muted if every channel (unless it's special) is muted. - // This is A) easier to implement and B) stops some confusion on startup. - muted: - this.channels.length >= 1 && - this.channels.every((chan) => chan.muted || chan.type === Chan.Type.SPECIAL), - }) - ); -} - -Network.prototype.validate = function (client) { - // Remove !, :, @ and whitespace characters from nicknames and usernames - const cleanNick = (str) => str.replace(/[\x00\s:!@]/g, "_").substring(0, 100); - - // Remove new lines and limit length - const cleanString = (str) => str.replace(/[\x00\r\n]/g, "").substring(0, 300); - - this.setNick(cleanNick(String(this.nick || Config.getDefaultNick()))); - - if (!this.username) { - // If username is empty, make one from the provided nick - this.username = this.nick.replace(/[^a-zA-Z0-9]/g, ""); - } - - this.username = cleanString(this.username) || "thelounge"; - this.realname = cleanString(this.realname) || "The Lounge User"; - this.leaveMessage = cleanString(this.leaveMessage); - this.password = cleanString(this.password); - this.host = cleanString(this.host).toLowerCase(); - this.name = cleanString(this.name); - this.saslAccount = cleanString(this.saslAccount); - this.saslPassword = cleanString(this.saslPassword); - - this.proxyHost = cleanString(this.proxyHost); - this.proxyPort = this.proxyPort || 1080; - this.proxyUsername = cleanString(this.proxyUsername); - this.proxyPassword = cleanString(this.proxyPassword); - this.proxyEnabled = !!this.proxyEnabled; - - const error = function (network, text) { - network.channels[0].pushMessage( - client, - new Msg({ - type: Msg.Type.ERROR, - text: text, - }), - true - ); - }; - - if (!this.port) { - this.port = this.tls ? 6697 : 6667; - } - - if (!["", "plain", "external"].includes(this.sasl)) { - this.sasl = ""; - } - - if (Config.values.lockNetwork) { - // This check is needed to prevent invalid user configurations - if ( - !Config.values.public && - this.host && - this.host.length > 0 && - this.host !== Config.values.defaults.host - ) { - error(this, `The hostname you specified (${this.host}) is not allowed.`); - return false; - } - - if (Config.values.public) { - this.name = Config.values.defaults.name; - // Sync lobby channel name - this.channels[0].name = Config.values.defaults.name; - } - - this.host = Config.values.defaults.host; - this.port = Config.values.defaults.port; - this.tls = Config.values.defaults.tls; - this.rejectUnauthorized = Config.values.defaults.rejectUnauthorized; - } - - if (this.host.length === 0) { - error(this, "You must specify a hostname to connect."); - return false; - } - - const stsPolicy = STSPolicies.get(this.host); - - if (stsPolicy && !this.tls) { - error( - this, - `${this.host} has an active strict transport security policy, will connect to port ${stsPolicy.port} over a secure connection.` - ); - - this.port = stsPolicy.port; - this.tls = true; - this.rejectUnauthorized = true; - } - - return true; -}; - -Network.prototype.createIrcFramework = function (client) { - this.irc = new IrcFramework.Client({ - version: false, // We handle it ourselves - outgoing_addr: Config.values.bind, - enable_chghost: true, - enable_echomessage: true, - enable_setname: true, - auto_reconnect: true, - - // Exponential backoff maxes out at 300 seconds after 9 reconnects, - // it will keep trying for well over an hour (plus the timeouts) - auto_reconnect_max_retries: 30, - }); - - this.setIrcFrameworkOptions(client); - - this.irc.requestCap([ - "znc.in/self-message", // Legacy echo-message for ZNC - "znc.in/playback", // See http://wiki.znc.in/Playback - ]); -}; - -Network.prototype.setIrcFrameworkOptions = function (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) - : this.username; - this.irc.options.gecos = this.realname; - this.irc.options.tls = this.tls; - this.irc.options.rejectUnauthorized = this.rejectUnauthorized; - this.irc.options.webirc = this.createWebIrc(client); - this.irc.options.client_certificate = null; - - if (this.proxyEnabled) { - this.irc.options.socks = { - host: this.proxyHost, - port: this.proxyPort, - user: this.proxyUsername, - pass: this.proxyPassword, - }; - } else { - delete this.irc.options.socks; - } - - if (!this.sasl) { - delete this.irc.options.sasl_mechanism; - delete this.irc.options.account; - } else if (this.sasl === "external") { - this.irc.options.sasl_mechanism = "EXTERNAL"; - this.irc.options.account = {}; - this.irc.options.client_certificate = ClientCertificate.get(this.uuid); - } else if (this.sasl === "plain") { - delete this.irc.options.sasl_mechanism; - this.irc.options.account = { - account: this.saslAccount, - password: this.saslPassword, - }; - } -}; - -Network.prototype.createWebIrc = function (client) { - if ( - !Config.values.webirc || - !Object.prototype.hasOwnProperty.call(Config.values.webirc, this.host) - ) { - return null; - } - - const webircObject = { - password: Config.values.webirc[this.host], - username: "thelounge", - address: client.config.browser.ip, - hostname: client.config.browser.hostname, - }; - - // https://ircv3.net/specs/extensions/webirc#options - if (client.config.browser.isSecure) { - webircObject.options = { - secure: true, - }; - } - - if (typeof Config.values.webirc[this.host] === "function") { - webircObject.password = null; - return Config.values.webirc[this.host](webircObject, this); - } - - return webircObject; -}; - -Network.prototype.edit = function (client, args) { - const oldNetworkName = this.name; - const oldNick = this.nick; - const oldRealname = this.realname; - - this.keepNick = null; - this.nick = args.nick; - this.host = String(args.host || ""); - this.name = String(args.name || "") || this.host; - this.port = parseInt(args.port, 10); - this.tls = !!args.tls; - this.rejectUnauthorized = !!args.rejectUnauthorized; - this.password = String(args.password || ""); - this.username = String(args.username || ""); - this.realname = String(args.realname || ""); - this.leaveMessage = String(args.leaveMessage || ""); - this.sasl = String(args.sasl || ""); - this.saslAccount = String(args.saslAccount || ""); - this.saslPassword = String(args.saslPassword || ""); - - this.proxyHost = String(args.proxyHost || ""); - this.proxyPort = parseInt(args.proxyPort, 10); - this.proxyUsername = String(args.proxyUsername || ""); - this.proxyPassword = String(args.proxyPassword || ""); - this.proxyEnabled = !!args.proxyEnabled; - - // Split commands into an array - this.commands = String(args.commands || "") - .replace(/\r\n|\r|\n/g, "\n") - .split("\n") - .filter((command) => command.length > 0); - - // Sync lobby channel name - this.channels[0].name = this.name; - - if (this.name !== oldNetworkName) { - // Send updated network name to all connected clients - client.emit("network:name", { - uuid: this.uuid, - name: this.name, - }); - } - - if (!this.validate(client)) { - return; - } - - if (this.irc) { - const connected = this.irc.connection && this.irc.connection.connected; - - if (this.nick !== oldNick) { - if (connected) { - // Send new nick straight away - this.irc.changeNick(this.nick); - } else { - this.irc.user.nick = this.nick; - - // Update UI nick straight away if IRC is not connected - client.emit("nick", { - network: this.uuid, - nick: this.nick, - }); - } - } - - if ( - connected && - this.realname !== oldRealname && - this.irc.network.cap.isEnabled("setname") - ) { - this.irc.raw("SETNAME", this.realname); - } - - this.setIrcFrameworkOptions(client); - - this.irc.user.username = this.irc.options.username; - this.irc.user.gecos = this.irc.options.gecos; - } - - client.save(); -}; - -Network.prototype.destroy = function () { - this.channels.forEach((channel) => channel.destroy()); -}; - -Network.prototype.setNick = function (nick) { - this.nick = nick; - this.highlightRegex = new RegExp( - // Do not match characters and numbers (unless IRC color) - "(?:^|[^a-z0-9]|\x03[0-9]{1,2})" + - // Escape nickname, as it may contain regex stuff - _.escapeRegExp(nick) + - // Do not match characters and numbers - "(?:[^a-z0-9]|$)", - - // Case insensitive search - "i" - ); - - if (this.keepNick === nick) { - this.keepNick = null; - } - - if (this.irc) { - this.irc.options.nick = nick; - } -}; - -/** - * Get a clean clone of this network that will be sent to the client. - * This function performs manual cloning of network object for - * better control of performance and memory usage. - * - * Both of the parameters that are accepted by this function are passed into channels' getFilteredClone call. - * - * @see {@link Chan#getFilteredClone} - */ -Network.prototype.getFilteredClone = function (lastActiveChannel, lastMessage) { - const filteredNetwork = Object.keys(this).reduce((newNetwork, prop) => { - if (prop === "channels") { - // Channels objects perform their own cloning - newNetwork[prop] = this[prop].map((channel) => - channel.getFilteredClone(lastActiveChannel, lastMessage) - ); - } else if (fieldsForClient[prop]) { - // Some properties that are not useful for the client are skipped - newNetwork[prop] = this[prop]; - } - - return newNetwork; - }, {}); - - filteredNetwork.status = this.getNetworkStatus(); - - return filteredNetwork; -}; - -Network.prototype.getNetworkStatus = function () { - const status = { - connected: false, - secure: false, - }; - - if (this.irc && this.irc.connection && this.irc.connection.transport) { - const transport = this.irc.connection.transport; - - if (transport.socket) { - const isLocalhost = transport.socket.remoteAddress === "127.0.0.1"; - const isAuthorized = transport.socket.encrypted && transport.socket.authorized; - - status.connected = transport.isConnected(); - status.secure = isAuthorized || isLocalhost; - } - } - - return status; -}; - -Network.prototype.addChannel = function (newChan) { - let index = this.channels.length; // Default to putting as the last item in the array - - // Don't sort special channels in amongst channels/users. - if (newChan.type === Chan.Type.CHANNEL || newChan.type === Chan.Type.QUERY) { - // We start at 1 so we don't test against the lobby - for (let i = 1; i < this.channels.length; i++) { - const compareChan = this.channels[i]; - - // Negative if the new chan is alphabetically before the next chan in the list, positive if after - if ( - newChan.name.localeCompare(compareChan.name, {sensitivity: "base"}) <= 0 || - (compareChan.type !== Chan.Type.CHANNEL && compareChan.type !== Chan.Type.QUERY) - ) { - index = i; - break; - } - } - } - - this.channels.splice(index, 0, newChan); - return index; -}; - -Network.prototype.quit = function (quitMessage) { - if (!this.irc) { - return; - } - - // https://ircv3.net/specs/extensions/sts#rescheduling-expiry-on-disconnect - STSPolicies.refreshExpiration(this.host); - - this.irc.quit(quitMessage || this.leaveMessage || Config.values.leaveMessage); -}; - -Network.prototype.exportForEdit = function () { - const fieldsToReturn = [ - "uuid", - "name", - "nick", - "password", - "username", - "realname", - "leaveMessage", - "sasl", - "saslAccount", - "saslPassword", - "commands", - - "proxyEnabled", - "proxyHost", - "proxyPort", - "proxyUsername", - "proxyPassword", - ]; - - if (!Config.values.lockNetwork) { - fieldsToReturn.push("host"); - fieldsToReturn.push("port"); - fieldsToReturn.push("tls"); - fieldsToReturn.push("rejectUnauthorized"); - } - - const data = _.pick(this, fieldsToReturn); - - data.hasSTSPolicy = !!STSPolicies.get(this.host); - - return data; -}; - -Network.prototype.export = function () { - const network = _.pick(this, [ - "uuid", - "awayMessage", - "nick", - "name", - "host", - "port", - "tls", - "userDisconnected", - "rejectUnauthorized", - "password", - "username", - "realname", - "leaveMessage", - "sasl", - "saslAccount", - "saslPassword", - "commands", - "ignoreList", - - "proxyHost", - "proxyPort", - "proxyUsername", - "proxyEnabled", - "proxyPassword", - ]); - - network.channels = this.channels - .filter(function (channel) { - return channel.type === Chan.Type.CHANNEL || channel.type === Chan.Type.QUERY; - }) - .map(function (chan) { - const keys = ["name", "muted"]; - - if (chan.type === Chan.Type.CHANNEL) { - keys.push("key"); - } else if (chan.type === Chan.Type.QUERY) { - keys.push("type"); - } - - return _.pick(chan, keys); - }); - - return network; -}; - -Network.prototype.getChannel = function (name) { - name = name.toLowerCase(); - - return _.find(this.channels, function (that, i) { - // Skip network lobby (it's always unshifted into first position) - return i > 0 && that.name.toLowerCase() === name; - }); -}; diff --git a/src/models/network.ts b/src/models/network.ts new file mode 100644 index 0000000000..b0cc07e2f7 --- /dev/null +++ b/src/models/network.ts @@ -0,0 +1,627 @@ +"use strict"; + +import _ from "lodash"; +import {v4 as uuidv4} from "uuid"; +import IrcFramework from "irc-framework"; +import Chan from "./chan"; +import Msg from "./msg"; +import Prefix from "./prefix"; +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 {NetworkStatus} from "src/types/models/network"; +import {MessageType} from "src/types/models/message"; +import {WebIRC} from "src/types/config"; + +/** + * @type {Object} List of keys which should be sent to the client by default. + */ +const fieldsForClient = { + uuid: true, + name: true, + nick: true, + serverOptions: true, +}; + +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; + 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; + } + | {}; + }; + }; + + chanCache: Chan[]; + ignoreList: string[]; + keepNick?: string; + + status: NetworkStatus; + + serverOptions: { + CHANTYPES: string[]; + PREFIX: Prefix; + NETWORK: string; + }; + + // TODO: this is only available on export + hasSTSPolicy: boolean; + + constructor(attr: Partial) { + _.defaults(this, attr, { + name: "", + nick: "", + host: "", + port: 6667, + tls: false, + userDisconnected: false, + rejectUnauthorized: false, + password: "", + awayMessage: "", + commands: [], + username: "", + realname: "", + leaveMessage: "", + sasl: "", + saslAccount: "", + saslPassword: "", + channels: [], + irc: null, + serverOptions: { + CHANTYPES: ["#", "&"], + PREFIX: new Prefix([ + {symbol: "!", mode: "Y"}, + {symbol: "@", mode: "o"}, + {symbol: "%", mode: "h"}, + {symbol: "+", mode: "v"}, + ]), + NETWORK: "", + }, + + proxyHost: "", + proxyPort: 1080, + proxyUsername: "", + proxyPassword: "", + proxyEnabled: false, + + chanCache: [], + ignoreList: [], + keepNick: null, + }); + + if (!this.uuid) { + this.uuid = uuidv4(); + } + + if (!this.name) { + this.name = this.host; + } + + this.channels.unshift( + new Chan({ + name: this.name, + type: ChanType.LOBBY, + // The lobby only starts as muted if every channel (unless it's special) is muted. + // This is A) easier to implement and B) stops some confusion on startup. + muted: + this.channels.length >= 1 && + this.channels.every((chan) => chan.muted || chan.type === ChanType.SPECIAL), + }) + ); + } + + validate(client: Client) { + // Remove !, :, @ and whitespace characters from nicknames and usernames + const cleanNick = (str: string) => str.replace(/[\x00\s:!@]/g, "_").substring(0, 100); + + // Remove new lines and limit length + const cleanString = (str: string) => str.replace(/[\x00\r\n]/g, "").substring(0, 300); + + this.setNick(cleanNick(String(this.nick || Config.getDefaultNick()))); + + if (!this.username) { + // If username is empty, make one from the provided nick + this.username = this.nick.replace(/[^a-zA-Z0-9]/g, ""); + } + + this.username = cleanString(this.username) || "thelounge"; + this.realname = cleanString(this.realname) || "The Lounge User"; + this.leaveMessage = cleanString(this.leaveMessage); + this.password = cleanString(this.password); + this.host = cleanString(this.host).toLowerCase(); + this.name = cleanString(this.name); + this.saslAccount = cleanString(this.saslAccount); + this.saslPassword = cleanString(this.saslPassword); + + this.proxyHost = cleanString(this.proxyHost); + this.proxyPort = this.proxyPort || 1080; + this.proxyUsername = cleanString(this.proxyUsername); + this.proxyPassword = cleanString(this.proxyPassword); + this.proxyEnabled = !!this.proxyEnabled; + + const error = function (network: Network, text: string) { + network.channels[0].pushMessage( + client, + new Msg({ + type: MessageType.ERROR, + text: text, + }), + true + ); + }; + + if (!this.port) { + this.port = this.tls ? 6697 : 6667; + } + + if (!["", "plain", "external"].includes(this.sasl)) { + this.sasl = ""; + } + + if (Config.values.lockNetwork) { + // This check is needed to prevent invalid user configurations + if ( + !Config.values.public && + this.host && + this.host.length > 0 && + this.host !== Config.values.defaults.host + ) { + error(this, `The hostname you specified (${this.host}) is not allowed.`); + return false; + } + + if (Config.values.public) { + this.name = Config.values.defaults.name; + // Sync lobby channel name + this.channels[0].name = Config.values.defaults.name; + } + + this.host = Config.values.defaults.host; + this.port = Config.values.defaults.port; + this.tls = Config.values.defaults.tls; + this.rejectUnauthorized = Config.values.defaults.rejectUnauthorized; + } + + if (this.host.length === 0) { + error(this, "You must specify a hostname to connect."); + return false; + } + + const stsPolicy = STSPolicies.get(this.host); + + if (stsPolicy && !this.tls) { + error( + this, + `${this.host} has an active strict transport security policy, will connect to port ${stsPolicy.port} over a secure connection.` + ); + + this.port = stsPolicy.port; + this.tls = true; + this.rejectUnauthorized = true; + } + + return true; + } + + createIrcFramework(client: Client) { + this.irc = new IrcFramework.Client({ + version: false, // We handle it ourselves + outgoing_addr: Config.values.bind, + enable_chghost: true, + enable_echomessage: true, + enable_setname: true, + auto_reconnect: true, + + // Exponential backoff maxes out at 300 seconds after 9 reconnects, + // it will keep trying for well over an hour (plus the timeouts) + auto_reconnect_max_retries: 30, + }); + + this.setIrcFrameworkOptions(client); + + this.irc.requestCap([ + "znc.in/self-message", // Legacy echo-message for ZNC + "znc.in/playback", // See http://wiki.znc.in/Playback + ]); + } + + setIrcFrameworkOptions(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) + : this.username; + this.irc.options.gecos = this.realname; + this.irc.options.tls = this.tls; + this.irc.options.rejectUnauthorized = this.rejectUnauthorized; + this.irc.options.webirc = this.createWebIrc(client); + this.irc.options.client_certificate = null; + + if (this.proxyEnabled) { + this.irc.options.socks = { + host: this.proxyHost, + port: this.proxyPort, + user: this.proxyUsername, + pass: this.proxyPassword, + }; + } else { + delete this.irc.options.socks; + } + + if (!this.sasl) { + delete this.irc.options.sasl_mechanism; + delete this.irc.options.account; + } else if (this.sasl === "external") { + this.irc.options.sasl_mechanism = "EXTERNAL"; + this.irc.options.account = {}; + this.irc.options.client_certificate = ClientCertificate.get(this.uuid); + } else if (this.sasl === "plain") { + delete this.irc.options.sasl_mechanism; + this.irc.options.account = { + account: this.saslAccount, + password: this.saslPassword, + }; + } + } + + createWebIrc(client: Client) { + if ( + !Config.values.webirc || + !Object.prototype.hasOwnProperty.call(Config.values.webirc, this.host) + ) { + return null; + } + + const webircObject = { + password: Config.values.webirc[this.host], + username: "thelounge", + address: client.config.browser.ip, + hostname: client.config.browser.hostname, + } as any; + + // https://ircv3.net/specs/extensions/webirc#options + if (client.config.browser.isSecure) { + webircObject.options = { + secure: true, + }; + } + + if (typeof Config.values.webirc[this.host] === "function") { + webircObject.password = null; + return Config.values.webirc[this.host](webircObject, this); + } + + return webircObject; + } + + edit(client: Client, args: any) { + const oldNetworkName = this.name; + const oldNick = this.nick; + const oldRealname = this.realname; + + this.keepNick = null; + this.nick = args.nick; + this.host = String(args.host || ""); + this.name = String(args.name || "") || this.host; + this.port = parseInt(args.port, 10); + this.tls = !!args.tls; + this.rejectUnauthorized = !!args.rejectUnauthorized; + this.password = String(args.password || ""); + this.username = String(args.username || ""); + this.realname = String(args.realname || ""); + this.leaveMessage = String(args.leaveMessage || ""); + this.sasl = String(args.sasl || ""); + this.saslAccount = String(args.saslAccount || ""); + this.saslPassword = String(args.saslPassword || ""); + + this.proxyHost = String(args.proxyHost || ""); + this.proxyPort = parseInt(args.proxyPort, 10); + this.proxyUsername = String(args.proxyUsername || ""); + this.proxyPassword = String(args.proxyPassword || ""); + this.proxyEnabled = !!args.proxyEnabled; + + // Split commands into an array + this.commands = String(args.commands || "") + .replace(/\r\n|\r|\n/g, "\n") + .split("\n") + .filter((command) => command.length > 0); + + // Sync lobby channel name + this.channels[0].name = this.name; + + if (this.name !== oldNetworkName) { + // Send updated network name to all connected clients + client.emit("network:name", { + uuid: this.uuid, + name: this.name, + }); + } + + if (!this.validate(client)) { + return; + } + + if (this.irc) { + const connected = this.irc.connection && this.irc.connection.connected; + + if (this.nick !== oldNick) { + if (connected) { + // Send new nick straight away + this.irc.changeNick(this.nick); + } else { + this.irc.user.nick = this.nick; + + // Update UI nick straight away if IRC is not connected + client.emit("nick", { + network: this.uuid, + nick: this.nick, + }); + } + } + + if ( + connected && + this.realname !== oldRealname && + this.irc.network.cap.isEnabled("setname") + ) { + this.irc.raw("SETNAME", this.realname); + } + + this.setIrcFrameworkOptions(client); + + this.irc.user.username = this.irc.options.username; + this.irc.user.gecos = this.irc.options.gecos; + } + + client.save(); + } + + destroy() { + this.channels.forEach((channel) => channel.destroy()); + } + + setNick(nick: string) { + this.nick = nick; + this.highlightRegex = new RegExp( + // Do not match characters and numbers (unless IRC color) + "(?:^|[^a-z0-9]|\x03[0-9]{1,2})" + + // Escape nickname, as it may contain regex stuff + _.escapeRegExp(nick) + + // Do not match characters and numbers + "(?:[^a-z0-9]|$)", + + // Case insensitive search + "i" + ); + + if (this.keepNick === nick) { + this.keepNick = null; + } + + if (this.irc) { + this.irc.options.nick = nick; + } + } + + getFilteredClone(lastActiveChannel: number, lastMessage: number) { + const filteredNetwork = Object.keys(this).reduce((newNetwork, prop) => { + if (prop === "channels") { + // Channels objects perform their own cloning + newNetwork[prop] = this[prop].map((channel) => + channel.getFilteredClone(lastActiveChannel, lastMessage) + ); + } else if (fieldsForClient[prop]) { + // Some properties that are not useful for the client are skipped + newNetwork[prop] = this[prop]; + } + + return newNetwork; + }, {}) as Network; + + filteredNetwork.status = this.getNetworkStatus(); + + return filteredNetwork; + } + + getNetworkStatus() { + const status = { + connected: false, + secure: false, + }; + + if (this.irc && this.irc.connection && this.irc.connection.transport) { + const transport = this.irc.connection.transport; + + if (transport.socket) { + const isLocalhost = transport.socket.remoteAddress === "127.0.0.1"; + const isAuthorized = transport.socket.encrypted && transport.socket.authorized; + + status.connected = transport.isConnected(); + status.secure = isAuthorized || isLocalhost; + } + } + + return status; + } + + addChannel(newChan: Chan) { + let index = this.channels.length; // Default to putting as the last item in the array + + // Don't sort special channels in amongst channels/users. + if (newChan.type === ChanType.CHANNEL || newChan.type === ChanType.QUERY) { + // We start at 1 so we don't test against the lobby + for (let i = 1; i < this.channels.length; i++) { + const compareChan = this.channels[i]; + + // Negative if the new chan is alphabetically before the next chan in the list, positive if after + if ( + newChan.name.localeCompare(compareChan.name, undefined, { + sensitivity: "base", + }) <= 0 || + (compareChan.type !== ChanType.CHANNEL && compareChan.type !== ChanType.QUERY) + ) { + index = i; + break; + } + } + } + + this.channels.splice(index, 0, newChan); + return index; + } + + quit(quitMessage?: string) { + if (!this.irc) { + return; + } + + // https://ircv3.net/specs/extensions/sts#rescheduling-expiry-on-disconnect + STSPolicies.refreshExpiration(this.host); + + this.irc.quit(quitMessage || this.leaveMessage || Config.values.leaveMessage); + } + + exportForEdit() { + const fieldsToReturn = [ + "uuid", + "name", + "nick", + "password", + "username", + "realname", + "leaveMessage", + "sasl", + "saslAccount", + "saslPassword", + "commands", + + "proxyEnabled", + "proxyHost", + "proxyPort", + "proxyUsername", + "proxyPassword", + ]; + + if (!Config.values.lockNetwork) { + fieldsToReturn.push("host"); + fieldsToReturn.push("port"); + fieldsToReturn.push("tls"); + fieldsToReturn.push("rejectUnauthorized"); + } + + const data = _.pick(this, fieldsToReturn) as Network; + + data.hasSTSPolicy = !!STSPolicies.get(this.host); + + return data; + } + + export() { + const network = _.pick(this, [ + "uuid", + "awayMessage", + "nick", + "name", + "host", + "port", + "tls", + "userDisconnected", + "rejectUnauthorized", + "password", + "username", + "realname", + "leaveMessage", + "sasl", + "saslAccount", + "saslPassword", + "commands", + "ignoreList", + + "proxyHost", + "proxyPort", + "proxyUsername", + "proxyEnabled", + "proxyPassword", + ]) as Network; + + network.channels = this.channels + .filter(function (channel) { + return channel.type === ChanType.CHANNEL || channel.type === ChanType.QUERY; + }) + .map(function (chan) { + const keys = ["name", "muted"]; + + if (chan.type === ChanType.CHANNEL) { + keys.push("key"); + } else if (chan.type === ChanType.QUERY) { + keys.push("type"); + } + + return _.pick(chan, keys); + // Override the type because we're omitting ID + }) as Channel[]; + + return network; + } + + getChannel(name: string) { + name = name.toLowerCase(); + + return _.find(this.channels, function (that, i) { + // Skip network lobby (it's always unshifted into first position) + return i > 0 && that.name.toLowerCase() === name; + }); + } +} + +export default Network; diff --git a/src/models/prefix.js b/src/models/prefix.ts similarity index 66% rename from src/models/prefix.js rename to src/models/prefix.ts index 331efdff82..c0e939ffdf 100644 --- a/src/models/prefix.js +++ b/src/models/prefix.ts @@ -1,7 +1,11 @@ "use strict"; class Prefix { - constructor(prefix) { + prefix: PrefixObject[]; + modeToSymbol: {[mode: string]: string}; + symbols: string[]; + + constructor(prefix: PrefixObject[]) { this.prefix = prefix || []; // [{symbol: "@", mode: "o"}, ... ] this.modeToSymbol = {}; this.symbols = []; @@ -20,14 +24,14 @@ class Prefix { }); } - update(prefix) { + update(prefix: PrefixObject[]) { this.prefix = prefix || []; this._update_internals(); } - forEach(f) { + forEach(f: (value: PrefixObject, index: number, array: PrefixObject[]) => void) { return this.prefix.forEach(f); } } -module.exports = Prefix; +export default Prefix; diff --git a/src/models/user.js b/src/models/user.js deleted file mode 100644 index dee1e9d0ca..0000000000 --- a/src/models/user.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; - -const _ = require("lodash"); - -module.exports = User; - -function User(attr, prefix) { - _.defaults(this, attr, { - modes: [], - away: "", - nick: "", - lastMessage: 0, - }); - - Object.defineProperty(this, "mode", { - get() { - return this.modes[0] || ""; - }, - }); - - this.setModes(this.modes, prefix); -} - -User.prototype.setModes = function (modes, prefix) { - // irc-framework sets character mode, but The Lounge works with symbols - this.modes = modes.map((mode) => prefix.modeToSymbol[mode]); -}; - -User.prototype.toJSON = function () { - return { - nick: this.nick, - modes: this.modes, - lastMessage: this.lastMessage, - }; -}; diff --git a/src/models/user.ts b/src/models/user.ts new file mode 100644 index 0000000000..32f1c537f6 --- /dev/null +++ b/src/models/user.ts @@ -0,0 +1,45 @@ +"use strict"; + +import _ from "lodash"; +import Prefix from "./prefix"; + +class User { + modes: string[]; + // Users in the channel have only one mode assigned + mode: string; + away: string; + nick: string; + lastMessage: number; + + constructor(attr: Partial, prefix?: Prefix) { + _.defaults(this, attr, { + modes: [], + away: "", + nick: "", + lastMessage: 0, + }); + + Object.defineProperty(this, "mode", { + get() { + return this.modes[0] || ""; + }, + }); + + this.setModes(this.modes, prefix); + } + + setModes(modes: string[], prefix: Prefix) { + // irc-framework sets character mode, but The Lounge works with symbols + this.modes = modes.map((mode) => prefix.modeToSymbol[mode]); + } + + toJSON() { + return { + nick: this.nick, + modes: this.modes, + lastMessage: this.lastMessage, + }; + } +} + +export default User; diff --git a/src/plugins/auth.js b/src/plugins/auth.ts similarity index 88% rename from src/plugins/auth.js rename to src/plugins/auth.ts index 6e95101064..1d00cad5b7 100644 --- a/src/plugins/auth.js +++ b/src/plugins/auth.ts @@ -1,7 +1,7 @@ "use strict"; -const log = require("../log"); -const colors = require("chalk"); +import colors from "chalk"; +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. @@ -15,8 +15,7 @@ function unimplemented(funcName) { ); } -// Default API implementations -module.exports = { +const toExport = { moduleName: "", // Must override: implements authentication mechanism @@ -27,7 +26,11 @@ module.exports = { // can do so without access to the user's unhashed password. // Returning 'false' triggers fallback to default behaviour of loading all users loadUsers: () => false, -}; + // TODO: fix typing +} as any; + +// Default API implementations +export default toExport; // local auth should always be enabled, but check here to verify let somethingEnabled = false; @@ -38,7 +41,7 @@ for (const plugin of plugins) { somethingEnabled = true; for (const name in plugin) { - module.exports[name] = plugin[name]; + toExport[name] = plugin[name]; } break; diff --git a/src/plugins/auth/ldap.js b/src/plugins/auth/ldap.ts similarity index 86% rename from src/plugins/auth/ldap.js rename to src/plugins/auth/ldap.ts index 952d9d1490..077908dee7 100644 --- a/src/plugins/auth/ldap.js +++ b/src/plugins/auth/ldap.ts @@ -1,11 +1,18 @@ "use strict"; -const log = require("../../log"); -const Config = require("../../config"); -const ldap = require("ldapjs"); -const colors = require("chalk"); - -function ldapAuthCommon(user, bindDN, password, callback) { +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"; + +function ldapAuthCommon( + user: string, + bindDN: string, + password: string, + callback: (success: boolean) => void +) { const config = Config.values; const ldapclient = ldap.createClient({ @@ -30,7 +37,7 @@ function ldapAuthCommon(user, bindDN, password, callback) { }); } -function simpleLdapAuth(user, password, callback) { +function simpleLdapAuth(user: string, password: string, callback: (success: boolean) => void) { if (!user || !password) { return callback(false); } @@ -48,7 +55,7 @@ function simpleLdapAuth(user, password, callback) { /** * LDAP auth using initial DN search (see config comment for ldap.searchDN) */ -function advancedLdapAuth(user, password, callback) { +function advancedLdapAuth(user: string, password: string, callback: (success: boolean) => void) { if (!user || !password) { return callback(false); } @@ -66,7 +73,7 @@ function advancedLdapAuth(user, password, callback) { scope: config.ldap.searchDN.scope, filter: `(&(${config.ldap.primaryKey}=${userDN})${config.ldap.searchDN.filter})`, attributes: ["dn"], - }; + } as SearchOptions; ldapclient.on("error", function (err) { log.error(`Unable to connect to LDAP server: ${err}`); @@ -117,12 +124,18 @@ function advancedLdapAuth(user, password, callback) { }); } -function ldapAuth(manager, client, user, password, callback) { +function ldapAuth( + manager: ClientManager, + client: Client, + user: string, + password: string, + callback: (success: boolean) => void +) { // 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 // auth plugin API - function callbackWrapper(valid) { + function callbackWrapper(valid: boolean) { if (valid && !client) { manager.addUser(user, null, true); } @@ -173,7 +186,7 @@ function advancedLdapLoadUsers(users, callbackLoadUser) { filter: `${config.ldap.searchDN.filter}`, attributes: [config.ldap.primaryKey], paged: true, - }; + } as SearchOptions; ldapclient.search(base, searchOptions, function (err2, res) { if (err2) { @@ -182,6 +195,8 @@ function advancedLdapLoadUsers(users, callbackLoadUser) { } res.on("searchEntry", function (entry) { + //@ts-ignore + //TODO const user = entry.attributes[0]._vals[0].toString(); if (remainingUsers.has(user)) { @@ -226,7 +241,7 @@ function isLdapEnabled() { return !Config.values.public && Config.values.ldap.enable; } -module.exports = { +export default { moduleName: "ldap", auth: ldapAuth, isEnabled: isLdapEnabled, diff --git a/src/plugins/clientCertificate.js b/src/plugins/clientCertificate.ts similarity index 84% rename from src/plugins/clientCertificate.js rename to src/plugins/clientCertificate.ts index eb76d97e3a..1daf17c42d 100644 --- a/src/plugins/clientCertificate.js +++ b/src/plugins/clientCertificate.ts @@ -1,18 +1,18 @@ "use strict"; -const path = require("path"); -const fs = require("fs"); -const crypto = require("crypto"); -const {md, pki} = require("node-forge"); -const log = require("../log"); -const Config = require("../config"); - -module.exports = { +import path from "path"; +import fs from "fs"; +import crypto from "crypto"; +import {md, pki} from "node-forge"; +import log from "../log"; +import Config from "../config"; + +export default { get, remove, }; -function get(uuid) { +function get(uuid: string): ClientCertificate { if (Config.values.public) { return null; } @@ -28,7 +28,7 @@ function get(uuid) { return { private_key: fs.readFileSync(paths.privateKeyPath, "utf-8"), certificate: fs.readFileSync(paths.certificatePath, "utf-8"), - }; + } as ClientCertificate; } catch (e) { log.error("Unable to get certificate", e); } @@ -36,7 +36,7 @@ function get(uuid) { return null; } -function remove(uuid) { +function remove(uuid: string) { if (Config.values.public) { return null; } @@ -56,7 +56,7 @@ function remove(uuid) { } } -function generateAndWrite(folderPath, paths) { +function generateAndWrite(folderPath: string, paths: {privateKeyPath: any; certificatePath: any}) { const certificate = generate(); try { @@ -121,12 +121,12 @@ function generate() { const pem = { private_key: pki.privateKeyToPem(keys.privateKey), certificate: pki.certificateToPem(cert), - }; + } as ClientCertificate; return pem; } -function getPaths(folderPath, uuid) { +function getPaths(folderPath: string, uuid: string) { return { privateKeyPath: path.join(folderPath, `${uuid}.pem`), certificatePath: path.join(folderPath, `${uuid}.crt`), diff --git a/src/plugins/inputs/action.js b/src/plugins/inputs/action.js index 8e03b761ad..22ae3c67ad 100644 --- a/src/plugins/inputs/action.js +++ b/src/plugins/inputs/action.js @@ -6,7 +6,7 @@ const Msg = require("../../models/msg"); exports.commands = ["slap", "me"]; exports.input = function ({irc}, chan, cmd, args) { - if (chan.type !== Chan.Type.CHANNEL && chan.type !== Chan.Type.QUERY) { + if (chan.type !== ChanType.CHANNEL && chan.type !== ChanType.QUERY) { chan.pushMessage( this, new Msg({ diff --git a/src/plugins/inputs/ban.js b/src/plugins/inputs/ban.js index 07f3f64627..082973fa09 100644 --- a/src/plugins/inputs/ban.js +++ b/src/plugins/inputs/ban.js @@ -6,7 +6,7 @@ const Msg = require("../../models/msg"); exports.commands = ["ban", "unban", "banlist", "kickban"]; exports.input = function ({irc}, chan, cmd, args) { - if (chan.type !== Chan.Type.CHANNEL) { + if (chan.type !== ChanType.CHANNEL) { chan.pushMessage( this, new Msg({ diff --git a/src/plugins/inputs/ignore.js b/src/plugins/inputs/ignore.js index b2e2dfd93b..b5326b9737 100644 --- a/src/plugins/inputs/ignore.js +++ b/src/plugins/inputs/ignore.js @@ -119,7 +119,7 @@ exports.input = function (network, chan, cmd, args) { if (typeof newChan === "undefined") { newChan = client.createChannel({ - type: Chan.Type.SPECIAL, + type: ChanType.SPECIAL, special: Chan.SpecialType.IGNORELIST, name: chanName, data: ignored, diff --git a/src/plugins/inputs/invite.js b/src/plugins/inputs/invite.js index 962cfb3385..b8a6842aa9 100644 --- a/src/plugins/inputs/invite.js +++ b/src/plugins/inputs/invite.js @@ -13,7 +13,7 @@ exports.input = function ({irc}, chan, cmd, args) { if (args.length === 2) { irc.raw("INVITE", args[0], args[1]); // Channel provided in the command - } else if (args.length === 1 && chan.type === Chan.Type.CHANNEL) { + } else if (args.length === 1 && chan.type === ChanType.CHANNEL) { irc.raw("INVITE", args[0], chan.name); // Current channel } else { chan.pushMessage( diff --git a/src/plugins/inputs/kick.js b/src/plugins/inputs/kick.js index e8307a749f..201bb8fc26 100644 --- a/src/plugins/inputs/kick.js +++ b/src/plugins/inputs/kick.js @@ -6,7 +6,7 @@ const Msg = require("../../models/msg"); exports.commands = ["kick"]; exports.input = function ({irc}, chan, cmd, args) { - if (chan.type !== Chan.Type.CHANNEL) { + if (chan.type !== ChanType.CHANNEL) { chan.pushMessage( this, new Msg({ diff --git a/src/plugins/inputs/mode.js b/src/plugins/inputs/mode.js index 1503b54bce..baa8c420eb 100644 --- a/src/plugins/inputs/mode.js +++ b/src/plugins/inputs/mode.js @@ -11,7 +11,7 @@ exports.input = function ({irc, nick}, chan, cmd, args) { return; } else if (cmd !== "mode") { - if (chan.type !== Chan.Type.CHANNEL) { + if (chan.type !== ChanType.CHANNEL) { chan.pushMessage( this, new Msg({ @@ -59,7 +59,7 @@ exports.input = function ({irc, nick}, chan, cmd, args) { if (args.length === 0 || args[0][0] === "+" || args[0][0] === "-") { args.unshift( - chan.type === Chan.Type.CHANNEL || chan.type === Chan.Type.QUERY ? chan.name : nick + chan.type === ChanType.CHANNEL || chan.type === ChanType.QUERY ? chan.name : nick ); } diff --git a/src/plugins/inputs/msg.js b/src/plugins/inputs/msg.js index f5f0fde799..b6f437ea7f 100644 --- a/src/plugins/inputs/msg.js +++ b/src/plugins/inputs/msg.js @@ -1,5 +1,6 @@ "use strict"; +const {ChanType} = require("src/types/models/channel"); const Chan = require("../../models/chan"); const Msg = require("../../models/msg"); @@ -63,7 +64,7 @@ exports.input = function (network, chan, cmd, args) { } const newChan = this.createChannel({ - type: Chan.Type.QUERY, + type: ChanType.QUERY, name: targetName, }); diff --git a/src/plugins/inputs/part.js b/src/plugins/inputs/part.js index 00c7889336..e3dda18cdf 100644 --- a/src/plugins/inputs/part.js +++ b/src/plugins/inputs/part.js @@ -20,7 +20,7 @@ exports.input = function (network, chan, cmd, args) { } } - if (target.type === Chan.Type.LOBBY) { + if (target.type === ChanType.LOBBY) { chan.pushMessage( this, new Msg({ @@ -34,7 +34,7 @@ exports.input = function (network, chan, cmd, args) { // If target is not a channel or we are not connected, instantly remove the channel // Otherwise send part to the server and wait for response if ( - target.type !== Chan.Type.CHANNEL || + target.type !== ChanType.CHANNEL || target.state === Chan.State.PARTED || !network.irc || !network.irc.connection || diff --git a/src/plugins/inputs/rejoin.js b/src/plugins/inputs/rejoin.js index 1b2157331b..9541ecba63 100644 --- a/src/plugins/inputs/rejoin.js +++ b/src/plugins/inputs/rejoin.js @@ -6,7 +6,7 @@ const Chan = require("../../models/chan"); exports.commands = ["cycle", "rejoin"]; exports.input = function ({irc}, chan) { - if (chan.type !== Chan.Type.CHANNEL) { + if (chan.type !== ChanType.CHANNEL) { chan.pushMessage( this, new Msg({ diff --git a/src/plugins/inputs/topic.js b/src/plugins/inputs/topic.js index a26b8106be..0bc9a5b474 100644 --- a/src/plugins/inputs/topic.js +++ b/src/plugins/inputs/topic.js @@ -6,7 +6,7 @@ const Msg = require("../../models/msg"); exports.commands = ["topic"]; exports.input = function ({irc}, chan, cmd, args) { - if (chan.type !== Chan.Type.CHANNEL) { + if (chan.type !== ChanType.CHANNEL) { chan.pushMessage( this, new Msg({ diff --git a/src/plugins/irc-events/away.js b/src/plugins/irc-events/away.js index a21a463bc7..35d53b6968 100644 --- a/src/plugins/irc-events/away.js +++ b/src/plugins/irc-events/away.js @@ -28,8 +28,8 @@ module.exports = function (irc, network) { network.channels.forEach((chan) => { let user; - switch (chan.type) { - case Chan.Type.QUERY: { + switch (ChanType) { + case ChanType.QUERY: { if (data.nick.toLowerCase() !== chan.name.toLowerCase()) { return; } @@ -56,7 +56,7 @@ module.exports = function (irc, network) { break; } - case Chan.Type.CHANNEL: { + case ChanType.CHANNEL: { user = chan.findUser(data.nick); if (!user || user.away === away) { diff --git a/src/plugins/irc-events/connection.js b/src/plugins/irc-events/connection.ts similarity index 85% rename from src/plugins/irc-events/connection.js rename to src/plugins/irc-events/connection.ts index 608c606394..e6a0fdfd8b 100644 --- a/src/plugins/irc-events/connection.js +++ b/src/plugins/irc-events/connection.ts @@ -1,13 +1,16 @@ "use strict"; -const _ = require("lodash"); -const log = require("../../log"); -const Msg = require("../../models/msg"); -const Chan = require("../../models/chan"); -const Helper = require("../../helper"); -const Config = require("../../config"); - -module.exports = function (irc, network) { +import _ from "lodash"; +import log from "../../log"; +import Msg from "../../models/msg"; +import Chan from "../../models/chan"; +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) { const client = this; network.channels[0].pushMessage( @@ -52,7 +55,7 @@ module.exports = function (irc, network) { } network.channels.forEach((chan) => { - if (chan.type !== Chan.Type.CHANNEL) { + if (chan.type !== ChanType.CHANNEL) { return; } @@ -109,14 +112,14 @@ module.exports = function (irc, network) { network.channels.forEach((chan) => { chan.users = new Map(); - chan.state = Chan.State.PARTED; + chan.state = ChanState.PARTED; }); if (error) { network.channels[0].pushMessage( client, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `Connection closed unexpectedly: ${error}`, }), true @@ -154,7 +157,7 @@ module.exports = function (irc, network) { client, new Msg({ self: !message.from_server, - type: Msg.Type.RAW, + type: MessageType.RAW, text: message.line, }), true @@ -166,7 +169,7 @@ module.exports = function (irc, network) { network.channels[0].pushMessage( client, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "Socket error: " + err, }), true @@ -212,8 +215,11 @@ module.exports = function (irc, network) { function sendStatus() { const status = network.getNetworkStatus(); - status.network = network.uuid; + const toSend = { + ...status, + network: network.uuid, + }; - client.emit("network:status", status); + client.emit("network:status", toSend); } -}; +} diff --git a/src/plugins/irc-events/list.js b/src/plugins/irc-events/list.js index 2e0abeca9d..76fbdd8eb9 100644 --- a/src/plugins/irc-events/list.js +++ b/src/plugins/irc-events/list.js @@ -35,7 +35,7 @@ module.exports = function (irc, network) { if (typeof chan === "undefined") { chan = client.createChannel({ - type: Chan.Type.SPECIAL, + type: ChanType.SPECIAL, special: Chan.SpecialType.CHANNELLIST, name: "Channel List", data: msg, diff --git a/src/plugins/irc-events/message.js b/src/plugins/irc-events/message.js index cd0a066171..720b3a67af 100644 --- a/src/plugins/irc-events/message.js +++ b/src/plugins/irc-events/message.js @@ -56,7 +56,7 @@ module.exports = function (irc, network) { data.from_server && (!data.target || !network.getChannel(data.target) || - network.getChannel(data.target).type !== Chan.Type.CHANNEL) + network.getChannel(data.target).type !== ChanType.CHANNEL) ) { chan = network.channels[0]; from = chan.getUser(data.nick); @@ -81,7 +81,7 @@ module.exports = function (irc, network) { chan = network.channels[0]; } else { chan = client.createChannel({ - type: Chan.Type.QUERY, + type: ChanType.QUERY, name: target, }); @@ -98,9 +98,9 @@ module.exports = function (irc, network) { from = chan.getUser(data.nick); // Query messages (unless self or muted) always highlight - if (chan.type === Chan.Type.QUERY) { + if (chan.type === ChanType.QUERY) { highlight = !self; - } else if (chan.type === Chan.Type.CHANNEL) { + } else if (chan.type === ChanType.CHANNEL) { from.lastMessage = data.time || Date.now(); } } @@ -166,7 +166,7 @@ module.exports = function (irc, network) { if (msg.type === Msg.Type.ACTION) { // For actions, do not include colon in the message body = `${data.nick} ${body}`; - } else if (chan.type !== Chan.Type.QUERY) { + } else if (chan.type !== ChanType.QUERY) { // In channels, prepend sender nickname to the message body = `${data.nick}: ${body}`; } @@ -174,7 +174,7 @@ module.exports = function (irc, network) { // If a channel is active on any client, highlight won't increment and notification will say (0 mention) if (chan.highlight > 0) { title += ` (${chan.highlight} ${ - chan.type === Chan.Type.QUERY ? "new message" : "mention" + chan.type === ChanType.QUERY ? "new message" : "mention" }${chan.highlight > 1 ? "s" : ""})`; } @@ -198,7 +198,7 @@ module.exports = function (irc, network) { } // Keep track of all mentions in channels for this client - if (msg.highlight && chan.type === Chan.Type.CHANNEL) { + if (msg.highlight && chan.type === ChanType.CHANNEL) { client.mentions.push({ chanId: chan.id, msgId: msg.id, diff --git a/src/plugins/irc-events/modelist.js b/src/plugins/irc-events/modelist.js index 202a254339..a9ee6f7cb7 100644 --- a/src/plugins/irc-events/modelist.js +++ b/src/plugins/irc-events/modelist.js @@ -51,7 +51,7 @@ module.exports = function (irc, network) { if (typeof chan === "undefined") { chan = client.createChannel({ - type: Chan.Type.SPECIAL, + type: ChanType.SPECIAL, special: type, name: chanName, data: data, diff --git a/src/plugins/irc-events/whois.js b/src/plugins/irc-events/whois.js index c0ceef776c..96ac2cd4b0 100644 --- a/src/plugins/irc-events/whois.js +++ b/src/plugins/irc-events/whois.js @@ -23,7 +23,7 @@ module.exports = function (irc, network) { chan = network.channels[0]; } else { chan = client.createChannel({ - type: Chan.Type.QUERY, + type: ChanType.QUERY, name: data.nick, }); diff --git a/src/plugins/messageStorage/sqlite.js b/src/plugins/messageStorage/sqlite.ts similarity index 87% rename from src/plugins/messageStorage/sqlite.js rename to src/plugins/messageStorage/sqlite.ts index 243d1ef861..9e6c6b101a 100644 --- a/src/plugins/messageStorage/sqlite.js +++ b/src/plugins/messageStorage/sqlite.ts @@ -1,10 +1,16 @@ "use strict"; -const log = require("../../log"); -const path = require("path"); -const fs = require("fs"); -const Config = require("../../config"); -const Msg = require("../../models/msg"); +import log from "../../log"; +import path from "path"; +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"; let sqlite3; @@ -28,8 +34,12 @@ const schema = [ "CREATE INDEX IF NOT EXISTS time ON messages (time)", ]; -class MessageStorage { - constructor(client) { +class SqliteMessageStorage implements SqliteMessageStorage { + client: Client; + isEnabled: boolean; + database: Database; + + constructor(client: Client) { this.client = client; this.isEnabled = false; } @@ -98,7 +108,7 @@ class MessageStorage { }); } - close(callback) { + close(callback?: (error?: Error) => void) { if (!this.isEnabled) { return; } @@ -116,7 +126,7 @@ class MessageStorage { }); } - index(network, channel, msg) { + index(network: Network, channel: Chan, msg: Msg) { if (!this.isEnabled) { return; } @@ -144,7 +154,7 @@ class MessageStorage { ); } - deleteChannel(network, channel) { + deleteChannel(network: Network, channel: Channel) { if (!this.isEnabled) { return; } @@ -164,7 +174,7 @@ class MessageStorage { * @param Network network - Network object where the channel is * @param Chan channel - Channel object for which to load messages for */ - getMessages(network, channel) { + getMessages(network: Network, channel: Channel) { if (!this.isEnabled || Config.values.maxHistory === 0) { return Promise.resolve([]); } @@ -197,7 +207,7 @@ class MessageStorage { } ) ); - }); + }) as Promise; } search(query) { @@ -225,7 +235,7 @@ class MessageStorage { const maxResults = 100; select += " ORDER BY time DESC LIMIT ? OFFSET ? "; - params.push(maxResults); + params.push(maxResults.toString()); query.offset = parseInt(query.offset, 10) || 0; params.push(query.offset); @@ -252,7 +262,7 @@ class MessageStorage { } } -module.exports = MessageStorage; +export default SqliteMessageStorage; function parseSearchRowsToMessages(id, rows) { const messages = []; diff --git a/src/plugins/messageStorage/text.js b/src/plugins/messageStorage/text.ts similarity index 77% rename from src/plugins/messageStorage/text.js rename to src/plugins/messageStorage/text.ts index 602aeeca5c..0039b7a375 100644 --- a/src/plugins/messageStorage/text.js +++ b/src/plugins/messageStorage/text.ts @@ -1,14 +1,22 @@ "use strict"; -const log = require("../../log"); -const fs = require("fs"); -const path = require("path"); -const filenamify = require("filenamify"); -const Config = require("../../config"); -const Msg = require("../../models/msg"); - -class TextFileMessageStorage { - constructor(client) { +import log from "../../log"; +import fs from "fs"; +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"; + +class TextFileMessageStorage implements MessageStorage { + client: Client; + isEnabled: boolean; + + constructor(client: Client) { this.client = client; this.isEnabled = false; } @@ -17,7 +25,7 @@ class TextFileMessageStorage { this.isEnabled = true; } - close(callback) { + close(callback: () => void) { this.isEnabled = false; if (callback) { @@ -25,7 +33,7 @@ class TextFileMessageStorage { } } - index(network, channel, msg) { + index(network: Network, channel: Channel, msg: Message) { if (!this.isEnabled) { return; } @@ -47,47 +55,47 @@ class TextFileMessageStorage { // message types from src/models/msg.js switch (msg.type) { - case Msg.Type.ACTION: + case MessageType.ACTION: // [2014-01-01 00:00:00] * @Arnold is eating cookies line += `* ${msg.from.mode}${msg.from.nick} ${msg.text}`; break; - case Msg.Type.JOIN: + case MessageType.JOIN: // [2014-01-01 00:00:00] *** Arnold (~arnold@foo.bar) joined line += `*** ${msg.from.nick} (${msg.hostmask}) joined`; break; - case Msg.Type.KICK: + case MessageType.KICK: // [2014-01-01 00:00:00] *** Arnold was kicked by Bernie (Don't steal my cookies!) line += `*** ${msg.target.nick} was kicked by ${msg.from.nick} (${msg.text})`; break; - case Msg.Type.MESSAGE: + case MessageType.MESSAGE: // [2014-01-01 00:00:00] <@Arnold> Put that cookie down.. Now!! line += `<${msg.from.mode}${msg.from.nick}> ${msg.text}`; break; - case Msg.Type.MODE: + case MessageType.MODE: // [2014-01-01 00:00:00] *** Arnold set mode +o Bernie line += `*** ${msg.from.nick} set mode ${msg.text}`; break; - case Msg.Type.NICK: + case MessageType.NICK: // [2014-01-01 00:00:00] *** Arnold changed nick to Bernie line += `*** ${msg.from.nick} changed nick to ${msg.new_nick}`; break; - case Msg.Type.NOTICE: + case MessageType.NOTICE: // [2014-01-01 00:00:00] -Arnold- pssst, I have cookies! line += `-${msg.from.nick}- ${msg.text}`; break; - case Msg.Type.PART: + case MessageType.PART: // [2014-01-01 00:00:00] *** Arnold (~arnold@foo.bar) left (Bye all!) line += `*** ${msg.from.nick} (${msg.hostmask}) left (${msg.text})`; break; - case Msg.Type.QUIT: + case MessageType.QUIT: // [2014-01-01 00:00:00] *** Arnold (~arnold@foo.bar) quit (Connection reset by peer) line += `*** ${msg.from.nick} (${msg.hostmask}) quit (${msg.text})`; break; - case Msg.Type.CHGHOST: + case MessageType.CHGHOST: // [2014-01-01 00:00:00] *** Arnold changed host to: new@fancy.host line += `*** ${msg.from.nick} changed host to '${msg.new_ident}@${msg.new_host}'`; break; - case Msg.Type.TOPIC: + case MessageType.TOPIC: // [2014-01-01 00:00:00] *** Arnold changed topic to: welcome everyone! line += `*** ${msg.from.nick} changed topic to '${msg.text}'`; break; @@ -141,7 +149,7 @@ class TextFileMessageStorage { return false; } - static getNetworkFolderName(network) { + static getNetworkFolderName(network: Network) { // Limit network name in the folder name to 23 characters // So we can still fit 12 characters of the uuid for de-duplication const networkName = cleanFilename(network.name.substring(0, 23).replace(/ /g, "-")); @@ -149,12 +157,12 @@ class TextFileMessageStorage { return `${networkName}-${network.uuid.substring(networkName.length + 1)}`; } - static getChannelFileName(channel) { + static getChannelFileName(channel: Channel) { return `${cleanFilename(channel.name)}.log`; } } -module.exports = TextFileMessageStorage; +export default TextFileMessageStorage; function cleanFilename(name) { name = filenamify(name, {replacement: "_"}); diff --git a/src/plugins/packages/index.js b/src/plugins/packages/index.ts similarity index 82% rename from src/plugins/packages/index.js rename to src/plugins/packages/index.ts index 13d5a1be5e..a3b7aa22a3 100644 --- a/src/plugins/packages/index.js +++ b/src/plugins/packages/index.ts @@ -1,20 +1,21 @@ "use strict"; -const _ = require("lodash"); -const log = require("../../log"); -const colors = require("chalk"); -const path = require("path"); -const semver = require("semver"); -const Helper = require("../../helper"); -const Config = require("../../config"); -const themes = require("./themes"); +import _ from "lodash"; +import log from "../../log"; +import colors from "chalk"; +import path from "path"; +import semver from "semver"; +import Helper from "../../helper"; +import Config from "../../config"; +import themes from "./themes"; const packageMap = new Map(); -const inputs = require("../inputs"); -const fs = require("fs"); -const Utils = require("../../command-line/utils"); +import inputs from "../inputs"; +import fs from "fs"; +import Utils from "../../command-line/utils"; +import Client from "src/client"; -const stylesheets = []; -const files = []; +const stylesheets: string[] = []; +const files: string[] = []; const TIME_TO_LIVE = 15 * 60 * 1000; // 15 minutes, in milliseconds @@ -24,7 +25,7 @@ const cache = { let experimentalWarningPrinted = false; -module.exports = { +export default { getFiles, getStylesheets, getPackage, @@ -42,7 +43,7 @@ const packageApis = function (packageInfo) { }, Commands: { add: inputs.addPluginCommand.bind(this, packageInfo), - runAsUser: (command, targetId, client) => + runAsUser: (command: string, targetId: number, client: Client) => client.inputLine({target: targetId, text: command}), }, Config: { @@ -66,7 +67,7 @@ function getStylesheets() { return stylesheets; } -function addFile(packageName, filename) { +function addFile(packageName: string, filename: string) { files.push(packageName + "/" + filename); } @@ -78,7 +79,7 @@ function getPackage(name) { return packageMap.get(name); } -function getEnabledPackages(packageJson) { +function getEnabledPackages(packageJson: string) { try { const json = JSON.parse(fs.readFileSync(packageJson, "utf-8")); return Object.keys(json.dependencies); @@ -89,15 +90,16 @@ function getEnabledPackages(packageJson) { return []; } -function getPersistentStorageDir(packageName) { +function getPersistentStorageDir(packageName: string) { const dir = path.join(Config.getPackagesPath(), packageName); fs.mkdirSync(dir, {recursive: true}); // we don't care if it already exists or not return dir; } -function loadPackage(packageName) { - let packageInfo; - let packageFile; +function loadPackage(packageName: string) { + let packageInfo: PackageInfo; + // TODO: type + let packageFile: any; try { const packagePath = Config.getPackageModulePath(packageName); @@ -125,9 +127,11 @@ function loadPackage(packageName) { } const version = packageInfo.version; - packageInfo = packageInfo.thelounge; - packageInfo.packageName = packageName; - packageInfo.version = version; + packageInfo = { + ...packageInfo.thelounge, + packageName: packageName, + version, + }; packageMap.set(packageName, packageFile); @@ -164,7 +168,7 @@ function loadPackages() { watchPackages(packageJson); } -function watchPackages(packageJson) { +function watchPackages(packageJson: string) { fs.watch( packageJson, { @@ -219,7 +223,8 @@ async function outdated(cacheTimeout = TIME_TO_LIVE) { } // If we get an error from calling outdated and the code isn't 0, then there are no outdated packages - await Utils.executeYarnCommand(...argsList) + // TODO: was (...argsList), verify this works + await Utils.executeYarnCommand(argsList.shift(), ...argsList) .then(() => updateOutdated(false)) .catch((code) => updateOutdated(code !== 0)); diff --git a/src/plugins/packages/themes.js b/src/plugins/packages/themes.ts similarity index 81% rename from src/plugins/packages/themes.js rename to src/plugins/packages/themes.ts index ba422bc33b..797fe4c1de 100644 --- a/src/plugins/packages/themes.js +++ b/src/plugins/packages/themes.ts @@ -1,12 +1,14 @@ "use strict"; -const fs = require("fs"); -const Config = require("../../config"); -const path = require("path"); -const _ = require("lodash"); +import fs from "fs"; +import path from "path"; +import _ from "lodash"; + +import Config from "../../config"; + const themes = new Map(); -module.exports = { +export default { addTheme, getAll, getByName, @@ -24,7 +26,7 @@ function loadLocalThemes() { .forEach((theme) => themes.set(theme.name, theme)); } -function addTheme(packageName, packageObject) { +function addTheme(packageName: string, packageObject) { const theme = makePackageThemeObject(packageName, packageObject); if (theme) { @@ -46,7 +48,7 @@ function getByName(name) { return themes.get(name); } -function makeLocalThemeObject(css) { +function makeLocalThemeObject(css: string) { const themeName = css.slice(0, -4); return { displayName: themeName.charAt(0).toUpperCase() + themeName.slice(1), @@ -55,7 +57,7 @@ function makeLocalThemeObject(css) { }; } -function makePackageThemeObject(moduleName, module) { +function makePackageThemeObject(moduleName: string, module: ThemeModule) { if (!module || module.type !== "theme") { return; } diff --git a/src/plugins/sts.js b/src/plugins/sts.ts similarity index 77% rename from src/plugins/sts.js rename to src/plugins/sts.ts index 697af9aee7..e490e3f03a 100644 --- a/src/plugins/sts.js +++ b/src/plugins/sts.ts @@ -1,12 +1,17 @@ "use strict"; -const _ = require("lodash"); -const fs = require("fs"); -const path = require("path"); -const log = require("../log"); -const Config = require("../config"); +import _ from "lodash"; +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"; class STSPolicies { + private stsFile: string; + private policies: PolicyMap; + private refresh: _.DebouncedFunc; + constructor() { this.stsFile = path.join(Config.getHomePath(), "sts-policies.json"); this.policies = new Map(); @@ -16,7 +21,7 @@ class STSPolicies { return; } - const storedPolicies = JSON.parse(fs.readFileSync(this.stsFile, "utf-8")); + const storedPolicies = JSON.parse(fs.readFileSync(this.stsFile, "utf-8")) as PolicyOption[]; const now = Date.now(); storedPolicies.forEach((value) => { @@ -30,7 +35,7 @@ class STSPolicies { }); } - get(host) { + get(host: string) { const policy = this.policies.get(host); if (typeof policy === "undefined") { @@ -46,7 +51,7 @@ class STSPolicies { return policy; } - update(host, port, duration) { + update(host: string, port: number, duration: number) { if (duration > 0) { this.policies.set(host, { port: port, @@ -60,7 +65,7 @@ class STSPolicies { this.refresh(); } - refreshExpiration(host) { + refreshExpiration(host: string) { const policy = this.policies.get(host); if (typeof policy === "undefined") { @@ -92,4 +97,4 @@ class STSPolicies { } } -module.exports = new STSPolicies(); +export default new STSPolicies(); diff --git a/src/server.js b/src/server.ts similarity index 92% rename from src/server.js rename to src/server.ts index 49c417a33d..a7bde7d5fd 100644 --- a/src/server.js +++ b/src/server.ts @@ -1,37 +1,50 @@ "use strict"; -const _ = require("lodash"); -const log = require("./log"); -const pkg = require("../package.json"); -const Client = require("./client"); -const ClientManager = require("./clientManager"); -const express = require("express"); -const fs = require("fs"); -const path = require("path"); -const io = require("socket.io"); -const dns = require("dns"); -const Uploader = require("./plugins/uploader"); -const Helper = require("./helper"); -const Config = require("./config"); -const colors = require("chalk"); -const net = require("net"); -const Identification = require("./identification"); -const changelog = require("./plugins/changelog"); -const inputs = require("./plugins/inputs"); -const Auth = require("./plugins/auth"); - -const themes = require("./plugins/packages/themes"); +import _ from "lodash"; +import log from "./log"; +import pkg from "../package.json"; +import Client from "./client"; +import ClientManager from "./clientManager"; +import express from "express"; +import fs from "fs"; +import path from "path"; +import {Server} from "socket.io"; +import dns from "dns"; +import Uploader from "./plugins/uploader"; +import Helper from "./helper"; +import Config from "./config"; +import colors from "chalk"; +import net from "net"; +import Identification from "./identification"; +import changelog from "./plugins/changelog"; +import inputs from "./plugins/inputs"; +import Auth from "./plugins/auth"; + +import themes from "./plugins/packages/themes"; themes.loadLocalThemes(); -const packages = require("./plugins/packages/index"); -const Chan = require("./models/chan"); +import packages from "./plugins/packages/index"; +import Chan from "./models/chan"; +import { + ClientConfiguration, + Defaults, + IndexTemplateConfiguration, + ServerConfiguration, +} 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; -module.exports = function (options = {}) { +export default function ( + options: ServerOptions = { + dev: false, + } +) { log.info(`The Lounge ${colors.green(Helper.getVersion())} \ (Node.js ${colors.green(process.versions.node)} on ${colors.green(process.platform)} ${ process.arch @@ -165,11 +178,13 @@ module.exports = function (options = {}) { ); } - const sockets = io(server, { - wsEngine: require("ws").Server, + const sockets = new Server(server, { + wsEngine: wsServer, cookie: false, serveClient: false, - transports: Config.values.transports, + + // TODO: type as Server.Transport[] + transports: Config.values.transports as any, pingTimeout: 60000, }); @@ -250,7 +265,7 @@ module.exports = function (options = {}) { }); return server; -}; +} function getClientLanguage(socket) { const acceptLanguage = socket.handshake.headers["accept-language"]; @@ -342,7 +357,7 @@ function indexRequest(req, res) { throw err; } - const config = getServerConfiguration(); + const config = getServerConfiguration() as IndexTemplateConfiguration; config.cacheBust = Helper.getVersionCacheBust(); res.send(_.template(file)(config)); @@ -465,8 +480,8 @@ function initializeClient(socket, client, token, lastMessage, openChannel) { const hash = Helper.password.hash(p1); - client.setPassword(hash, (success) => { - const obj = {success: false}; + client.setPassword(hash, (success: boolean) => { + const obj = {success: false, error: undefined}; if (success) { obj.success = true; @@ -477,7 +492,7 @@ function initializeClient(socket, client, token, lastMessage, openChannel) { socket.emit("change-password", obj); }); }) - .catch((error) => { + .catch((error: any) => { log.error(`Error while checking users password. Error: ${error}`); }); } @@ -673,14 +688,14 @@ function initializeClient(socket, client, token, lastMessage, openChannel) { const {chan, network} = client.find(target); // If the user mutes the lobby, we mute the entire network. - if (chan.type === Chan.Type.LOBBY) { + if (chan.type === ChanType.LOBBY) { for (const channel of network.channels) { - if (channel.type !== Chan.Type.SPECIAL) { + if (channel.type !== ChanType.SPECIAL) { channel.setMuteStatus(setMutedTo); } } } else { - if (chan.type !== Chan.Type.SPECIAL) { + if (chan.type !== ChanType.SPECIAL) { chan.setMuteStatus(setMutedTo); } } @@ -757,8 +772,13 @@ function initializeClient(socket, client, token, lastMessage, openChannel) { } } -function getClientConfiguration() { - const config = _.pick(Config.values, ["public", "lockNetwork", "useHexIp", "prefetch"]); +function getClientConfiguration(): ClientConfiguration { + const config = _.pick(Config.values, [ + "public", + "lockNetwork", + "useHexIp", + "prefetch", + ]) as ClientConfiguration; config.fileUpload = Config.values.fileUpload.enable; config.ldapEnabled = Config.values.ldap.enable; @@ -774,7 +794,7 @@ function getClientConfiguration() { "password", "realname", "join", - ]); + ]) as Defaults; } config.isUpdateAvailable = changelog.isUpdateAvailable; @@ -795,8 +815,8 @@ function getClientConfiguration() { return config; } -function getServerConfiguration() { - const config = _.clone(Config.values); +function getServerConfiguration(): ServerConfiguration { + const config = _.clone(Config.values) as ServerConfiguration; config.stylesheets = packages.getStylesheets(); @@ -917,6 +937,9 @@ function reverseDnsLookup(ip, callback) { } dns.resolve(hostnames[0], net.isIP(ip) === 6 ? "AAAA" : "A", (resolveErr, resolvedIps) => { + // TODO: investigate SoaRecord class + if (!Array.isArray(resolvedIps)) return callback(ip); + if (resolveErr || resolvedIps.length < 1) { return callback(ip); } diff --git a/src/tsconfig.json b/src/tsconfig.json new file mode 100644 index 0000000000..7a3b2b4c7a --- /dev/null +++ b/src/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../tsconfig.json", + "files": ["index.d.ts"], + + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "types": ["node"], + "resolveJsonModule": true + } +} diff --git a/src/types/client.d.ts b/src/types/client.d.ts new file mode 100644 index 0000000000..e45bd27645 --- /dev/null +++ b/src/types/client.d.ts @@ -0,0 +1,40 @@ +import {MessageType, UserInMessage} from "./models/message"; + +type ClientConfig = { + log: boolean; + password: string; + sessions: { + [token: string]: { + lastUse: number; + ip: string; + agent: string; + pushSubscription: PushSubscription; + }; + }; + clientSettings: { + [key: string]: any; + }; + browser?: { + language?: string; + ip?: string; + hostname?: string; + isSecure?: boolean; + }; +}; + +type PushSubscription = { + endpoint: string; + keys: { + p256dh: string; + auth: string; + }; +}; + +type Mention = { + chanId: number; + msgId: number; + type: MessageType; + time: number; + text: string; + from: UserInMessage; +}; diff --git a/src/types/config.d.ts b/src/types/config.d.ts new file mode 100644 index 0000000000..c8c468aa93 --- /dev/null +++ b/src/types/config.d.ts @@ -0,0 +1,113 @@ +type Config = { + public: boolean; + host: string | undefined; + port: number; + bind: string | undefined; + reverseProxy: boolean; + maxHistory: number; + https: Https; + theme: string; + prefetch: boolean; + disableMediaPreview: boolean; + prefetchStorage: boolean; + prefetchMaxImageSize: number; + prefetchMaxSearchSize: number; + prefetchTimeout: number; + fileUpload: FileUpload; + transports: string[]; + leaveMessage: string; + defaults: Defaults; + lockNetwork: boolean; + messageStorage: string[]; + useHexIp: boolean; + webirc?: WebIRC; + identd: Identd; + oidentd?: string; + ldap: Ldap; + debug: Debug; + themeColor: string; +}; + +type ClientConfiguration = Pick< + Config, + "public" | "lockNetwork" | "useHexIp" | "prefetch" | "defaults" +> & { + fileUpload: boolean; + ldapEnabled: boolean; + isUpdateAvailable: boolean; + applicationServerKey: string; + version: string; + gitCommit: string; + defaultTheme: string; + themes: string[]; + defaults: Defaults & { + sasl?: string; + saslAccount?: string; + saslPassword?: string; + }; + fileUploadMaxFileSize?: number; +}; + +type ServerConfiguration = Config & { + stylesheets: string[]; +}; + +// TODO: Type this +type WebIRC = { + [key: string]: any; +}; + +type Https = { + enable: boolean; + key: string; + certificate: string; + ca: string; +}; + +export type FileUpload = { + enable: boolean; + maxFileSize: number; + baseUrl?: string; +}; + +export type Defaults = { + name: string; + host: string; + port: number; + password: string; + tls: boolean; + rejectUnauthorized: boolean; + nick: string; + username: string; + realname: string; + join: string; + leaveMessage: string; +}; + +export type Identd = { + enable: boolean; + port: number; +}; + +export type Ldap = { + enable: boolean; + url: string; + tlsOptions: any; + primaryKey: string; + searchDN: SearchDN; +}; + +export type TlsOptions = any; + +export type SearchDN = { + rootDN: string; + rootPassword: string; + filter: string; + base: string; + scope: string; +}; + +export type Debug = { + ircFramework: boolean; + raw: boolean; +}; diff --git a/src/types/helper.d.ts b/src/types/helper.d.ts new file mode 100644 index 0000000000..1f06e0803f --- /dev/null +++ b/src/types/helper.d.ts @@ -0,0 +1,5 @@ +type Hostmask = { + nick: string; + ident: string; + hostname: string; +}; diff --git a/src/types/index.d.ts b/src/types/index.d.ts new file mode 100644 index 0000000000..5b24cc03f8 --- /dev/null +++ b/src/types/index.d.ts @@ -0,0 +1,5 @@ +/// +/// +/// +/// +/// diff --git a/src/types/models/channel.d.ts b/src/types/models/channel.d.ts new file mode 100644 index 0000000000..148730922b --- /dev/null +++ b/src/types/models/channel.d.ts @@ -0,0 +1,27 @@ +import Chan from "src/models/chan"; + +export type Channel = Chan; + +export type FilteredChannel = Chan & { + users: []; + totalMessages: number; +}; + +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, +} diff --git a/src/types/models/index.d.ts b/src/types/models/index.d.ts new file mode 100644 index 0000000000..5bc4645ce9 --- /dev/null +++ b/src/types/models/index.d.ts @@ -0,0 +1,3 @@ +/// +/// +/// diff --git a/src/types/models/message.d.ts b/src/types/models/message.d.ts new file mode 100644 index 0000000000..0cea813dbf --- /dev/null +++ b/src/types/models/message.d.ts @@ -0,0 +1,43 @@ +import Msg from "src/models/msg"; +import User from "src/models/user"; + +type Message = Msg; + +type UserInMessage = Partial & { + mode: 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", +} diff --git a/src/types/models/network.d.ts b/src/types/models/network.d.ts new file mode 100644 index 0000000000..57f0010f8f --- /dev/null +++ b/src/types/models/network.d.ts @@ -0,0 +1,8 @@ +import NetworkClass from "src/models/network"; + +export type Network = NetworkClass; + +export type NetworkStatus = { + connected: boolean; + secure: boolean; +}; diff --git a/src/types/models/prefix.d.ts b/src/types/models/prefix.d.ts new file mode 100644 index 0000000000..53d67cce8c --- /dev/null +++ b/src/types/models/prefix.d.ts @@ -0,0 +1,6 @@ +type PrefixSymbol = string; + +type PrefixObject = { + symbol: PrefixSymbol; + mode: string; +}; diff --git a/src/types/models/user.d.ts b/src/types/models/user.d.ts new file mode 100644 index 0000000000..1f1d773875 --- /dev/null +++ b/src/types/models/user.d.ts @@ -0,0 +1,3 @@ +import UserClass from "src/models/user"; + +export type User = UserClass; diff --git a/src/types/modules/irc-framework.d.ts b/src/types/modules/irc-framework.d.ts new file mode 100644 index 0000000000..f713a9691e --- /dev/null +++ b/src/types/modules/irc-framework.d.ts @@ -0,0 +1,414 @@ +// https://raw.githubusercontent.com/eternagame/HTML-Chat/vue-rewrite/src/app/types/modules/irc-framework/irc-framework.d.ts + +declare module "irc-framework" { + import {EventEmitter} from "eventemitter3"; + // import { DuplexStream } from 'stream'; + import Connection from "irc-framework/src/transports/websocket"; + + type ConnectionOpts = { + connected: boolean; + requested_disconnect: boolean; + + reconnect_attempts: number; + + // When an IRC connection was successfully registered. + registered: boolean; + + transport: any; + }; + + export class Client extends EventEmitter { + constructor(options: ClientConstructorParameters); + + // Added by Max + connection: ConnectionOpts; + network: { + options: { + CHANTYPES: string; + PREFIX: any; + CHANMODES: string; + }; + cap: { + isEnabled: (cap: string) => boolean; + enabled: string[]; + }; + }; + // End of added by Max + + static setDefaultTransport(transport: any): void; + + // get Message(): ClassDecorator;//TODO + /** Applies the default options to the options object given as impot, and returns it. */ + _applyDefaultOptions( + user_options: ClientConstructorParameters + ): ClientConstructorParameters; + + createStructure(): void; + + /** Is connected to the IRC network and successfully registered. */ + connected: boolean; + + // TODO + /** The object for the connected message, as long as the client is connected. */ user: IrcUser; + + // TODO + /** Request */ requestCap(capability: string[]): void; + + use(a: any): any; + + connect(connect_options?: Object): void; + + /** + * Proxy the command handler events onto the client object, with some added sugar + * Events are handled in order: + * 1. Received from the command handler + * 2. Checked if any extra properties/methods are to be added to the params + re-emitted + * 3. Routed through middleware + * 4. Emitted from the client instance + */ + proxyIrcEvents(): void; + + addCommandHandlerListeners(): void; + + registerToNetwork(): void; + + startPeriodicPing(): void; + + raw(...raw_data_line: string[]): void; + + rawString(...parameters: Array): string; + + rawString(parameters: Array): string; + + quit(quit_message?: string): void; + + ping(message?: string): void; + + changeNick(nick: string): void; + + sendMessage(commandName: string, target: string, message: string): string[]; + + say(target: string, message: string): string[]; + + notice(target: string, message: string): string[]; + + join(channel: string, key?: string): void; + + part(channel: string, message?: string): void; + + mode(channel: string, mode: string, extra_args?: string[]): void; + + inviteList(channel: string, cb: (e: Event) => any): void; + + // TODO: typeof e? + invite(channel: string, nick: string): void; + + addInvite(channel: String, mask: string): void; + + removeInvite(channel: string, mask: string): void; + + banlist(channel: string, cb: (e: Event) => any): void; + + ban(channel: string, mask: string): void; + + unban(channel: string, mask: string): void; + + setTopic(channel: string, newTopic: string): void; + + ctcpRequest(target: string, type: string /* , ...params: Array */): void; + + ctcpResponse(target: string, type: string /* , params: Array */): void; + + action(target: string, message: string): string[]; + + whowas(target: string, cb: (event: Event) => any): void; + + whois(nick: string, cb: (event: any) => void): void; + + /** + * WHO requests are queued up to run serially. + * This is mostly because networks will only reply serially and it makes + * it easier to include the correct replies to callbacks + */ + who(target: string, cb: (event: any) => void): void; + + list(/* params: Array */): void; + + channel(channel_name: string): IrcChannel; + + match( + match_regex: string, + cb: (event: Event) => any, + message_type: string + ): {stop: () => void}; + + matchNotice(match_regex: string, cb: (event: Event) => any): void; + + matchMessage(match_regex: string, cb: (event: Event) => any): void; + + matchAction(match_regex: string, cb: (event: Event) => any): void; + + stringToBlocks(str: string, block_size?: number): string[]; + + on(eventType: string | symbol, cb: (event: any) => void): this; + + on(eventType: "raw", cb: (event: RawEventArgs) => void): this; + + on(eventType: "join", cb: (event: JoinEventArgs) => void): this; + + on(eventType: "registered", cb: (event: RegisteredEventArgs) => void): this; + + on(eventType: "quit", cb: (event: QuitEventArgs) => void): this; + + on(eventType: "part", cb: (event: QuitEventArgs) => void): this; + + on(eventType: "kick", cb: (event: QuitEventArgs) => void): this; + + on(eventType: "message", cb: (event: MessageEventArgs) => any): this; + + on(eventType: "notice", cb: (event: MessageEventArgs /* TODO */) => any): this; + + on(eventType: "mode", cb: (event: ModeEventArgs) => any): this; + + on(eventType: "socket close", cb: (event: {}) => any): this; + + on(eventType: "socket connected", cb: (event: {}) => any): this; + + on(eventType: "raw socket connected", cb: (event: {}) => any): this; + + on(eventType: "server options", cb: (event: ServerOptionsEventArgs) => any): this; + + on(eventType: "debug", cb: (message: string) => any): this; + + on(eventType: "nick in use", cb: (event: NickInUseEventArgs) => any): this; + + on(eventType: "nick invalid", cb: (event: NickInvalidEventArgs) => any): this; + + on(eventType: "irc error", cb: (event: IrcErrorEventArgs) => any): this; + } + export class Message { + // TODO: What is actually in it and what was in the event? + constructor(command?: string, ...args: string[]); + + account?: IrcUser; + + group?: any; + + hostname: string; + + ident: string; + + message: string; + + nick: string; + + reply(e: any): any; + + tags: Object; + + // any + time?: any; + + type: string; + } + export interface MessageEventArgs { + account?: any; + group?: any; + hostname: string; + ident: string; + message: string; + nick: string; + reply: (message: string) => void; + tags: {[key: string]: string}; + target: string; + time?: any; + type: "privmsg" | "action"; // TODO + } + export interface JoinEventArgs { + // todo: is that wrong? + account: boolean; + channel: string; + gecos: string; + hostname: string; + ident: string; + nick: string; + time?: any; + } + export interface KickEventArgs { + kicked: string; + nick: string; + ident: string; + hostname: string; + channel: string; + message: string; + time: number; + } + export interface RawEventArgs { + from_server: boolean; + line: string; + } + export interface RegisteredEventArgs { + nick: string; + } + export interface QuitEventArgs { + hostname: string; + ident: string; + message: string; + nick: string; + time?: any; + } + interface Mode { + mode: string; + param: string; + } + export interface ModeEventArgs { + modes: Mode[]; + nick: string; + raw_modes: string; + raw_params: string[]; + target: string; + time?: any; + } + export interface ServerOptionsEventArgs { + options: any; + cap: any; + } + export interface NickInvalidEventArgs { + nick: string; + reason: string; + } + export interface NickInUseEventArgs { + nick: string; + reason: string; + } + export interface IrcErrorEventArgs { + error: string; + channel: string; + reason: string; + } + // interface IrcUser { + // /**The current nick you are currently using.*/ + // nick: string; + // /**Your username (ident) that the network sees you as using.*/ + // username: string; + // /**Your current gecos (realname).*/ + // gecos: string; + // /**On supported servers, the hostname that the networksees you are using.*/ + // host: string; + // /**Your current away status. Empty for not away.*/ + // away: string; + // /**A set() instance with your current message modes.*/ + // modes: Set; + // } + // TODO: what to call it? why is it channel.users empty after join? + interface IrcUser { + hostname: string; + ident: string; + modes: string[]; // any[] + nick: string; + username: string; + gecos: string; + } + + class IrcChannel extends EventEmitter { + constructor(irc_client: Client, channel_name: string, key: string); + + irc_client: Client; + + name: string; + + say(message: string): string[]; + + notice(message: string): string[]; + + join(key?: string): void; + + part(message?: string): void; + + mode(mode: string, extra_args?: string[]): void; + + banlist(cb: (e: Event) => any): void; + + ban(mask: string): void; + + unban(mask: string): void; + + users: IrcUser[]; + + /** + * Relay messages between this channel to another + * @param {IrcChannel|String} target_chan Target channel + * @param {Object} opts Extra options + * + * opts may contain the following properties: + * one_way (false) Only relay messages to target_chan, not the reverse + * replay_nicks (true) Include the sending nick as part of the relayed message + */ + relay(target_chan: IrcChannel | String, opts: Object): void; + + // stream(stream_ops: Object): DuplexStream; + + updateUsers(cb: (channel: IrcChannel) => any): void; + + on(eventType: "channel info", cb: (event: ChannelInfoEventArgs) => any): this; + + on(eventType: string | symbol, cb: (event: any) => any): this; + } + export interface ChannelInfoEventArgs { + channel: string; + created_at?: number; + modes?: Mode[]; // TODO: check type + url?: string; + } + export interface UserListEventArgs { + channel: string; + users: IrcUser[]; // TODO: check type + } + export interface WhoListEventArgs { + target: string; + users: IrcUser[]; // TODO: check type + } + export interface BanlistEventArgs { + channel: string; + bans: IrcUser[]; // TODO: check type + } + export interface TopicEventArgs { + channel: string; + topic: string; + nick?: string; + time?: number; + } + export interface TopicSetByEventArgs { + channel: string; + nick: string; + ident: string; + hostname: string; + when?: number; + } + interface ClientConstructorParameters { + host?: string; + nick?: string; + outgoing_addr?: string; + username?: string; + gecos?: string; + encoding?: string; + version?: string | boolean; + enable_chghost?: boolean; + enable_echomessage?: boolean; + enable_setname?: boolean; + message_max_length?: number; + auto_reconnect?: boolean; + auto_reconnect_wait?: number; + auto_reconnect_max_retries?: number; + ping_interval?: number; + ping_timeout?: number; + transport?: new (options: any) => Connection; + ssl?: boolean; + webirc?: { + password?: string; + username?: string; + hostname?: string; + ip?: string; + }; + } +} diff --git a/src/types/packages/index.d.ts b/src/types/packages/index.d.ts new file mode 100644 index 0000000000..5117fe68de --- /dev/null +++ b/src/types/packages/index.d.ts @@ -0,0 +1,9 @@ +/// + +type PackageInfo = { + packageName: string; + thelounge?: {supports: any}; + version: string; + type?: string; + files?: string[]; +}; diff --git a/src/types/packages/themes.d.ts b/src/types/packages/themes.d.ts new file mode 100644 index 0000000000..264f44a0c5 --- /dev/null +++ b/src/types/packages/themes.d.ts @@ -0,0 +1,10 @@ +// TODO: move to index.d.ts when more types are added +type Module = { + type: string; + name: string; +}; + +type ThemeModule = Module & { + themeColor: string; + css: string; +}; diff --git a/src/types/plugins/clientCertificate.d.ts b/src/types/plugins/clientCertificate.d.ts new file mode 100644 index 0000000000..d65b97235d --- /dev/null +++ b/src/types/plugins/clientCertificate.d.ts @@ -0,0 +1,4 @@ +type ClientCertificate = { + private_key: string; + certificate: string; +}; diff --git a/src/types/plugins/index.d.ts b/src/types/plugins/index.d.ts new file mode 100644 index 0000000000..4fcde5f21e --- /dev/null +++ b/src/types/plugins/index.d.ts @@ -0,0 +1,3 @@ +/// +/// +/// diff --git a/src/types/plugins/messageStorage/index.d.ts b/src/types/plugins/messageStorage/index.d.ts new file mode 100644 index 0000000000..f642371bcc --- /dev/null +++ b/src/types/plugins/messageStorage/index.d.ts @@ -0,0 +1,26 @@ +import {Channel} from "../../models/channel"; +import {Message} from "../../models/message"; +import {Network} from "../../models/network"; +import sqlite from "sqlite3"; +import Client from "src/client"; + +interface MessageStorage { + client: Client; + isEnabled: boolean; + + enable(): void; + + close(callback?: () => void): void; + + index(network: Network, channel: Channel, msg: Message): void; + + deleteChannel(network: Network, channel: Channel); + + getMessages(network: Network, channel: Channel): Promise; + + canProvideMessages(): boolean; +} + +interface SqliteMessageStorage extends MessageStorage { + database: sqlite.Database; +} diff --git a/src/types/plugins/sts.d.ts b/src/types/plugins/sts.d.ts new file mode 100644 index 0000000000..59efce218e --- /dev/null +++ b/src/types/plugins/sts.d.ts @@ -0,0 +1,8 @@ +type PolicyOption = { + port: number; + duration: number; + expires: number; + host: string; +}; + +export type PolicyMap = Map>; diff --git a/src/types/server.d.ts b/src/types/server.d.ts new file mode 100644 index 0000000000..04fd03c072 --- /dev/null +++ b/src/types/server.d.ts @@ -0,0 +1,3 @@ +type ServerOptions = { + dev: boolean; +}; diff --git a/test/commands/mode.js b/test/commands/mode.js index a6ea9badb5..9072ca5e86 100644 --- a/test/commands/mode.js +++ b/test/commands/mode.js @@ -13,7 +13,7 @@ describe("Commands", function () { const lobby = new Chan({ name: "Network Lobby", - type: Chan.Type.LOBBY, + type: ChanType.LOBBY, }); const testableNetwork = { diff --git a/test/models/network.js b/test/models/network.js index d933439c1d..2200d8bab9 100644 --- a/test/models/network.js +++ b/test/models/network.js @@ -24,7 +24,7 @@ describe("Network", function () { const network = new Network({ name: "Super Nice Network", channels: [ - new Chan({name: "AAAA!", type: Chan.Type.QUERY}), + new Chan({name: "AAAA!", type: ChanType.QUERY}), new Chan({name: "#thelounge"}), new Chan({name: "&foobar"}), ], @@ -32,7 +32,7 @@ describe("Network", function () { network.channels.push(new Chan({name: "#swag"})); expect(network.channels[0].name).to.equal("Super Nice Network"); - expect(network.channels[0].type).to.equal(Chan.Type.LOBBY); + expect(network.channels[0].type).to.equal(ChanType.LOBBY); }); it("should maintain channel reference", function () { @@ -83,8 +83,8 @@ describe("Network", function () { new Chan({name: "&foobar", key: "", muted: false}), new Chan({name: "#secret", key: "foo", muted: false}), new Chan({name: "&secure", key: "bar", muted: true}), - new Chan({name: "Channel List", type: Chan.Type.SPECIAL}), - new Chan({name: "PrivateChat", type: Chan.Type.QUERY, muted: true}), + new Chan({name: "Channel List", type: ChanType.SPECIAL}), + new Chan({name: "PrivateChat", type: ChanType.QUERY, muted: true}), ], }); network.setNick("chillin`"); @@ -420,7 +420,7 @@ describe("Network", function () { channels: [chan1, chan2], }); - const newUser = new Chan({name: "mcinkay", type: Chan.Type.QUERY}); + const newUser = new Chan({name: "mcinkay", type: ChanType.QUERY}); network.addChannel(newUser); expect(network.channels[1]).to.equal(chan1); @@ -431,14 +431,14 @@ describe("Network", function () { it("should sort users alphabetically", function () { const chan1 = new Chan({name: "#abc"}); const chan2 = new Chan({name: "#THELOUNGE"}); - const user1 = new Chan({name: "astorije", type: Chan.Type.QUERY}); - const user2 = new Chan({name: "xpaw", type: Chan.Type.QUERY}); + const user1 = new Chan({name: "astorije", type: ChanType.QUERY}); + const user2 = new Chan({name: "xpaw", type: ChanType.QUERY}); const network = new Network({ channels: [chan1, chan2, user1, user2], }); - const newUser = new Chan({name: "mcinkay", type: Chan.Type.QUERY}); + const newUser = new Chan({name: "mcinkay", type: ChanType.QUERY}); network.addChannel(newUser); expect(network.channels[1]).to.equal(chan1); @@ -451,14 +451,14 @@ describe("Network", function () { it("should not sort special channels", function () { const chan1 = new Chan({name: "#abc"}); const chan2 = new Chan({name: "#THELOUNGE"}); - const user1 = new Chan({name: "astorije", type: Chan.Type.QUERY}); - const user2 = new Chan({name: "xpaw", type: Chan.Type.QUERY}); + const user1 = new Chan({name: "astorije", type: ChanType.QUERY}); + const user2 = new Chan({name: "xpaw", type: ChanType.QUERY}); const network = new Network({ channels: [chan1, chan2, user1, user2], }); - const newBanlist = new Chan({name: "Banlist for #THELOUNGE", type: Chan.Type.SPECIAL}); + const newBanlist = new Chan({name: "Banlist for #THELOUNGE", type: ChanType.SPECIAL}); network.addChannel(newBanlist); expect(network.channels[1]).to.equal(chan1); @@ -471,15 +471,15 @@ describe("Network", function () { it("should not compare against special channels", function () { const chan1 = new Chan({name: "#abc"}); const chan2 = new Chan({name: "#THELOUNGE"}); - const user1 = new Chan({name: "astorije", type: Chan.Type.QUERY}); + const user1 = new Chan({name: "astorije", type: ChanType.QUERY}); const network = new Network({ channels: [chan1, chan2, user1], }); - const newBanlist = new Chan({name: "Banlist for #THELOUNGE", type: Chan.Type.SPECIAL}); + const newBanlist = new Chan({name: "Banlist for #THELOUNGE", type: ChanType.SPECIAL}); network.addChannel(newBanlist); - const newUser = new Chan({name: "mcinkay", type: Chan.Type.QUERY}); + const newUser = new Chan({name: "mcinkay", type: ChanType.QUERY}); network.addChannel(newUser); expect(network.channels[1]).to.equal(chan1); @@ -490,9 +490,9 @@ describe("Network", function () { }); it("should insert before first special channel", function () { - const banlist = new Chan({name: "Banlist for #THELOUNGE", type: Chan.Type.SPECIAL}); + const banlist = new Chan({name: "Banlist for #THELOUNGE", type: ChanType.SPECIAL}); const chan1 = new Chan({name: "#thelounge"}); - const user1 = new Chan({name: "astorije", type: Chan.Type.QUERY}); + const user1 = new Chan({name: "astorije", type: ChanType.QUERY}); const network = new Network({ channels: [banlist, chan1, user1], diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..682c69e188 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "commonjs", + "outDir": "dist", + "moduleResolution": "node", + "esModuleInterop": true, + "lib": ["es2019"], + "baseUrl": ".", + "allowJs": true + }, + "exclude": ["src/node_modules", "public/*", "client/*"], + "include": ["src/**/*"] +} diff --git a/vetur.config.js b/vetur.config.js new file mode 100644 index 0000000000..6bb6145d62 --- /dev/null +++ b/vetur.config.js @@ -0,0 +1,3 @@ +module.exports = { + projects: ["./client/tsconfig.json"], +}; diff --git a/webpack.config.js b/webpack.config.js index 018533a5d6..fb46475fa3 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,6 +5,7 @@ const path = require("path"); const CopyPlugin = require("copy-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const VueLoaderPlugin = require("vue-loader/lib/plugin"); +const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin"); const Helper = require("./src/helper.js"); const babelConfig = require("./babel.config.cjs"); @@ -12,7 +13,7 @@ const isProduction = process.env.NODE_ENV === "production"; const config = { mode: isProduction ? "production" : "development", entry: { - "js/bundle.js": [path.resolve(__dirname, "client/js/vue.js")], + "js/bundle.js": [path.resolve(__dirname, "client/js/vue.ts")], }, devtool: "source-map", output: { @@ -28,14 +29,22 @@ const config = { rules: [ { test: /\.vue$/, + loader: "vue-loader", + }, + { + test: /\.ts$/, use: { - loader: "vue-loader", + loader: "ts-loader", + // options: { + // compilerOptions: { + // preserveWhitespace: false, + // }, + // }, options: { - compilerOptions: { - preserveWhitespace: false, - }, + appendTsSuffixTo: [/\.vue$/], }, }, + exclude: path.resolve(__dirname, "node_modules"), }, { test: /\.css$/, @@ -63,11 +72,21 @@ const config = { ], }, { - test: /\.js$/, + test: /\.{js,ts,tsx}$/, include: [path.resolve(__dirname, "client")], use: { loader: "babel-loader", +<<<<<<< HEAD options: babelConfig, +||||||| parent of f6bd9354 (ts progress) + options: { + presets: [["@babel/env"]], + }, +======= + options: { + presets: ["@babel/env", "babel-preset-typescript-vue"], + }, +>>>>>>> f6bd9354 (ts progress) }, }, ], @@ -83,11 +102,27 @@ const config = { }, }, }, + resolve: { + alias: { + vue$: "vue/dist/vue.esm.js", + }, + extensions: [".js", ".vue", ".json", ".ts"], + // modules: ["node_modules", path.resolve(__dirname, "client")], + plugins: [ + new TsconfigPathsPlugin({ + configFile: path.resolve(__dirname, "client/tsconfig.json"), + extensions: [".js", ".vue", ".json", ".ts"], + baseUrl: path.resolve(__dirname, "client"), + }), + ], + }, externals: { json3: "JSON", // socket.io uses json3.js, but we do not target any browsers that need it }, plugins: [ - new VueLoaderPlugin(), + new VueLoaderPlugin({ + esModule: true, + }), new MiniCssExtractPlugin({ filename: "css/style.css", }), diff --git a/yarn.lock b/yarn.lock index b65c5a5ab2..6b931e576c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -556,6 +556,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" +"@babel/plugin-syntax-typescript@^7.16.7": + version "7.17.10" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.17.10.tgz#80031e6042cad6a95ed753f672ebd23c30933195" + integrity sha512-xJefea1DWXW09pW4Tm9bjwVlPDyYA2it3fWlmEjpYz6alPvTUjL0EOzNzI/FEOyI3r4/J7uVH5UqKgl1TQ5hqQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-transform-arrow-functions@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.16.7.tgz#44125e653d94b98db76369de9c396dc14bef4154" @@ -791,6 +798,15 @@ dependencies: "@babel/helper-plugin-utils" "^7.16.7" +"@babel/plugin-transform-typescript@^7.16.7", "@babel/plugin-transform-typescript@^7.3.2": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz#591ce9b6b83504903fa9dd3652c357c2ba7a1ee0" + integrity sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/plugin-syntax-typescript" "^7.16.7" + "@babel/plugin-transform-unicode-escapes@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.16.7.tgz#da8717de7b3287a2c6d659750c964f302b31ece3" @@ -897,6 +913,15 @@ "@babel/types" "^7.4.4" esutils "^2.0.2" +"@babel/preset-typescript@7.16.7", "@babel/preset-typescript@^7.3.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz#ab114d68bb2020afc069cd51b37ff98a046a70b9" + integrity sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-validator-option" "^7.16.7" + "@babel/plugin-transform-typescript" "^7.16.7" + "@babel/runtime@^7.8.4": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" @@ -961,6 +986,18 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" +"@cspotcode/source-map-consumer@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" + integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== + +"@cspotcode/source-map-support@0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== + dependencies: + "@cspotcode/source-map-consumer" "0.8.0" + "@csstools/postcss-font-format-keywords@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.0.tgz#7e7df948a83a0dfb7eb150a96e2390ac642356a1" @@ -1232,6 +1269,34 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== +"@tsconfig/node10@^1.0.7": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" + integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== + +"@tsconfig/node12@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" + integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== + +"@tsconfig/node14@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" + integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== + +"@tsconfig/node16@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" + integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== + +"@types/body-parser@*": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + "@types/cacheable-request@^6.0.1": version "6.0.2" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" @@ -1254,6 +1319,13 @@ resolved "https://registry.yarnpkg.com/@types/component-emitter/-/component-emitter-1.2.11.tgz#50d47d42b347253817a39709fef03ce66a108506" integrity sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ== +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + "@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" @@ -1290,6 +1362,25 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/express-serve-static-core@^4.17.18": + version "4.17.28" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz#c47def9f34ec81dc6328d0b1b5303d1ec98d86b8" + integrity sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/http-cache-semantics@*": version "4.0.1" resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" @@ -1305,6 +1396,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + "@types/keyv@*": version "3.1.4" resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" @@ -1312,6 +1408,23 @@ dependencies: "@types/node" "*" +"@types/ldapjs@2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@types/ldapjs/-/ldapjs-2.2.2.tgz#cf79510d8dc34e5579442c2743f8a228427eb99c" + integrity sha512-U5HdnwIZ5uZa+f3usxdqgyfNmOROxOxXvQdQtsu6sKo8fte5vej9br2csHxPvXreAbAO1bs8/rdEzvCLpi67nQ== + dependencies: + "@types/node" "*" + +"@types/lodash@4.14.182": + version "4.14.182" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" + integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + "@types/minimatch@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -1322,6 +1435,11 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== +"@types/mousetrap@1.6.9": + version "1.6.9" + resolved "https://registry.yarnpkg.com/@types/mousetrap/-/mousetrap-1.6.9.tgz#f1ef9adbd1eac3466f21b6988b1c82c633a45340" + integrity sha512-HUAiN65VsRXyFCTicolwb5+I7FM6f72zjMWr+ajGk+YTvzBgXqa2A5U7d+rtsouAkunJ5U4Sb5lNJjo9w+nmXg== + "@types/node@*", "@types/node@>=10.0.0": version "17.0.23" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" @@ -1337,6 +1455,16 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== +"@types/qs@*": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + "@types/responselike@*", "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" @@ -1344,6 +1472,38 @@ dependencies: "@types/node" "*" +"@types/serve-static@*": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/sqlite3@3.1.8": + version "3.1.8" + resolved "https://registry.yarnpkg.com/@types/sqlite3/-/sqlite3-3.1.8.tgz#e64310c5841fc01c1a8795d960d951e4cf940296" + integrity sha512-sQMt/qnyUWnqiTcJXm5ZfNPIBeJ/DVvJDwxw+0tAxPJvadzfiP1QhryO1JOR6t1yfb8NpzQb/Rud06mob5laIA== + dependencies: + "@types/node" "*" + +"@types/ua-parser-js@0.7.36": + version "0.7.36" + resolved "https://registry.yarnpkg.com/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz#9bd0b47f26b5a3151be21ba4ce9f5fa457c5f190" + integrity sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ== + +"@types/uuid@8.3.4": + version "8.3.4" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== + +"@types/ws@8.5.3": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + "@ungap/promise-all-settled@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" @@ -1365,6 +1525,30 @@ optionalDependencies: prettier "^1.18.2 || ^2.0.0" +"@vue/reactivity@3.2.33": + version "3.2.33" + resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.33.tgz#c84eedb5225138dbfc2472864c151d3efbb4b673" + integrity sha512-62Sq0mp9/0bLmDuxuLD5CIaMG2susFAGARLuZ/5jkU1FCf9EDbwUuF+BO8Ub3Rbodx0ziIecM/NsmyjardBxfQ== + dependencies: + "@vue/shared" "3.2.33" + +"@vue/runtime-core@3.2.33": + version "3.2.33" + resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.33.tgz#2df8907c85c37c3419fbd1bdf1a2df097fa40df2" + integrity sha512-N2D2vfaXsBPhzCV3JsXQa2NECjxP3eXgZlFqKh4tgakp3iX6LCGv76DLlc+IfFZq+TW10Y8QUfeihXOupJ1dGw== + dependencies: + "@vue/reactivity" "3.2.33" + "@vue/shared" "3.2.33" + +"@vue/runtime-dom@3.2.33": + version "3.2.33" + resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.33.tgz#123b8969247029ea0d9c1983676d4706a962d848" + integrity sha512-LSrJ6W7CZTSUygX5s8aFkraDWlO6K4geOwA3quFF2O+hC3QuAMZt/0Xb7JKE3C4JD4pFwCSO7oCrZmZ0BIJUnw== + dependencies: + "@vue/runtime-core" "3.2.33" + "@vue/shared" "3.2.33" + csstype "^2.6.8" + "@vue/server-test-utils@1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@vue/server-test-utils/-/server-test-utils-1.3.0.tgz#56c8f41cbb4ed9af38a5668cc23f861fcbbcd44b" @@ -1373,6 +1557,11 @@ "@types/cheerio" "^0.22.10" cheerio "^1.0.0-rc.2" +"@vue/shared@3.2.33": + version "3.2.33" + resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.33.tgz#69a8c99ceb37c1b031d5cc4aec2ff1dc77e1161e" + integrity sha512-UBc1Pg1T3yZ97vsA2ueER0F6GbJebLHYlEi4ou1H5YL4KWvMOOWwpYo9/QpWq93wxKG6Wo13IY74Hcn/f7c7Bg== + "@vue/test-utils@1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@vue/test-utils/-/test-utils-1.3.0.tgz#d563decdcd9c68a7bca151d4179a2bfd6d5c3e15" @@ -1558,6 +1747,11 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" @@ -1706,6 +1900,11 @@ are-we-there-yet@^3.0.0: delegates "^1.0.0" readable-stream "^3.6.0" +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -1849,6 +2048,16 @@ babel-plugin-polyfill-regenerator@^0.3.0: dependencies: "@babel/helper-define-polyfill-provider" "^0.3.1" +babel-preset-typescript-vue@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/babel-preset-typescript-vue/-/babel-preset-typescript-vue-1.1.1.tgz#6a617dcb0ee26f911735d5f2bbe530286b2c7c02" + integrity sha512-wXeR7Y4xCsRUEdm4t4qlpv4wnxolS6jU0c7P2E6zJRWeG1sR0e6NL7DRN0tNuUwkUt0PU8bqVo4vzoA2VEuxnw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-typescript" "^7.3.2" + "@babel/preset-typescript" "^7.3.3" + vue-template-compiler "^2.6.11" + backo2@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" @@ -2447,6 +2656,11 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -2595,6 +2809,11 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" +csstype@^2.6.8: + version "2.6.20" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda" + integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA== + dayjs@1.10.8: version "1.10.8" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.8.tgz#267df4bc6276fcb33c04a6735287e3f429abec41" @@ -2712,6 +2931,11 @@ diff@5.0.0, diff@^5.0.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -2865,7 +3089,7 @@ engine.io@~6.1.0: engine.io-parser "~5.0.3" ws "~8.2.3" -enhanced-resolve@^5.8.3: +enhanced-resolve@^5.0.0, enhanced-resolve@^5.7.0, enhanced-resolve@^5.8.3: version "5.9.3" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz#44a342c012cbc473254af5cc6ae20ebd0aae5d88" integrity sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow== @@ -4576,6 +4800,11 @@ make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: dependencies: semver "^6.0.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + make-fetch-happen@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz#53085a09e7971433e6765f7971bf63f4e05cb968" @@ -4680,7 +4909,7 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^4.0.4: +micromatch@^4.0.0, micromatch@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -4777,7 +5006,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.5: +minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== @@ -7123,6 +7352,54 @@ trim-repeated@^1.0.0: dependencies: escape-string-regexp "^1.0.2" +ts-loader@9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.3.0.tgz#980f4dbfb60e517179e15e10ed98e454b132159f" + integrity sha512-2kLLAdAD+FCKijvGKi9sS0OzoqxLCF3CxHpok7rVgCZ5UldRzH0TkbwG9XECKjBzHsAewntC5oDaI/FwKzEUog== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + +ts-node@10.7.0: + version "10.7.0" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" + integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A== + dependencies: + "@cspotcode/source-map-support" "0.7.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.0" + yn "3.1.1" + +tsconfig-paths-webpack-plugin@3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-3.5.2.tgz#01aafff59130c04a8c4ebc96a3045c43c376449a" + integrity sha512-EhnfjHbzm5IYI9YPNVIxx1moxMI4bpHD2e0zTXeDNQcwjjRaGepP7IhTHJkyDBG0CAOoxRfe7jCG630Ou+C6Pw== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.7.0" + tsconfig-paths "^3.9.0" + +tsconfig-paths@^3.9.0: + version "3.14.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" @@ -7175,6 +7452,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript@4.6.4: + version "4.6.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" + integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== + ua-parser-js@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.2.tgz#e2976c34dbfb30b15d2c300b2a53eac87c57a775" @@ -7286,6 +7568,11 @@ uuid@^3.3.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +v8-compile-cache-lib@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -7385,7 +7672,7 @@ vue-style-loader@^4.1.0: hash-sum "^1.0.2" loader-utils "^1.0.2" -vue-template-compiler@2.6.14: +vue-template-compiler@2.6.14, vue-template-compiler@^2.6.11: version "2.6.14" resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.14.tgz#a2f0e7d985670d42c9c9ee0d044fed7690f4f763" integrity sha512-ODQS1SyMbjKoO1JBJZojSw6FE4qnh9rIpUZn2EUT86FKizx9uH5z6uXiIrm4/Nb/gwxTi/o17ZDEGWAXHvtC7g== @@ -7752,6 +8039,11 @@ yeast@0.1.2: resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 8909d71f95886a5c1a01ac4f35213d8d8c02fd8e Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 1 May 2022 23:01:55 -0700 Subject: [PATCH 02/88] [ts-migrate][src] Init tsconfig.json file Co-authored-by: ts-migrate <> --- src/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tsconfig.json b/src/tsconfig.json index 7a3b2b4c7a..8766ce16cf 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../tsconfig.json", "files": ["index.d.ts"], - + "include": ["./**/*.ts"], "compilerOptions": { "allowJs": true, "checkJs": true, From 7640ef71d0ecf0fd5d88032fe157b38d6e85b979 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 1 May 2022 23:02:18 -0700 Subject: [PATCH 03/88] [ts-migrate][src] Init tsconfig.json file Co-authored-by: ts-migrate <> --- src/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tsconfig.json b/src/tsconfig.json index 8766ce16cf..c803d23823 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig.json", + // "extends": "../tsconfig.json", "files": ["index.d.ts"], "include": ["./**/*.ts"], "compilerOptions": { From 53f0ad947bbacd357d09bf38403eaf66676d69ae Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 1 May 2022 23:03:33 -0700 Subject: [PATCH 04/88] [ts-migrate][src] Init tsconfig.json file Co-authored-by: ts-migrate <> --- src/tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tsconfig.json b/src/tsconfig.json index c803d23823..906d8ac499 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,7 +1,7 @@ { - // "extends": "../tsconfig.json", + "extends": "../tsconfig.json", "files": ["index.d.ts"], - "include": ["./**/*.ts"], + "include": ["./**/*.ts", "./**/*.js"], "compilerOptions": { "allowJs": true, "checkJs": true, From facde53b0908aa4947e2a15bd9e2526be9db8307 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 1 May 2022 23:04:43 -0700 Subject: [PATCH 05/88] [ts-migrate][src] Rename files from JS/JSX to TS/TSX Co-authored-by: ts-migrate <> --- src/plugins/inputs/{action.js => action.ts} | 0 src/plugins/inputs/{away.js => away.ts} | 0 src/plugins/inputs/{ban.js => ban.ts} | 0 src/plugins/inputs/{connect.js => connect.ts} | 0 src/plugins/inputs/{ctcp.js => ctcp.ts} | 0 src/plugins/inputs/{disconnect.js => disconnect.ts} | 0 src/plugins/inputs/{ignore.js => ignore.ts} | 0 src/plugins/inputs/{index.js => index.ts} | 0 src/plugins/inputs/{invite.js => invite.ts} | 0 src/plugins/inputs/{kick.js => kick.ts} | 0 src/plugins/inputs/{kill.js => kill.ts} | 0 src/plugins/inputs/{list.js => list.ts} | 0 src/plugins/inputs/{mode.js => mode.ts} | 0 src/plugins/inputs/{msg.js => msg.ts} | 0 src/plugins/inputs/{mute.js => mute.ts} | 0 src/plugins/inputs/{nick.js => nick.ts} | 0 src/plugins/inputs/{notice.js => notice.ts} | 0 src/plugins/inputs/{part.js => part.ts} | 0 src/plugins/inputs/{quit.js => quit.ts} | 0 src/plugins/inputs/{raw.js => raw.ts} | 0 src/plugins/inputs/{rejoin.js => rejoin.ts} | 0 src/plugins/inputs/{topic.js => topic.ts} | 0 src/plugins/inputs/{whois.js => whois.ts} | 0 23 files changed, 0 insertions(+), 0 deletions(-) rename src/plugins/inputs/{action.js => action.ts} (100%) rename src/plugins/inputs/{away.js => away.ts} (100%) rename src/plugins/inputs/{ban.js => ban.ts} (100%) rename src/plugins/inputs/{connect.js => connect.ts} (100%) rename src/plugins/inputs/{ctcp.js => ctcp.ts} (100%) rename src/plugins/inputs/{disconnect.js => disconnect.ts} (100%) rename src/plugins/inputs/{ignore.js => ignore.ts} (100%) rename src/plugins/inputs/{index.js => index.ts} (100%) rename src/plugins/inputs/{invite.js => invite.ts} (100%) rename src/plugins/inputs/{kick.js => kick.ts} (100%) rename src/plugins/inputs/{kill.js => kill.ts} (100%) rename src/plugins/inputs/{list.js => list.ts} (100%) rename src/plugins/inputs/{mode.js => mode.ts} (100%) rename src/plugins/inputs/{msg.js => msg.ts} (100%) rename src/plugins/inputs/{mute.js => mute.ts} (100%) rename src/plugins/inputs/{nick.js => nick.ts} (100%) rename src/plugins/inputs/{notice.js => notice.ts} (100%) rename src/plugins/inputs/{part.js => part.ts} (100%) rename src/plugins/inputs/{quit.js => quit.ts} (100%) rename src/plugins/inputs/{raw.js => raw.ts} (100%) rename src/plugins/inputs/{rejoin.js => rejoin.ts} (100%) rename src/plugins/inputs/{topic.js => topic.ts} (100%) rename src/plugins/inputs/{whois.js => whois.ts} (100%) diff --git a/src/plugins/inputs/action.js b/src/plugins/inputs/action.ts similarity index 100% rename from src/plugins/inputs/action.js rename to src/plugins/inputs/action.ts diff --git a/src/plugins/inputs/away.js b/src/plugins/inputs/away.ts similarity index 100% rename from src/plugins/inputs/away.js rename to src/plugins/inputs/away.ts diff --git a/src/plugins/inputs/ban.js b/src/plugins/inputs/ban.ts similarity index 100% rename from src/plugins/inputs/ban.js rename to src/plugins/inputs/ban.ts diff --git a/src/plugins/inputs/connect.js b/src/plugins/inputs/connect.ts similarity index 100% rename from src/plugins/inputs/connect.js rename to src/plugins/inputs/connect.ts diff --git a/src/plugins/inputs/ctcp.js b/src/plugins/inputs/ctcp.ts similarity index 100% rename from src/plugins/inputs/ctcp.js rename to src/plugins/inputs/ctcp.ts diff --git a/src/plugins/inputs/disconnect.js b/src/plugins/inputs/disconnect.ts similarity index 100% rename from src/plugins/inputs/disconnect.js rename to src/plugins/inputs/disconnect.ts diff --git a/src/plugins/inputs/ignore.js b/src/plugins/inputs/ignore.ts similarity index 100% rename from src/plugins/inputs/ignore.js rename to src/plugins/inputs/ignore.ts diff --git a/src/plugins/inputs/index.js b/src/plugins/inputs/index.ts similarity index 100% rename from src/plugins/inputs/index.js rename to src/plugins/inputs/index.ts diff --git a/src/plugins/inputs/invite.js b/src/plugins/inputs/invite.ts similarity index 100% rename from src/plugins/inputs/invite.js rename to src/plugins/inputs/invite.ts diff --git a/src/plugins/inputs/kick.js b/src/plugins/inputs/kick.ts similarity index 100% rename from src/plugins/inputs/kick.js rename to src/plugins/inputs/kick.ts diff --git a/src/plugins/inputs/kill.js b/src/plugins/inputs/kill.ts similarity index 100% rename from src/plugins/inputs/kill.js rename to src/plugins/inputs/kill.ts diff --git a/src/plugins/inputs/list.js b/src/plugins/inputs/list.ts similarity index 100% rename from src/plugins/inputs/list.js rename to src/plugins/inputs/list.ts diff --git a/src/plugins/inputs/mode.js b/src/plugins/inputs/mode.ts similarity index 100% rename from src/plugins/inputs/mode.js rename to src/plugins/inputs/mode.ts diff --git a/src/plugins/inputs/msg.js b/src/plugins/inputs/msg.ts similarity index 100% rename from src/plugins/inputs/msg.js rename to src/plugins/inputs/msg.ts diff --git a/src/plugins/inputs/mute.js b/src/plugins/inputs/mute.ts similarity index 100% rename from src/plugins/inputs/mute.js rename to src/plugins/inputs/mute.ts diff --git a/src/plugins/inputs/nick.js b/src/plugins/inputs/nick.ts similarity index 100% rename from src/plugins/inputs/nick.js rename to src/plugins/inputs/nick.ts diff --git a/src/plugins/inputs/notice.js b/src/plugins/inputs/notice.ts similarity index 100% rename from src/plugins/inputs/notice.js rename to src/plugins/inputs/notice.ts diff --git a/src/plugins/inputs/part.js b/src/plugins/inputs/part.ts similarity index 100% rename from src/plugins/inputs/part.js rename to src/plugins/inputs/part.ts diff --git a/src/plugins/inputs/quit.js b/src/plugins/inputs/quit.ts similarity index 100% rename from src/plugins/inputs/quit.js rename to src/plugins/inputs/quit.ts diff --git a/src/plugins/inputs/raw.js b/src/plugins/inputs/raw.ts similarity index 100% rename from src/plugins/inputs/raw.js rename to src/plugins/inputs/raw.ts diff --git a/src/plugins/inputs/rejoin.js b/src/plugins/inputs/rejoin.ts similarity index 100% rename from src/plugins/inputs/rejoin.js rename to src/plugins/inputs/rejoin.ts diff --git a/src/plugins/inputs/topic.js b/src/plugins/inputs/topic.ts similarity index 100% rename from src/plugins/inputs/topic.js rename to src/plugins/inputs/topic.ts diff --git a/src/plugins/inputs/whois.js b/src/plugins/inputs/whois.ts similarity index 100% rename from src/plugins/inputs/whois.js rename to src/plugins/inputs/whois.ts From 70fae2ee3f038f95334cec9435f95cdc91699d75 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 1 May 2022 23:26:24 -0700 Subject: [PATCH 06/88] [ts-migrate][src] Init tsconfig.json file Co-authored-by: ts-migrate <> --- src/models/chan.ts | 4 +-- src/models/msg.ts | 1 + src/models/network.ts | 4 +-- src/plugins/inputs/action.ts | 18 ++++++++---- src/plugins/inputs/away.ts | 12 ++++++-- src/plugins/inputs/ban.ts | 19 ++++++++---- src/plugins/inputs/connect.ts | 18 ++++++++---- src/plugins/inputs/ctcp.ts | 21 +++++++++---- src/plugins/inputs/disconnect.ts | 15 ++++++++-- src/plugins/inputs/ignore.ts | 44 ++++++++++++++++++---------- src/plugins/inputs/index.ts | 13 ++++++-- src/plugins/inputs/invite.ts | 18 ++++++++---- src/plugins/inputs/kick.ts | 18 ++++++++---- src/plugins/inputs/kill.ts | 12 ++++++-- src/plugins/inputs/list.ts | 12 ++++++-- src/plugins/inputs/mode.ts | 19 ++++++++---- src/plugins/inputs/msg.ts | 23 ++++++++++----- src/plugins/inputs/mute.ts | 18 +++++++++--- src/plugins/inputs/nick.ts | 23 ++++++++++----- src/plugins/inputs/notice.ts | 12 ++++++-- src/plugins/inputs/part.ts | 25 +++++++++++----- src/plugins/inputs/quit.ts | 18 ++++++++---- src/plugins/inputs/raw.ts | 12 ++++++-- src/plugins/inputs/rejoin.ts | 18 ++++++++---- src/plugins/inputs/topic.ts | 18 ++++++++---- src/plugins/inputs/whois.ts | 12 ++++++-- src/types/models/network.d.ts | 6 ++++ src/types/modules/irc-framework.d.ts | 16 +++++----- 28 files changed, 323 insertions(+), 126 deletions(-) diff --git a/src/models/chan.ts b/src/models/chan.ts index 695211628f..cc212926a1 100644 --- a/src/models/chan.ts +++ b/src/models/chan.ts @@ -46,7 +46,7 @@ class Chan { this.dereferencePreviews(this.messages); } - pushMessage(client: Client, msg: Msg, increasesUnread: boolean) { + pushMessage(client: Client, msg: Msg, increasesUnread = false) { const chan = this.id; const obj = {chan, msg} as any; @@ -164,7 +164,7 @@ class Chan { * If true, channel is assumed active. * @param {int} lastMessage - Last message id seen by active client to avoid sending duplicates. */ - getFilteredClone(lastActiveChannel: number | boolean, lastMessage: number): FilteredChannel { + getFilteredClone(lastActiveChannel: number | boolean, lastMessage?: number): FilteredChannel { return Object.keys(this).reduce((newChannel, prop) => { if (prop === "users") { // Do not send users, client requests updated user list whenever needed diff --git a/src/models/msg.ts b/src/models/msg.ts index 5c22816ff2..d036f63b50 100644 --- a/src/models/msg.ts +++ b/src/models/msg.ts @@ -21,6 +21,7 @@ class Msg { showInActive: boolean; new_ident: string; new_host: string; + ctcpMessage: string; constructor(attr: Partial) { // Some properties need to be copied in the Msg object instead of referenced diff --git a/src/models/network.ts b/src/models/network.ts index b0cc07e2f7..b5e576694e 100644 --- a/src/models/network.ts +++ b/src/models/network.ts @@ -12,7 +12,7 @@ import STSPolicies from "../plugins/sts"; import ClientCertificate from "../plugins/clientCertificate"; import {Channel, ChanType} from "src/types/models/channel"; import Client from "src/client"; -import {NetworkStatus} from "src/types/models/network"; +import {IgnoreList, NetworkStatus} from "src/types/models/network"; import {MessageType} from "src/types/models/message"; import {WebIRC} from "src/types/config"; @@ -81,7 +81,7 @@ class Network { }; chanCache: Chan[]; - ignoreList: string[]; + ignoreList: IgnoreList; keepNick?: string; status: NetworkStatus; diff --git a/src/plugins/inputs/action.ts b/src/plugins/inputs/action.ts index 22ae3c67ad..7d6e133e41 100644 --- a/src/plugins/inputs/action.ts +++ b/src/plugins/inputs/action.ts @@ -1,16 +1,17 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Chan from "../../models/chan"; +import Msg from "../../models/msg"; -exports.commands = ["slap", "me"]; - -exports.input = function ({irc}, chan, cmd, args) { +const commands = ["slap", "me"]; +const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) { if (chan.type !== ChanType.CHANNEL && chan.type !== ChanType.QUERY) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `${cmd} command can only be used in channels and queries.`, }) ); @@ -46,3 +47,8 @@ exports.input = function ({irc}, chan, cmd, args) { return true; }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/away.ts b/src/plugins/inputs/away.ts index acafdc49ea..a87c4aef7d 100644 --- a/src/plugins/inputs/away.ts +++ b/src/plugins/inputs/away.ts @@ -1,8 +1,11 @@ "use strict"; -exports.commands = ["away", "back"]; +import Network from "src/models/network"; +import {Channel} from "src/types/models/channel"; -exports.input = function (network, chan, cmd, args) { +const commands = ["away", "back"]; + +const input = function (network: Network, chan: Channel, cmd: string, args: string[]) { let reason = ""; if (cmd === "away") { @@ -18,3 +21,8 @@ exports.input = function (network, chan, cmd, args) { this.save(); }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/ban.ts b/src/plugins/inputs/ban.ts index 082973fa09..db91068e21 100644 --- a/src/plugins/inputs/ban.ts +++ b/src/plugins/inputs/ban.ts @@ -1,16 +1,18 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +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"; -exports.commands = ["ban", "unban", "banlist", "kickban"]; +const commands = ["ban", "unban", "banlist", "kickban"]; -exports.input = function ({irc}, chan, cmd, args) { +const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) { if (chan.type !== ChanType.CHANNEL) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `${cmd} command can only be used in channels.`, }) ); @@ -23,7 +25,7 @@ exports.input = function ({irc}, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `Usage: /${cmd} `, }) ); @@ -47,3 +49,8 @@ exports.input = function ({irc}, chan, cmd, args) { break; } }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/connect.ts b/src/plugins/inputs/connect.ts index da98f5c0ec..f62fe57a19 100644 --- a/src/plugins/inputs/connect.ts +++ b/src/plugins/inputs/connect.ts @@ -1,11 +1,14 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {Channel} from "src/types/models/channel"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -exports.commands = ["connect", "server"]; -exports.allowDisconnected = true; +const commands = ["connect", "server"]; +const allowDisconnected = true; -exports.input = function (network, chan, cmd, args) { +const input = function (network: Network, chan: Channel, cmd: string, args: string[]) { if (args.length === 0) { network.userDisconnected = false; this.save(); @@ -20,7 +23,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "You are already connected.", }) ); @@ -44,3 +47,8 @@ exports.input = function (network, chan, cmd, args) { return true; }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/ctcp.ts b/src/plugins/inputs/ctcp.ts index ed263a9a5d..751318940d 100644 --- a/src/plugins/inputs/ctcp.ts +++ b/src/plugins/inputs/ctcp.ts @@ -1,15 +1,18 @@ "use strict"; -const Msg = require("../../models/msg"); +import Chan from "src/models/chan"; +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -exports.commands = ["ctcp"]; +const commands = ["ctcp"]; -exports.input = function ({irc}, chan, cmd, args) { +const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) { if (args.length < 2) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "Usage: /ctcp ", }) ); @@ -19,11 +22,17 @@ exports.input = function ({irc}, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.CTCP_REQUEST, + type: MessageType.CTCP_REQUEST, ctcpMessage: `"${args.slice(1).join(" ")}" to ${args[0]}`, from: chan.getUser(irc.user.nick), }) ); - irc.ctcpRequest(...args); + // TODO: check. Was ctcpRequest(...args) + irc.ctcpRequest(args.shift(), args.shift(), ...args); +}; + +export default { + commands, + input, }; diff --git a/src/plugins/inputs/disconnect.ts b/src/plugins/inputs/disconnect.ts index 7b94b92542..84e2cfeadb 100644 --- a/src/plugins/inputs/disconnect.ts +++ b/src/plugins/inputs/disconnect.ts @@ -1,9 +1,12 @@ "use strict"; -exports.commands = ["disconnect"]; -exports.allowDisconnected = true; +import Chan from "src/models/chan"; +import Network from "src/models/network"; -exports.input = function (network, chan, cmd, args) { +const commands = ["disconnect"]; +const allowDisconnected = true; + +const input = function (network: Network, chan: Chan, cmd: string, args: string[]) { const quitMessage = args[0] ? args.join(" ") : null; network.quit(quitMessage); @@ -11,3 +14,9 @@ exports.input = function (network, chan, cmd, args) { this.save(); }; + +export default { + commands, + input, + allowDisconnected, +}; diff --git a/src/plugins/inputs/ignore.ts b/src/plugins/inputs/ignore.ts index b5326b9737..325711b8d4 100644 --- a/src/plugins/inputs/ignore.ts +++ b/src/plugins/inputs/ignore.ts @@ -1,21 +1,26 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); -const Helper = require("../../helper"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; -exports.commands = ["ignore", "unignore", "ignorelist"]; +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 {SpecialChanType} from "src/types/models/channel"; -exports.input = function (network, chan, cmd, args) { +const commands = ["ignore", "unignore", "ignorelist"]; + +const input = function (network: Network, chan: Chan, cmd: string, args: string[]) { const client = this; - let target; - let hostmask; + let target: string; + let hostmask: IgnoreListItem; if (cmd !== "ignorelist" && (args.length === 0 || args[0].trim().length === 0)) { chan.pushMessage( client, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `Usage: /${cmd} [!ident][@host]`, }) ); @@ -26,7 +31,7 @@ exports.input = function (network, chan, cmd, args) { if (cmd !== "ignorelist") { // Trim to remove any spaces from the hostmask target = args[0].trim(); - hostmask = Helper.parseHostmask(target); + hostmask = Helper.parseHostmask(target) as IgnoreListItem; } switch (cmd) { @@ -36,7 +41,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( client, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "You can't ignore yourself", }) ); @@ -52,7 +57,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( client, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `\u0002${hostmask.nick}!${hostmask.ident}@${hostmask.hostname}\u000f added to ignorelist`, }) ); @@ -60,7 +65,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( client, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "The specified user/hostmask is already ignored", }) ); @@ -83,7 +88,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( client, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `Successfully removed \u0002${hostmask.nick}!${hostmask.ident}@${hostmask.hostname}\u000f from ignorelist`, }) ); @@ -91,7 +96,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( client, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "The specified user/hostmask is not ignored", }) ); @@ -105,7 +110,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( client, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "Ignorelist is empty", }) ); @@ -120,7 +125,7 @@ exports.input = function (network, chan, cmd, args) { if (typeof newChan === "undefined") { newChan = client.createChannel({ type: ChanType.SPECIAL, - special: Chan.SpecialType.IGNORELIST, + special: SpecialChanType.IGNORELIST, name: chanName, data: ignored, }); @@ -130,6 +135,8 @@ exports.input = function (network, chan, cmd, args) { index: network.addChannel(newChan), }); } else { + // TODO: add type for this chan/event + //@ts-expect-error newChan.data = ignored; client.emit("msg:special", { @@ -142,3 +149,8 @@ exports.input = function (network, chan, cmd, args) { break; } }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/index.ts b/src/plugins/inputs/index.ts index 50a2dc03df..30a344f43d 100644 --- a/src/plugins/inputs/index.ts +++ b/src/plugins/inputs/index.ts @@ -1,3 +1,6 @@ +import Chan from "src/models/chan"; +import Network from "src/models/network"; + const clientSideCommands = ["/collapse", "/expand", "/search"]; const passThroughCommands = [ @@ -37,8 +40,12 @@ const userInputs = [ "whois", "mute", ].reduce(function (plugins, name) { - const plugin = require(`./${name}`); - plugin.commands.forEach((command) => plugins.set(command, plugin)); + 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)); return plugins; }, new Map()); @@ -57,7 +64,7 @@ const addPluginCommand = (packageInfo, command, func) => { pluginCommands.set(command, func); }; -module.exports = { +export default { addPluginCommand, getCommands, pluginCommands, diff --git a/src/plugins/inputs/invite.ts b/src/plugins/inputs/invite.ts index b8a6842aa9..9483a23c34 100644 --- a/src/plugins/inputs/invite.ts +++ b/src/plugins/inputs/invite.ts @@ -1,11 +1,14 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +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"; -exports.commands = ["invite", "invitelist"]; +const commands = ["invite", "invitelist"]; -exports.input = function ({irc}, chan, cmd, args) { +const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) { if (cmd === "invitelist") { irc.inviteList(chan.name); return; @@ -19,9 +22,14 @@ exports.input = function ({irc}, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `${cmd} command can only be used in channels or by specifying a target.`, }) ); } }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/kick.ts b/src/plugins/inputs/kick.ts index 201bb8fc26..38f72955af 100644 --- a/src/plugins/inputs/kick.ts +++ b/src/plugins/inputs/kick.ts @@ -1,16 +1,19 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +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"; -exports.commands = ["kick"]; +const commands = ["kick"]; -exports.input = function ({irc}, chan, cmd, args) { +const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) { if (chan.type !== ChanType.CHANNEL) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `${cmd} command can only be used in channels.`, }) ); @@ -24,3 +27,8 @@ exports.input = function ({irc}, chan, cmd, args) { return true; }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/kill.ts b/src/plugins/inputs/kill.ts index 3842394807..dcce90087b 100644 --- a/src/plugins/inputs/kill.ts +++ b/src/plugins/inputs/kill.ts @@ -1,11 +1,19 @@ "use strict"; -exports.commands = ["kill"]; +import Chan from "src/models/chan"; +import Network from "src/models/network"; -exports.input = function ({irc}, chan, cmd, args) { +const commands = ["kill"]; + +const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) { if (args.length !== 0) { irc.raw("KILL", args[0], args.slice(1).join(" ")); } return true; }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/list.ts b/src/plugins/inputs/list.ts index 220c0069cb..4a3430f841 100644 --- a/src/plugins/inputs/list.ts +++ b/src/plugins/inputs/list.ts @@ -1,9 +1,17 @@ "use strict"; -exports.commands = ["list"]; +import Chan from "src/models/chan"; +import Network from "src/models/network"; -exports.input = function (network, chan, cmd, args) { +const commands = ["list"]; + +const input = function (network: Network, chan: Chan, cmd: string, args: string[]) { network.chanCache = []; network.irc.list(...args); return true; }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/mode.ts b/src/plugins/inputs/mode.ts index baa8c420eb..bec6cc6c27 100644 --- a/src/plugins/inputs/mode.ts +++ b/src/plugins/inputs/mode.ts @@ -1,11 +1,13 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +import {ChanType} from "src/types/models/channel"; +import {MessageType} from "src/types/models/message"; +import Chan from "../../models/chan"; +import Msg from "../../models/msg"; -exports.commands = ["mode", "umode", "op", "deop", "hop", "dehop", "voice", "devoice"]; +const commands = ["mode", "umode", "op", "deop", "hop", "dehop", "voice", "devoice"]; -exports.input = function ({irc, nick}, chan, cmd, args) { +const input = function ({irc, nick}, chan, cmd, args) { if (cmd === "umode") { irc.raw("MODE", nick, ...args); @@ -15,7 +17,7 @@ exports.input = function ({irc, nick}, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `${cmd} command can only be used in channels.`, }) ); @@ -29,7 +31,7 @@ exports.input = function ({irc, nick}, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `Usage: /${cmd} [...nick]`, }) ); @@ -65,3 +67,8 @@ exports.input = function ({irc, nick}, chan, cmd, args) { irc.raw("MODE", ...args); }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/msg.ts b/src/plugins/inputs/msg.ts index b6f437ea7f..8521e67f61 100644 --- a/src/plugins/inputs/msg.ts +++ b/src/plugins/inputs/msg.ts @@ -1,10 +1,12 @@ "use strict"; -const {ChanType} = require("src/types/models/channel"); -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +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"; -exports.commands = ["query", "msg", "say"]; +const commands = ["query", "msg", "say"]; function getTarget(cmd, args, chan) { switch (cmd) { @@ -16,7 +18,7 @@ function getTarget(cmd, args, chan) { } } -exports.input = function (network, chan, cmd, args) { +const input = function (network: Network, chan: Chan, cmd: string, args: string[]) { let targetName = getTarget(cmd, args, chan); if (cmd === "query") { @@ -24,7 +26,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "You cannot open a query window without an argument.", }) ); @@ -43,7 +45,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "You can not open query windows for channels, use /join instead.", }) ); @@ -55,7 +57,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "You can not open query windows for names starting with a user prefix.", }) ); @@ -116,3 +118,8 @@ exports.input = function (network, chan, cmd, args) { return true; }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/mute.ts b/src/plugins/inputs/mute.ts index dda1758239..5c54b90148 100644 --- a/src/plugins/inputs/mute.ts +++ b/src/plugins/inputs/mute.ts @@ -1,7 +1,11 @@ "use strict"; -const Msg = require("../../models/msg"); +import Chan from "src/models/chan"; +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -exports.commands = ["mute", "unmute"]; +const commands = ["mute", "unmute"]; +const allowDisconnected = true; function args_to_channels(network, args) { const targets = []; @@ -29,7 +33,7 @@ function change_mute_state(client, target, valueToSet) { }); } -exports.input = function (network, chan, cmd, args) { +const input = function (network: Network, chan: Chan, cmd: string, args: string[]) { const valueToSet = cmd === "mute" ? true : false; const client = this; @@ -46,7 +50,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( client, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `No open ${ missing.length === 1 ? "channel or user" : "channels or users" } found for ${missing.join(",")}`, @@ -59,3 +63,9 @@ exports.input = function (network, chan, cmd, args) { change_mute_state(client, target, valueToSet); } }; + +export default { + commands, + input, + allowDisconnected, +}; diff --git a/src/plugins/inputs/nick.ts b/src/plugins/inputs/nick.ts index 40e180eb6e..7914985e93 100644 --- a/src/plugins/inputs/nick.ts +++ b/src/plugins/inputs/nick.ts @@ -1,16 +1,19 @@ "use strict"; -const Msg = require("../../models/msg"); +import Chan from "src/models/chan"; +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -exports.commands = ["nick"]; -exports.allowDisconnected = true; +const commands = ["nick"]; +const allowDisconnected = true; -exports.input = function (network, chan, cmd, args) { +const input = function (network: Network, chan: Chan, cmd: string, args: string[]) { if (args.length === 0) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "Usage: /nick ", }) ); @@ -21,7 +24,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "Nicknames may not contain spaces.", }) ); @@ -34,7 +37,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "Nicknames may not be this long.", }) ); @@ -65,3 +68,9 @@ exports.input = function (network, chan, cmd, args) { this.save(); }; + +export default { + commands, + input, + allowDisconnected, +}; diff --git a/src/plugins/inputs/notice.ts b/src/plugins/inputs/notice.ts index 6569719fdf..206d2d02a7 100644 --- a/src/plugins/inputs/notice.ts +++ b/src/plugins/inputs/notice.ts @@ -1,8 +1,11 @@ "use strict"; -exports.commands = ["notice"]; +import Chan from "src/models/chan"; +import Network from "src/models/network"; -exports.input = function (network, chan, cmd, args) { +const commands = ["notice"]; + +const input = function (network: Network, chan: Chan, cmd: string, args: string[]) { if (!args[1]) { return; } @@ -37,3 +40,8 @@ exports.input = function (network, chan, cmd, args) { return true; }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/part.ts b/src/plugins/inputs/part.ts index e3dda18cdf..4010609949 100644 --- a/src/plugins/inputs/part.ts +++ b/src/plugins/inputs/part.ts @@ -1,13 +1,16 @@ "use strict"; -const Msg = require("../../models/msg"); -const Chan = require("../../models/chan"); -const Config = require("../../config"); +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"; -exports.commands = ["close", "leave", "part"]; -exports.allowDisconnected = true; +const commands = ["close", "leave", "part"]; +const allowDisconnected = true; -exports.input = function (network, chan, cmd, args) { +const input = function (network: Network, chan: Chan, cmd: string, args: string[]) { let target = chan; if (args.length > 0) { @@ -24,7 +27,7 @@ exports.input = function (network, chan, cmd, args) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "You can not part from networks, use /quit instead.", }) ); @@ -35,7 +38,7 @@ exports.input = function (network, chan, cmd, args) { // Otherwise send part to the server and wait for response if ( target.type !== ChanType.CHANNEL || - target.state === Chan.State.PARTED || + target.state === ChanState.PARTED || !network.irc || !network.irc.connection || !network.irc.connection.connected @@ -48,3 +51,9 @@ exports.input = function (network, chan, cmd, args) { return true; }; + +export default { + commands, + input, + allowDisconnected, +}; diff --git a/src/plugins/inputs/quit.ts b/src/plugins/inputs/quit.ts index 0d10ced181..b25efc2b38 100644 --- a/src/plugins/inputs/quit.ts +++ b/src/plugins/inputs/quit.ts @@ -1,12 +1,14 @@ "use strict"; -const _ = require("lodash"); -const ClientCertificate = require("../clientCertificate"); +import _ from "lodash"; +import Chan from "src/models/chan"; +import Network from "src/models/network"; +import ClientCertificate from "../clientCertificate"; -exports.commands = ["quit"]; -exports.allowDisconnected = true; +const commands = ["quit"]; +const allowDisconnected = true; -exports.input = function (network, chan, cmd, args) { +const input = function (network: Network, chan: Chan, cmd: string, args: string[]) { const client = this; client.networks = _.without(client.networks, network); @@ -23,3 +25,9 @@ exports.input = function (network, chan, cmd, args) { return true; }; + +export default { + commands, + input, + allowDisconnected, +}; diff --git a/src/plugins/inputs/raw.ts b/src/plugins/inputs/raw.ts index 816cfaae73..80295e6a9f 100644 --- a/src/plugins/inputs/raw.ts +++ b/src/plugins/inputs/raw.ts @@ -1,11 +1,19 @@ "use strict"; -exports.commands = ["raw", "send", "quote"]; +import Chan from "src/models/chan"; +import Network from "src/models/network"; -exports.input = function ({irc}, chan, cmd, args) { +const commands = ["raw", "send", "quote"]; + +const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) { if (args.length !== 0) { irc.connection.write(args.join(" ")); } return true; }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/rejoin.ts b/src/plugins/inputs/rejoin.ts index 9541ecba63..ee968e068f 100644 --- a/src/plugins/inputs/rejoin.ts +++ b/src/plugins/inputs/rejoin.ts @@ -1,16 +1,19 @@ "use strict"; -const Msg = require("../../models/msg"); -const Chan = require("../../models/chan"); +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"; -exports.commands = ["cycle", "rejoin"]; +const commands = ["cycle", "rejoin"]; -exports.input = function ({irc}, chan) { +const input = function ({irc}: Network, chan: Chan) { if (chan.type !== ChanType.CHANNEL) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "You can only rejoin channels.", }) ); @@ -22,3 +25,8 @@ exports.input = function ({irc}, chan) { return true; }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/topic.ts b/src/plugins/inputs/topic.ts index 0bc9a5b474..3cb4d852b2 100644 --- a/src/plugins/inputs/topic.ts +++ b/src/plugins/inputs/topic.ts @@ -1,16 +1,19 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +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"; -exports.commands = ["topic"]; +const commands = ["topic"]; -exports.input = function ({irc}, chan, cmd, args) { +const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) { if (chan.type !== ChanType.CHANNEL) { chan.pushMessage( this, new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: `${cmd} command can only be used in channels.`, }) ); @@ -21,3 +24,8 @@ exports.input = function ({irc}, chan, cmd, args) { irc.setTopic(chan.name, args.join(" ")); return true; }; + +export default { + commands, + input, +}; diff --git a/src/plugins/inputs/whois.ts b/src/plugins/inputs/whois.ts index 480a72dc7f..a6afeeeb09 100644 --- a/src/plugins/inputs/whois.ts +++ b/src/plugins/inputs/whois.ts @@ -1,8 +1,11 @@ "use strict"; -exports.commands = ["whois"]; +import Chan from "src/models/chan"; +import Network from "src/models/network"; -exports.input = function ({irc}, chan, cmd, args) { +const commands = ["whois"]; + +const input = function ({irc}: Network, chan: Chan, cmd: string, args: string[]) { if (args.length === 1) { // This queries server of the other user and not of the current user, which // does not know idle time. @@ -13,3 +16,8 @@ exports.input = function ({irc}, chan, cmd, args) { irc.raw(`${cmd} ${args.join(" ")}`); } }; + +export default { + commands, + input, +}; diff --git a/src/types/models/network.d.ts b/src/types/models/network.d.ts index 57f0010f8f..084819286c 100644 --- a/src/types/models/network.d.ts +++ b/src/types/models/network.d.ts @@ -6,3 +6,9 @@ export type NetworkStatus = { connected: boolean; secure: boolean; }; + +type IgnoreListItem = Hostmask & { + when?: number; +}; + +type IgnoreList = IgnoreListItem[]; diff --git a/src/types/modules/irc-framework.d.ts b/src/types/modules/irc-framework.d.ts index f713a9691e..a69eb6617b 100644 --- a/src/types/modules/irc-framework.d.ts +++ b/src/types/modules/irc-framework.d.ts @@ -1,5 +1,6 @@ // https://raw.githubusercontent.com/eternagame/HTML-Chat/vue-rewrite/src/app/types/modules/irc-framework/irc-framework.d.ts - +// TODO: Fix this +type Event = any; declare module "irc-framework" { import {EventEmitter} from "eventemitter3"; // import { DuplexStream } from 'stream'; @@ -15,6 +16,7 @@ declare module "irc-framework" { registered: boolean; transport: any; + write: (data: string) => void; }; export class Client extends EventEmitter { @@ -98,7 +100,7 @@ declare module "irc-framework" { mode(channel: string, mode: string, extra_args?: string[]): void; - inviteList(channel: string, cb: (e: Event) => any): void; + inviteList(channel: string, cb?: (e: Event) => any): void; // TODO: typeof e? invite(channel: string, nick: string): void; @@ -107,7 +109,7 @@ declare module "irc-framework" { removeInvite(channel: string, mask: string): void; - banlist(channel: string, cb: (e: Event) => any): void; + banlist(channel: string, cb?: (e: Event) => any): void; ban(channel: string, mask: string): void; @@ -115,13 +117,13 @@ declare module "irc-framework" { setTopic(channel: string, newTopic: string): void; - ctcpRequest(target: string, type: string /* , ...params: Array */): void; + ctcpRequest(target: string, type: string, ...params: Array): void; - ctcpResponse(target: string, type: string /* , params: Array */): void; + ctcpResponse(target: string, type: string, ...params: Array): void; action(target: string, message: string): string[]; - whowas(target: string, cb: (event: Event) => any): void; + whowas(target: string, cb?: (event: Event) => any): void; whois(nick: string, cb: (event: any) => void): void; @@ -132,7 +134,7 @@ declare module "irc-framework" { */ who(target: string, cb: (event: any) => void): void; - list(/* params: Array */): void; + list(...params: Array): void; channel(channel_name: string): IrcChannel; From e2362c836ace1f4e481aa28b779b3a7a5a0da705 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 1 May 2022 23:26:30 -0700 Subject: [PATCH 07/88] [ts-migrate][src] Rename files from JS/JSX to TS/TSX Co-authored-by: ts-migrate <> --- src/plugins/irc-events/{away.js => away.ts} | 0 src/plugins/irc-events/{cap.js => cap.ts} | 0 src/plugins/irc-events/{chghost.js => chghost.ts} | 0 src/plugins/irc-events/{ctcp.js => ctcp.ts} | 0 src/plugins/irc-events/{error.js => error.ts} | 0 src/plugins/irc-events/{help.js => help.ts} | 0 src/plugins/irc-events/{info.js => info.ts} | 0 src/plugins/irc-events/{invite.js => invite.ts} | 0 src/plugins/irc-events/{join.js => join.ts} | 0 src/plugins/irc-events/{kick.js => kick.ts} | 0 src/plugins/irc-events/{link.js => link.ts} | 0 src/plugins/irc-events/{list.js => list.ts} | 0 src/plugins/irc-events/{message.js => message.ts} | 0 src/plugins/irc-events/{mode.js => mode.ts} | 0 src/plugins/irc-events/{modelist.js => modelist.ts} | 0 src/plugins/irc-events/{motd.js => motd.ts} | 0 src/plugins/irc-events/{names.js => names.ts} | 0 src/plugins/irc-events/{nick.js => nick.ts} | 0 src/plugins/irc-events/{part.js => part.ts} | 0 src/plugins/irc-events/{quit.js => quit.ts} | 0 src/plugins/irc-events/{sasl.js => sasl.ts} | 0 src/plugins/irc-events/{topic.js => topic.ts} | 0 src/plugins/irc-events/{unhandled.js => unhandled.ts} | 0 src/plugins/irc-events/{welcome.js => welcome.ts} | 0 src/plugins/irc-events/{whois.js => whois.ts} | 0 25 files changed, 0 insertions(+), 0 deletions(-) rename src/plugins/irc-events/{away.js => away.ts} (100%) rename src/plugins/irc-events/{cap.js => cap.ts} (100%) rename src/plugins/irc-events/{chghost.js => chghost.ts} (100%) rename src/plugins/irc-events/{ctcp.js => ctcp.ts} (100%) rename src/plugins/irc-events/{error.js => error.ts} (100%) rename src/plugins/irc-events/{help.js => help.ts} (100%) rename src/plugins/irc-events/{info.js => info.ts} (100%) rename src/plugins/irc-events/{invite.js => invite.ts} (100%) rename src/plugins/irc-events/{join.js => join.ts} (100%) rename src/plugins/irc-events/{kick.js => kick.ts} (100%) rename src/plugins/irc-events/{link.js => link.ts} (100%) rename src/plugins/irc-events/{list.js => list.ts} (100%) rename src/plugins/irc-events/{message.js => message.ts} (100%) rename src/plugins/irc-events/{mode.js => mode.ts} (100%) rename src/plugins/irc-events/{modelist.js => modelist.ts} (100%) rename src/plugins/irc-events/{motd.js => motd.ts} (100%) rename src/plugins/irc-events/{names.js => names.ts} (100%) rename src/plugins/irc-events/{nick.js => nick.ts} (100%) rename src/plugins/irc-events/{part.js => part.ts} (100%) rename src/plugins/irc-events/{quit.js => quit.ts} (100%) rename src/plugins/irc-events/{sasl.js => sasl.ts} (100%) rename src/plugins/irc-events/{topic.js => topic.ts} (100%) rename src/plugins/irc-events/{unhandled.js => unhandled.ts} (100%) rename src/plugins/irc-events/{welcome.js => welcome.ts} (100%) rename src/plugins/irc-events/{whois.js => whois.ts} (100%) diff --git a/src/plugins/irc-events/away.js b/src/plugins/irc-events/away.ts similarity index 100% rename from src/plugins/irc-events/away.js rename to src/plugins/irc-events/away.ts diff --git a/src/plugins/irc-events/cap.js b/src/plugins/irc-events/cap.ts similarity index 100% rename from src/plugins/irc-events/cap.js rename to src/plugins/irc-events/cap.ts diff --git a/src/plugins/irc-events/chghost.js b/src/plugins/irc-events/chghost.ts similarity index 100% rename from src/plugins/irc-events/chghost.js rename to src/plugins/irc-events/chghost.ts diff --git a/src/plugins/irc-events/ctcp.js b/src/plugins/irc-events/ctcp.ts similarity index 100% rename from src/plugins/irc-events/ctcp.js rename to src/plugins/irc-events/ctcp.ts diff --git a/src/plugins/irc-events/error.js b/src/plugins/irc-events/error.ts similarity index 100% rename from src/plugins/irc-events/error.js rename to src/plugins/irc-events/error.ts diff --git a/src/plugins/irc-events/help.js b/src/plugins/irc-events/help.ts similarity index 100% rename from src/plugins/irc-events/help.js rename to src/plugins/irc-events/help.ts diff --git a/src/plugins/irc-events/info.js b/src/plugins/irc-events/info.ts similarity index 100% rename from src/plugins/irc-events/info.js rename to src/plugins/irc-events/info.ts diff --git a/src/plugins/irc-events/invite.js b/src/plugins/irc-events/invite.ts similarity index 100% rename from src/plugins/irc-events/invite.js rename to src/plugins/irc-events/invite.ts diff --git a/src/plugins/irc-events/join.js b/src/plugins/irc-events/join.ts similarity index 100% rename from src/plugins/irc-events/join.js rename to src/plugins/irc-events/join.ts diff --git a/src/plugins/irc-events/kick.js b/src/plugins/irc-events/kick.ts similarity index 100% rename from src/plugins/irc-events/kick.js rename to src/plugins/irc-events/kick.ts diff --git a/src/plugins/irc-events/link.js b/src/plugins/irc-events/link.ts similarity index 100% rename from src/plugins/irc-events/link.js rename to src/plugins/irc-events/link.ts diff --git a/src/plugins/irc-events/list.js b/src/plugins/irc-events/list.ts similarity index 100% rename from src/plugins/irc-events/list.js rename to src/plugins/irc-events/list.ts diff --git a/src/plugins/irc-events/message.js b/src/plugins/irc-events/message.ts similarity index 100% rename from src/plugins/irc-events/message.js rename to src/plugins/irc-events/message.ts diff --git a/src/plugins/irc-events/mode.js b/src/plugins/irc-events/mode.ts similarity index 100% rename from src/plugins/irc-events/mode.js rename to src/plugins/irc-events/mode.ts diff --git a/src/plugins/irc-events/modelist.js b/src/plugins/irc-events/modelist.ts similarity index 100% rename from src/plugins/irc-events/modelist.js rename to src/plugins/irc-events/modelist.ts diff --git a/src/plugins/irc-events/motd.js b/src/plugins/irc-events/motd.ts similarity index 100% rename from src/plugins/irc-events/motd.js rename to src/plugins/irc-events/motd.ts diff --git a/src/plugins/irc-events/names.js b/src/plugins/irc-events/names.ts similarity index 100% rename from src/plugins/irc-events/names.js rename to src/plugins/irc-events/names.ts diff --git a/src/plugins/irc-events/nick.js b/src/plugins/irc-events/nick.ts similarity index 100% rename from src/plugins/irc-events/nick.js rename to src/plugins/irc-events/nick.ts diff --git a/src/plugins/irc-events/part.js b/src/plugins/irc-events/part.ts similarity index 100% rename from src/plugins/irc-events/part.js rename to src/plugins/irc-events/part.ts diff --git a/src/plugins/irc-events/quit.js b/src/plugins/irc-events/quit.ts similarity index 100% rename from src/plugins/irc-events/quit.js rename to src/plugins/irc-events/quit.ts diff --git a/src/plugins/irc-events/sasl.js b/src/plugins/irc-events/sasl.ts similarity index 100% rename from src/plugins/irc-events/sasl.js rename to src/plugins/irc-events/sasl.ts diff --git a/src/plugins/irc-events/topic.js b/src/plugins/irc-events/topic.ts similarity index 100% rename from src/plugins/irc-events/topic.js rename to src/plugins/irc-events/topic.ts diff --git a/src/plugins/irc-events/unhandled.js b/src/plugins/irc-events/unhandled.ts similarity index 100% rename from src/plugins/irc-events/unhandled.js rename to src/plugins/irc-events/unhandled.ts diff --git a/src/plugins/irc-events/welcome.js b/src/plugins/irc-events/welcome.ts similarity index 100% rename from src/plugins/irc-events/welcome.js rename to src/plugins/irc-events/welcome.ts diff --git a/src/plugins/irc-events/whois.js b/src/plugins/irc-events/whois.ts similarity index 100% rename from src/plugins/irc-events/whois.js rename to src/plugins/irc-events/whois.ts From d164784d8b4789fb26b5e0c66bc0b805422f929a Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Mon, 2 May 2022 00:21:11 -0700 Subject: [PATCH 08/88] [ts-migrate][src] Init tsconfig.json file Co-authored-by: ts-migrate <> --- src/client.ts | 2 +- src/command-line/users/index.js | 14 ++++---- src/identification.ts | 2 +- src/{log.js => log.ts} | 21 ++++++------ src/models/chan.ts | 3 ++ src/models/msg.ts | 17 ++++++++++ src/plugins/{changelog.js => changelog.ts} | 23 ++++++++------ src/plugins/inputs/action.ts | 1 + src/plugins/inputs/ban.ts | 1 + src/plugins/inputs/ignore.ts | 2 +- src/plugins/irc-events/away.ts | 16 ++++++---- src/plugins/irc-events/cap.ts | 11 ++++--- src/plugins/irc-events/chghost.ts | 11 ++++--- src/plugins/irc-events/connection.ts | 1 - src/plugins/irc-events/ctcp.ts | 21 ++++++------ src/plugins/irc-events/error.ts | 18 +++++++---- src/plugins/irc-events/help.ts | 10 +++--- src/plugins/irc-events/info.ts | 11 ++++--- src/plugins/irc-events/invite.ts | 10 +++--- src/plugins/irc-events/join.ts | 18 +++++++---- src/plugins/irc-events/kick.ts | 16 ++++++---- src/plugins/irc-events/link.ts | 37 +++++++++++++--------- src/plugins/irc-events/list.ts | 2 +- src/plugins/irc-events/message.ts | 30 ++++++++++-------- src/plugins/irc-events/mode.ts | 16 ++++++---- src/plugins/irc-events/modelist.ts | 32 +++++++++++++------ src/plugins/irc-events/motd.ts | 12 ++++--- src/plugins/irc-events/names.ts | 6 ++-- src/plugins/irc-events/nick.ts | 10 +++--- src/plugins/irc-events/part.ts | 11 ++++--- src/plugins/irc-events/quit.ts | 10 +++--- src/plugins/irc-events/sasl.ts | 13 +++++--- src/plugins/irc-events/topic.ts | 12 ++++--- src/plugins/irc-events/unhandled.ts | 10 +++--- src/plugins/irc-events/welcome.ts | 7 ++-- src/plugins/irc-events/whois.ts | 14 ++++---- src/plugins/messageStorage/text.ts | 2 +- src/plugins/packages/publicClient.js | 2 +- src/types/config.d.ts | 4 +++ src/types/modules/irc-framework.d.ts | 5 +++ 40 files changed, 289 insertions(+), 175 deletions(-) rename src/{log.js => log.ts} (66%) rename src/plugins/{changelog.js => changelog.ts} (83%) diff --git a/src/client.ts b/src/client.ts index ed5f8b50ef..5a88285aa7 100644 --- a/src/client.ts +++ b/src/client.ts @@ -174,7 +174,7 @@ class Client { return chan; } - emit(event: string, data: any) { + emit(event: string, data?: any) { if (this.manager !== null) { this.manager.sockets.in(this.id.toString()).emit(event, data); } diff --git a/src/command-line/users/index.js b/src/command-line/users/index.js index 76ed999fcb..3f5d69c6a6 100644 --- a/src/command-line/users/index.js +++ b/src/command-line/users/index.js @@ -1,10 +1,12 @@ "use strict"; -if (!require("../../config").values.ldap.enable) { - require("./add"); - require("./reset"); +import config from "../../config"; + +if (!config.values.ldap.enable) { + import("./add"); + import("./reset"); } -require("./list"); -require("./remove"); -require("./edit"); +import "./list"; +import "./remove"; +import "./edit"; diff --git a/src/identification.ts b/src/identification.ts index 63644f464f..03a271acd9 100644 --- a/src/identification.ts +++ b/src/identification.ts @@ -122,7 +122,7 @@ class Identification { fs.writeFile(this.oidentdFile, file, {flag: "w+"}, function (err) { if (err) { - log.error("Failed to update oidentd file!", err); + log.error("Failed to update oidentd file!", err.message); } }); } diff --git a/src/log.js b/src/log.ts similarity index 66% rename from src/log.js rename to src/log.ts index eaf7f48ef7..5325c4546e 100644 --- a/src/log.js +++ b/src/log.ts @@ -1,7 +1,7 @@ "use strict"; -const colors = require("chalk"); -const read = require("read"); +import colors from "chalk"; +import read from "read"; function timestamp() { const datetime = new Date().toISOString().split(".")[0].replace("T", " "); @@ -9,26 +9,29 @@ function timestamp() { return colors.dim(datetime); } -module.exports = { +export default { /* eslint-disable no-console */ - error(...args) { + error(...args: string[]) { console.error(timestamp(), colors.red("[ERROR]"), ...args); }, - warn(...args) { + warn(...args: string[]) { console.error(timestamp(), colors.yellow("[WARN]"), ...args); }, - info(...args) { + info(...args: string[]) { console.log(timestamp(), colors.blue("[INFO]"), ...args); }, - debug(...args) { + debug(...args: string[]) { console.log(timestamp(), colors.green("[DEBUG]"), ...args); }, - raw(...args) { + raw(...args: string[]) { console.log(...args); }, /* eslint-enable no-console */ - prompt(options, callback) { + prompt( + options: {prompt: string; text: string}, + callback: (error, result, isDefault) => void + ): void { options.prompt = [timestamp(), colors.cyan("[PROMPT]"), options.text].join(" "); read(options, callback); }, diff --git a/src/models/chan.ts b/src/models/chan.ts index cc212926a1..11dda2e649 100644 --- a/src/models/chan.ts +++ b/src/models/chan.ts @@ -25,6 +25,9 @@ class Chan { type: ChanType; state: ChanState; + // TODO: this only exists when it's a query... should be better typed + userAway: boolean; + constructor(attr: Partial) { _.defaults(this, attr, { id: 0, diff --git a/src/models/msg.ts b/src/models/msg.ts index d036f63b50..2aa89e37e4 100644 --- a/src/models/msg.ts +++ b/src/models/msg.ts @@ -22,6 +22,23 @@ class Msg { 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; + + raw_modes: any; + when: Date; + whois: any; + users: UserInMessage[]; + statusmsgGroup: string; + params: string[]; constructor(attr: Partial) { // Some properties need to be copied in the Msg object instead of referenced diff --git a/src/plugins/changelog.js b/src/plugins/changelog.ts similarity index 83% rename from src/plugins/changelog.js rename to src/plugins/changelog.ts index 5ffbdf3be6..11fe2d039d 100644 --- a/src/plugins/changelog.js +++ b/src/plugins/changelog.ts @@ -1,13 +1,14 @@ "use strict"; -const got = require("got"); -const colors = require("chalk"); -const log = require("../log"); -const pkg = require("../../package.json"); +import got, {Response} from "got"; +import colors from "chalk"; +import log from "../log"; +import pkg from "../../package.json"; +import ClientManager from "src/clientManager"; const TIME_TO_LIVE = 15 * 60 * 1000; // 15 minutes, in milliseconds -module.exports = { +export default { isUpdateAvailable: false, fetch, checkForUpdates, @@ -16,7 +17,11 @@ module.exports = { const versions = { current: { version: `v${pkg.version}`, + changelog: undefined, }, + expiresAt: -1, + latest: undefined, + packages: undefined, }; async function fetch() { @@ -50,9 +55,9 @@ async function fetch() { return versions; } -function updateVersions(response) { - let i; - let release; +function updateVersions(response: Response) { + let i: number; + let release: {tag_name: string; body_html: any; prerelease: boolean; html_url: any}; let prerelease = false; const body = JSON.parse(response.body); @@ -90,7 +95,7 @@ function updateVersions(response) { } } -function checkForUpdates(manager) { +function checkForUpdates(manager: ClientManager) { fetch().then((versionData) => { if (!module.exports.isUpdateAvailable) { // Check for updates every 24 hours + random jitter of <3 hours diff --git a/src/plugins/inputs/action.ts b/src/plugins/inputs/action.ts index 7d6e133e41..ff8b0d7928 100644 --- a/src/plugins/inputs/action.ts +++ b/src/plugins/inputs/action.ts @@ -1,6 +1,7 @@ "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"; diff --git a/src/plugins/inputs/ban.ts b/src/plugins/inputs/ban.ts index db91068e21..82f87215f3 100644 --- a/src/plugins/inputs/ban.ts +++ b/src/plugins/inputs/ban.ts @@ -4,6 +4,7 @@ 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"; const commands = ["ban", "unban", "banlist", "kickban"]; diff --git a/src/plugins/inputs/ignore.ts b/src/plugins/inputs/ignore.ts index 325711b8d4..44cc3f3121 100644 --- a/src/plugins/inputs/ignore.ts +++ b/src/plugins/inputs/ignore.ts @@ -7,7 +7,7 @@ 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 {SpecialChanType} from "src/types/models/channel"; +import {ChanType, SpecialChanType} from "src/types/models/channel"; const commands = ["ignore", "unignore", "ignorelist"]; diff --git a/src/plugins/irc-events/away.ts b/src/plugins/irc-events/away.ts index 35d53b6968..eeddfb0e7a 100644 --- a/src/plugins/irc-events/away.ts +++ b/src/plugins/irc-events/away.ts @@ -1,13 +1,15 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +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"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; - irc.on("away", (data) => handleAway(Msg.Type.AWAY, data)); - irc.on("back", (data) => handleAway(Msg.Type.BACK, data)); + irc.on("away", (data) => handleAway(MessageType.AWAY, data)); + irc.on("back", (data) => handleAway(MessageType.BACK, data)); function handleAway(type, data) { const away = data.message; @@ -28,7 +30,7 @@ module.exports = function (irc, network) { network.channels.forEach((chan) => { let user; - switch (ChanType) { + switch (chan.type) { case ChanType.QUERY: { if (data.nick.toLowerCase() !== chan.name.toLowerCase()) { return; @@ -70,4 +72,4 @@ module.exports = function (irc, network) { } }); } -}; +} diff --git a/src/plugins/irc-events/cap.ts b/src/plugins/irc-events/cap.ts index 18e78cf763..cc211d6724 100644 --- a/src/plugins/irc-events/cap.ts +++ b/src/plugins/irc-events/cap.ts @@ -1,9 +1,10 @@ "use strict"; -const Msg = require("../../models/msg"); -const STSPolicies = require("../sts"); +import Network from "src/models/network"; +import Msg from "../../models/msg"; +import STSPolicies from "../sts"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("cap ls", (data) => { @@ -20,7 +21,7 @@ module.exports = function (irc, network) { } const isSecure = irc.connection.transport.socket.encrypted; - const values = {}; + const values = {} as any; data.capabilities.sts.split(",").map((value) => { value = value.split("=", 2); @@ -75,4 +76,4 @@ module.exports = function (irc, network) { client.save(); } } -}; +} diff --git a/src/plugins/irc-events/chghost.ts b/src/plugins/irc-events/chghost.ts index 492cdf5534..7503b0d39c 100644 --- a/src/plugins/irc-events/chghost.ts +++ b/src/plugins/irc-events/chghost.ts @@ -1,8 +1,11 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; -module.exports = function (irc, network) { +import Msg from "src/models/msg"; + +export default function (irc: Network["irc"], network: Network) { const client = this; // If server supports CHGHOST cap, then changing the hostname does not require @@ -17,7 +20,7 @@ module.exports = function (irc, network) { const msg = new Msg({ time: data.time, - type: Msg.Type.CHGHOST, + type: MessageType.CHGHOST, new_ident: data.ident !== data.new_ident ? data.new_ident : "", new_host: data.hostname !== data.new_hostname ? data.new_hostname : "", self: data.nick === irc.user.nick, @@ -27,4 +30,4 @@ module.exports = function (irc, network) { chan.pushMessage(client, msg); }); }); -}; +} diff --git a/src/plugins/irc-events/connection.ts b/src/plugins/irc-events/connection.ts index e6a0fdfd8b..c81607731d 100644 --- a/src/plugins/irc-events/connection.ts +++ b/src/plugins/irc-events/connection.ts @@ -3,7 +3,6 @@ import _ from "lodash"; import log from "../../log"; import Msg from "../../models/msg"; -import Chan from "../../models/chan"; import Helper from "../../helper"; import Config from "../../config"; import Network from "src/models/network"; diff --git a/src/plugins/irc-events/ctcp.ts b/src/plugins/irc-events/ctcp.ts index 70a46de896..e5d9004fb0 100644 --- a/src/plugins/irc-events/ctcp.ts +++ b/src/plugins/irc-events/ctcp.ts @@ -1,10 +1,13 @@ "use strict"; -const _ = require("lodash"); -const Helper = require("../../helper"); -const Msg = require("../../models/msg"); -const User = require("../../models/user"); -const pkg = require("../../../package.json"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; + +import _ from "lodash"; +import Helper from "../../helper"; +import Msg from "../../models/msg"; +import User from "../../models/user"; +import pkg from "../../../package.json"; const ctcpResponses = { CLIENTINFO: () => @@ -16,7 +19,7 @@ const ctcpResponses = { VERSION: () => pkg.name + " " + Helper.getVersion() + " -- " + pkg.homepage, }; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; const lobby = network.channels[0]; @@ -36,7 +39,7 @@ module.exports = function (irc, network) { } const msg = new Msg({ - type: Msg.Type.CTCP, + type: MessageType.CTCP, time: data.time, from: chan.getUser(data.nick), ctcpMessage: data.message, @@ -76,7 +79,7 @@ module.exports = function (irc, network) { // Let user know someone is making a CTCP request against their nick const msg = new Msg({ - type: Msg.Type.CTCP_REQUEST, + type: MessageType.CTCP_REQUEST, time: data.time, from: new User({nick: target}), hostmask: data.ident + "@" + data.hostname, @@ -88,4 +91,4 @@ module.exports = function (irc, network) { {trailing: false} ) ); -}; +} diff --git a/src/plugins/irc-events/error.ts b/src/plugins/irc-events/error.ts index 8bb49af62a..f04428b379 100644 --- a/src/plugins/irc-events/error.ts +++ b/src/plugins/irc-events/error.ts @@ -1,19 +1,23 @@ "use strict"; -const Msg = require("../../models/msg"); -const Config = require("../../config"); +import Msg from "../../models/msg"; +import Config from "../../config"; +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("irc error", function (data) { const msg = new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, error: data.error, showInActive: true, + // @ts-ignore nick: data.nick, channel: data.channel, reason: data.reason, + // @ts-ignore TODO command: data.command, }); @@ -48,7 +52,7 @@ module.exports = function (irc, network) { const lobby = network.channels[0]; const msg = new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: message, showInActive: true, }); @@ -74,7 +78,7 @@ module.exports = function (irc, network) { irc.on("nick invalid", function (data) { const lobby = network.channels[0]; const msg = new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: data.nick + ": " + (data.reason || "Nickname is invalid."), showInActive: true, }); @@ -89,4 +93,4 @@ module.exports = function (irc, network) { nick: irc.user.nick, }); }); -}; +} diff --git a/src/plugins/irc-events/help.ts b/src/plugins/irc-events/help.ts index ccf57fe4b5..7fc20c54f9 100644 --- a/src/plugins/irc-events/help.ts +++ b/src/plugins/irc-events/help.ts @@ -1,8 +1,10 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("help", function (data) { @@ -10,11 +12,11 @@ module.exports = function (irc, network) { if (data.help) { const msg = new Msg({ - type: Msg.Type.MONOSPACE_BLOCK, + type: MessageType.MONOSPACE_BLOCK, command: "help", text: data.help, }); lobby.pushMessage(client, msg, true); } }); -}; +} diff --git a/src/plugins/irc-events/info.ts b/src/plugins/irc-events/info.ts index 663e6c7778..1e1fc291a3 100644 --- a/src/plugins/irc-events/info.ts +++ b/src/plugins/irc-events/info.ts @@ -1,8 +1,11 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; -module.exports = function (irc, network) { +import Msg from "../../models/msg"; + +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("info", function (data) { @@ -10,11 +13,11 @@ module.exports = function (irc, network) { if (data.info) { const msg = new Msg({ - type: Msg.Type.MONOSPACE_BLOCK, + type: MessageType.MONOSPACE_BLOCK, command: "info", text: data.info, }); lobby.pushMessage(client, msg, true); } }); -}; +} diff --git a/src/plugins/irc-events/invite.ts b/src/plugins/irc-events/invite.ts index 2d0ffc4510..c37b46f8dd 100644 --- a/src/plugins/irc-events/invite.ts +++ b/src/plugins/irc-events/invite.ts @@ -1,8 +1,10 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("invite", function (data) { @@ -15,7 +17,7 @@ module.exports = function (irc, network) { const invitedYou = data.invited === irc.user.nick; const msg = new Msg({ - type: Msg.Type.INVITE, + type: MessageType.INVITE, time: data.time, from: chan.getUser(data.nick), target: chan.getUser(data.invited), @@ -25,4 +27,4 @@ module.exports = function (irc, network) { }); chan.pushMessage(client, msg); }); -}; +} diff --git a/src/plugins/irc-events/join.ts b/src/plugins/irc-events/join.ts index 741df63a95..7b8973f8d7 100644 --- a/src/plugins/irc-events/join.ts +++ b/src/plugins/irc-events/join.ts @@ -1,10 +1,14 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); -const User = require("../../models/user"); +import {ChanState} from "src/types/models/channel"; +import {MessageType} from "src/types/models/message"; +import {Network} from "src/types/models/network"; -module.exports = function (irc, network) { +import Chan from "../../models/chan"; +import Msg from "../../models/msg"; +import User from "../../models/user"; + +module.exports = function (irc: Network["irc"], network: Network) { const client = this; irc.on("join", function (data) { @@ -13,7 +17,7 @@ module.exports = function (irc, network) { if (typeof chan === "undefined") { chan = client.createChannel({ name: data.channel, - state: Chan.State.JOINED, + state: ChanState.JOINED, }); client.emit("join", { @@ -28,7 +32,7 @@ module.exports = function (irc, network) { // Request channels' modes network.irc.raw("MODE", chan.name); } else if (data.nick === irc.user.nick) { - chan.state = Chan.State.JOINED; + chan.state = ChanState.JOINED; client.emit("channel:state", { chan: chan.id, @@ -43,7 +47,7 @@ module.exports = function (irc, network) { hostmask: data.ident + "@" + data.hostname, gecos: data.gecos, account: data.account, - type: Msg.Type.JOIN, + type: MessageType.JOIN, self: data.nick === irc.user.nick, }); chan.pushMessage(client, msg); diff --git a/src/plugins/irc-events/kick.ts b/src/plugins/irc-events/kick.ts index aeac8036a9..da99a334c3 100644 --- a/src/plugins/irc-events/kick.ts +++ b/src/plugins/irc-events/kick.ts @@ -1,9 +1,13 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {ChanState} from "src/types/models/channel"; +import {MessageType} from "src/types/models/message"; -module.exports = function (irc, network) { +import Chan from "../../models/chan"; +import Msg from "../../models/msg"; + +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("kick", function (data) { @@ -14,7 +18,7 @@ module.exports = function (irc, network) { } const msg = new Msg({ - type: Msg.Type.KICK, + type: MessageType.KICK, time: data.time, from: chan.getUser(data.nick), target: chan.getUser(data.kicked), @@ -26,7 +30,7 @@ module.exports = function (irc, network) { if (data.kicked === irc.user.nick) { chan.users = new Map(); - chan.state = Chan.State.PARTED; + chan.state = ChanState.PARTED; client.emit("channel:state", { chan: chan.id, @@ -36,4 +40,4 @@ module.exports = function (irc, network) { chan.removeUser(msg.target); } }); -}; +} diff --git a/src/plugins/irc-events/link.ts b/src/plugins/irc-events/link.ts index 6bfa366b4c..508c09827f 100644 --- a/src/plugins/irc-events/link.ts +++ b/src/plugins/irc-events/link.ts @@ -1,18 +1,23 @@ "use strict"; -const cheerio = require("cheerio"); -const got = require("got"); -const URL = require("url").URL; -const mime = require("mime-types"); -const Config = require("../../config"); -const {findLinksWithSchema} = require("../../../client/js/helpers/ircmessageparser/findLinks"); -const storage = require("../storage"); +import * as cheerio from "cheerio"; +import got from "got"; +import {URL} from "url"; +import mime from "mime-types"; + +import Config from "../../config"; +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"; + const currentFetchPromises = new Map(); const imageTypeRegex = /^image\/.+/; const mediaTypeRegex = /^(audio|video)\/.+/; -const log = require("../../log"); -module.exports = function (client, chan, msg, cleanText) { +export default function (client: Client, chan: Chan, msg: Msg, cleanText: string) { if (!Config.values.prefetch) { return; } @@ -43,6 +48,8 @@ module.exports = function (client, chan, msg, cleanText) { size: -1, link: link.link, // Send original matched link to the client shown: null, + error: undefined, + message: undefined, }; cleanLinks.push(preview); @@ -63,9 +70,9 @@ module.exports = function (client, chan, msg, cleanText) { return cleanLinks; }, []); -}; +} -function parseHtml(preview, res, client) { +function parseHtml(preview, res, client: Client) { return new Promise((resolve) => { const $ = cheerio.load(res.data); @@ -128,7 +135,7 @@ function parseHtml(preview, res, client) { }); } -function parseHtmlMedia($, preview, client) { +function parseHtmlMedia($: cheerio.CheerioAPI, preview, client) { return new Promise((resolve, reject) => { if (Config.values.disableMediaPreview) { reject(); @@ -202,8 +209,8 @@ function parseHtmlMedia($, preview, client) { } }); } - -function parse(msg, chan, preview, res, client) { +// TODO: type preview +function parse(msg: Msg, chan: Chan, preview: any, res, client: Client) { let promise; preview.size = res.size; @@ -467,7 +474,7 @@ function fetch(uri, headers) { return promise; } -function normalizeURL(link, baseLink, disallowHttp = false) { +function normalizeURL(link: string, baseLink?: string, disallowHttp = false) { try { const url = new URL(link, baseLink); diff --git a/src/plugins/irc-events/list.ts b/src/plugins/irc-events/list.ts index 76fbdd8eb9..2a9c24a388 100644 --- a/src/plugins/irc-events/list.ts +++ b/src/plugins/irc-events/list.ts @@ -2,7 +2,7 @@ const Chan = require("../../models/chan"); -module.exports = function (irc, network) { +module.exports = function (irc: Network["irc"], network: Network) { const client = this; const MAX_CHANS = 500; diff --git a/src/plugins/irc-events/message.ts b/src/plugins/irc-events/message.ts index 720b3a67af..ac5d433bae 100644 --- a/src/plugins/irc-events/message.ts +++ b/src/plugins/irc-events/message.ts @@ -1,33 +1,35 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); -const LinkPrefetch = require("./link"); -const cleanIrcMessage = require("../../../client/js/helpers/ircmessageparser/cleanIrcMessage"); -const Helper = require("../../helper"); +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"; const nickRegExp = /(?:\x03[0-9]{1,2}(?:,[0-9]{1,2})?)?([\w[\]\\`^{|}-]+)/g; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("notice", function (data) { - data.type = Msg.Type.NOTICE; + data.type = MessageType.NOTICE as any; handleMessage(data); }); irc.on("action", function (data) { - data.type = Msg.Type.ACTION; + data.type = MessageType.ACTION; handleMessage(data); }); irc.on("privmsg", function (data) { - data.type = Msg.Type.MESSAGE; + data.type = MessageType.MESSAGE; handleMessage(data); }); irc.on("wallops", function (data) { data.from_server = true; - data.type = Msg.Type.WALLOPS; + data.type = MessageType.WALLOPS; handleMessage(data); }); @@ -76,7 +78,7 @@ module.exports = function (irc, network) { if (typeof chan === "undefined") { // Send notices that are not targeted at us into the server window - if (data.type === Msg.Type.NOTICE) { + if (data.type === MessageType.NOTICE) { showInActive = true; chan = network.channels[0]; } else { @@ -152,7 +154,7 @@ module.exports = function (irc, network) { } // No prefetch URLs unless are simple MESSAGE or ACTION types - if ([Msg.Type.MESSAGE, Msg.Type.ACTION].includes(data.type)) { + if ([MessageType.MESSAGE, MessageType.ACTION].includes(data.type)) { LinkPrefetch(client, chan, msg, cleanMessage); } @@ -163,7 +165,7 @@ module.exports = function (irc, network) { let title = chan.name; let body = cleanMessage; - if (msg.type === Msg.Type.ACTION) { + if (msg.type === MessageType.ACTION) { // For actions, do not include colon in the message body = `${data.nick} ${body}`; } else if (chan.type !== ChanType.QUERY) { @@ -213,4 +215,4 @@ module.exports = function (irc, network) { } } } -}; +} diff --git a/src/plugins/irc-events/mode.ts b/src/plugins/irc-events/mode.ts index 8046207fe3..1104d70b76 100644 --- a/src/plugins/irc-events/mode.ts +++ b/src/plugins/irc-events/mode.ts @@ -1,9 +1,11 @@ "use strict"; -const _ = require("lodash"); -const Msg = require("../../models/msg"); +import _ from "lodash"; +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; // The following saves the channel key based on channel mode instead of @@ -33,7 +35,7 @@ module.exports = function (irc, network) { }); const msg = new Msg({ - type: Msg.Type.MODE_CHANNEL, + type: MessageType.MODE_CHANNEL, text: `${data.raw_modes} ${data.raw_params.join(" ")}`, }); targetChan.pushMessage(client, msg); @@ -43,7 +45,7 @@ module.exports = function (irc, network) { const serverChan = network.channels[0]; const msg = new Msg({ - type: Msg.Type.MODE_USER, + type: MessageType.MODE_USER, raw_modes: data.raw_modes, self: false, showInActive: true, @@ -66,7 +68,7 @@ module.exports = function (irc, network) { const msg = new Msg({ time: data.time, - type: Msg.Type.MODE, + type: MessageType.MODE, from: targetChan.getUser(data.nick), text: `${data.raw_modes} ${data.raw_params.join(" ")}`, self: data.nick === irc.user.nick, @@ -144,4 +146,4 @@ module.exports = function (irc, network) { }); } }); -}; +} diff --git a/src/plugins/irc-events/modelist.ts b/src/plugins/irc-events/modelist.ts index a9ee6f7cb7..b231fc01b5 100644 --- a/src/plugins/irc-events/modelist.ts +++ b/src/plugins/irc-events/modelist.ts @@ -1,9 +1,13 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {ChanType, SpecialChanType} from "src/types/models/channel"; +import {MessageType} from "src/types/models/message"; -module.exports = function (irc, network) { +import Chan from "../../models/chan"; +import Msg from "../../models/msg"; + +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("banlist", (list) => { @@ -13,7 +17,7 @@ module.exports = function (irc, network) { banned_at: ban.banned_at * 1000, })); - handleList(Chan.SpecialType.BANLIST, "Ban list", list.channel, data); + handleList(SpecialChanType.BANLIST, "Ban list", list.channel, data); }); irc.on("inviteList", (list) => { @@ -23,14 +27,23 @@ module.exports = function (irc, network) { invited_at: invite.invited_at * 1000, })); - handleList(Chan.SpecialType.INVITELIST, "Invite list", list.channel, data); + handleList(SpecialChanType.INVITELIST, "Invite list", list.channel, data); }); - function handleList(type, name, channel, data) { + function handleList( + type: SpecialChanType, + name: string, + channel: string, + data: { + hostmask: string; + invited_by?: string; + inivted_at?: number; + }[] + ) { if (data.length === 0) { const msg = new Msg({ - time: Date.now(), - type: Msg.Type.ERROR, + time: new Date(), + type: MessageType.ERROR, text: `${name} is empty`, }); let chan = network.getChannel(channel); @@ -62,6 +75,7 @@ module.exports = function (irc, network) { index: network.addChannel(chan), }); } else { + //@ts-ignore TODO chan.data = data; client.emit("msg:special", { @@ -70,4 +84,4 @@ module.exports = function (irc, network) { }); } } -}; +} diff --git a/src/plugins/irc-events/motd.ts b/src/plugins/irc-events/motd.ts index 135838404e..1d7c57eca8 100644 --- a/src/plugins/irc-events/motd.ts +++ b/src/plugins/irc-events/motd.ts @@ -1,8 +1,10 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("motd", function (data) { @@ -10,7 +12,7 @@ module.exports = function (irc, network) { if (data.motd) { const msg = new Msg({ - type: Msg.Type.MONOSPACE_BLOCK, + type: MessageType.MONOSPACE_BLOCK, command: "motd", text: data.motd, }); @@ -19,11 +21,11 @@ module.exports = function (irc, network) { if (data.error) { const msg = new Msg({ - type: Msg.Type.MONOSPACE_BLOCK, + type: MessageType.MONOSPACE_BLOCK, command: "motd", text: data.error, }); lobby.pushMessage(client, msg); } }); -}; +} diff --git a/src/plugins/irc-events/names.ts b/src/plugins/irc-events/names.ts index 8368b281c7..12d65c4497 100644 --- a/src/plugins/irc-events/names.ts +++ b/src/plugins/irc-events/names.ts @@ -1,6 +1,8 @@ "use strict"; -module.exports = function (irc, network) { +import Network from "src/models/network"; + +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("userlist", function (data) { @@ -25,4 +27,4 @@ module.exports = function (irc, network) { chan: chan.id, }); }); -}; +} diff --git a/src/plugins/irc-events/nick.ts b/src/plugins/irc-events/nick.ts index dc930c28be..2618a8473e 100644 --- a/src/plugins/irc-events/nick.ts +++ b/src/plugins/irc-events/nick.ts @@ -1,8 +1,10 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("nick", function (data) { @@ -34,7 +36,7 @@ module.exports = function (irc, network) { const msg = new Msg({ time: data.time, from: user, - type: Msg.Type.NICK, + type: MessageType.NICK, new_nick: data.new_nick, }); chan.pushMessage(client, msg); @@ -48,4 +50,4 @@ module.exports = function (irc, network) { }); }); }); -}; +} diff --git a/src/plugins/irc-events/part.ts b/src/plugins/irc-events/part.ts index 25c698cf58..e635f57fec 100644 --- a/src/plugins/irc-events/part.ts +++ b/src/plugins/irc-events/part.ts @@ -1,8 +1,11 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; -module.exports = function (irc, network) { +import Msg from "../../models/msg"; + +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("part", function (data) { @@ -14,7 +17,7 @@ module.exports = function (irc, network) { const user = chan.getUser(data.nick); const msg = new Msg({ - type: Msg.Type.PART, + type: MessageType.PART, time: data.time, text: data.message || "", hostmask: data.ident + "@" + data.hostname, @@ -29,4 +32,4 @@ module.exports = function (irc, network) { chan.removeUser(user); } }); -}; +} diff --git a/src/plugins/irc-events/quit.ts b/src/plugins/irc-events/quit.ts index 36bd336d6d..9d0e6587b7 100644 --- a/src/plugins/irc-events/quit.ts +++ b/src/plugins/irc-events/quit.ts @@ -1,8 +1,10 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("quit", function (data) { @@ -15,7 +17,7 @@ module.exports = function (irc, network) { const msg = new Msg({ time: data.time, - type: Msg.Type.QUIT, + type: MessageType.QUIT, text: data.message || "", hostmask: data.ident + "@" + data.hostname, from: user, @@ -31,4 +33,4 @@ module.exports = function (irc, network) { network.keepNick = null; } }); -}; +} diff --git a/src/plugins/irc-events/sasl.ts b/src/plugins/irc-events/sasl.ts index 2b429880a1..5e05ee2d2b 100644 --- a/src/plugins/irc-events/sasl.ts +++ b/src/plugins/irc-events/sasl.ts @@ -1,15 +1,18 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; -module.exports = function (irc, network) { +import Msg from "../../models/msg"; + +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("loggedin", (data) => { const lobby = network.channels[0]; const msg = new Msg({ - type: Msg.Type.LOGIN, + type: MessageType.LOGIN, text: "Logged in as: " + data.account, }); lobby.pushMessage(client, msg, true); @@ -19,9 +22,9 @@ module.exports = function (irc, network) { const lobby = network.channels[0]; const msg = new Msg({ - type: Msg.Type.LOGOUT, + type: MessageType.LOGOUT, text: "Logged out", }); lobby.pushMessage(client, msg, true); }); -}; +} diff --git a/src/plugins/irc-events/topic.ts b/src/plugins/irc-events/topic.ts index dedc53e70f..e8531a689f 100644 --- a/src/plugins/irc-events/topic.ts +++ b/src/plugins/irc-events/topic.ts @@ -1,8 +1,10 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("topic", function (data) { @@ -14,7 +16,7 @@ module.exports = function (irc, network) { const msg = new Msg({ time: data.time, - type: Msg.Type.TOPIC, + type: MessageType.TOPIC, from: data.nick && chan.getUser(data.nick), text: data.topic, self: data.nick === irc.user.nick, @@ -36,11 +38,11 @@ module.exports = function (irc, network) { } const msg = new Msg({ - type: Msg.Type.TOPIC_SET_BY, + type: MessageType.TOPIC_SET_BY, from: chan.getUser(data.nick), when: new Date(data.when * 1000), self: data.nick === irc.user.nick, }); chan.pushMessage(client, msg); }); -}; +} diff --git a/src/plugins/irc-events/unhandled.ts b/src/plugins/irc-events/unhandled.ts index e0b4a13428..cd0a3987d7 100644 --- a/src/plugins/irc-events/unhandled.ts +++ b/src/plugins/irc-events/unhandled.ts @@ -1,8 +1,10 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import {MessageType} from "src/types/models/message"; +import Msg from "../../models/msg"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("unknown command", function (command) { @@ -27,11 +29,11 @@ module.exports = function (irc, network) { target.pushMessage( client, new Msg({ - type: Msg.Type.UNHANDLED, + type: MessageType.UNHANDLED, command: command.command, params: command.params, }), true ); }); -}; +} diff --git a/src/plugins/irc-events/welcome.ts b/src/plugins/irc-events/welcome.ts index 21d8a70f92..274df822e2 100644 --- a/src/plugins/irc-events/welcome.ts +++ b/src/plugins/irc-events/welcome.ts @@ -1,8 +1,9 @@ "use strict"; -const Msg = require("../../models/msg"); +import Network from "src/models/network"; +import Msg from "../../models/msg"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("registered", function (data) { @@ -20,4 +21,4 @@ module.exports = function (irc, network) { nick: data.nick, }); }); -}; +} diff --git a/src/plugins/irc-events/whois.ts b/src/plugins/irc-events/whois.ts index 96ac2cd4b0..31ea7aa109 100644 --- a/src/plugins/irc-events/whois.ts +++ b/src/plugins/irc-events/whois.ts @@ -1,9 +1,11 @@ "use strict"; -const Chan = require("../../models/chan"); -const Msg = require("../../models/msg"); +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"; -module.exports = function (irc, network) { +export default function (irc: Network["irc"], network: Network) { const client = this; irc.on("whois", handleWhois); @@ -42,7 +44,7 @@ module.exports = function (irc, network) { if (data.error) { msg = new Msg({ - type: Msg.Type.ERROR, + type: MessageType.ERROR, text: "No such nick: " + data.nick, }); } else { @@ -51,11 +53,11 @@ module.exports = function (irc, network) { // Absolute datetime in milliseconds when nick logged on. data.logonTime = data.logon * 1000; msg = new Msg({ - type: Msg.Type.WHOIS, + type: MessageType.WHOIS, whois: data, }); } chan.pushMessage(client, msg); } -}; +} diff --git a/src/plugins/messageStorage/text.ts b/src/plugins/messageStorage/text.ts index 0039b7a375..cfc55a9241 100644 --- a/src/plugins/messageStorage/text.ts +++ b/src/plugins/messageStorage/text.ts @@ -112,7 +112,7 @@ class TextFileMessageStorage implements MessageStorage { line, (e) => { if (e) { - log.error("Failed to write user log", e); + log.error("Failed to write user log", e.message); } } ); diff --git a/src/plugins/packages/publicClient.js b/src/plugins/packages/publicClient.js index f650797103..ae99469db7 100644 --- a/src/plugins/packages/publicClient.js +++ b/src/plugins/packages/publicClient.js @@ -51,7 +51,7 @@ module.exports = class PublicClient { chan.pushMessage( this.client, new Msg({ - type: Msg.Type.PLUGIN, + type: MessageType.PLUGIN, text: text, from: { nick: this.packageInfo.name || this.packageInfo.packageName, diff --git a/src/types/config.d.ts b/src/types/config.d.ts index c8c468aa93..73e0824ec4 100644 --- a/src/types/config.d.ts +++ b/src/types/config.d.ts @@ -52,6 +52,10 @@ type ServerConfiguration = Config & { stylesheets: string[]; }; +type IndexTemplateConfiguration = ServerConfiguration & { + cacheBust: string; +}; + // TODO: Type this type WebIRC = { [key: string]: any; diff --git a/src/types/modules/irc-framework.d.ts b/src/types/modules/irc-framework.d.ts index a69eb6617b..e4c6f4bb0c 100644 --- a/src/types/modules/irc-framework.d.ts +++ b/src/types/modules/irc-framework.d.ts @@ -17,6 +17,7 @@ declare module "irc-framework" { transport: any; write: (data: string) => void; + end: () => void; }; export class Client extends EventEmitter { @@ -29,11 +30,13 @@ declare module "irc-framework" { CHANTYPES: string; PREFIX: any; CHANMODES: string; + NICKLEN: string; }; cap: { isEnabled: (cap: string) => boolean; enabled: string[]; }; + extractTargetGroup: (target: string) => any; }; // End of added by Max @@ -258,6 +261,7 @@ declare module "irc-framework" { message: string; nick: string; time?: any; + channel?: string; } interface Mode { mode: string; @@ -310,6 +314,7 @@ declare module "irc-framework" { nick: string; username: string; gecos: string; + host: string; } class IrcChannel extends EventEmitter { From 291ac3e903304a16b84b2ccf6e7c775db1b65412 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Mon, 2 May 2022 00:21:19 -0700 Subject: [PATCH 09/88] [ts-migrate][src] Rename files from JS/JSX to TS/TSX Co-authored-by: ts-migrate <> --- src/command-line/users/{add.js => add.ts} | 0 src/command-line/users/{edit.js => edit.ts} | 0 src/command-line/users/{index.js => index.ts} | 0 src/command-line/users/{list.js => list.ts} | 0 src/command-line/users/{remove.js => remove.ts} | 0 src/command-line/users/{reset.js => reset.ts} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename src/command-line/users/{add.js => add.ts} (100%) rename src/command-line/users/{edit.js => edit.ts} (100%) rename src/command-line/users/{index.js => index.ts} (100%) rename src/command-line/users/{list.js => list.ts} (100%) rename src/command-line/users/{remove.js => remove.ts} (100%) rename src/command-line/users/{reset.js => reset.ts} (100%) diff --git a/src/command-line/users/add.js b/src/command-line/users/add.ts similarity index 100% rename from src/command-line/users/add.js rename to src/command-line/users/add.ts diff --git a/src/command-line/users/edit.js b/src/command-line/users/edit.ts similarity index 100% rename from src/command-line/users/edit.js rename to src/command-line/users/edit.ts diff --git a/src/command-line/users/index.js b/src/command-line/users/index.ts similarity index 100% rename from src/command-line/users/index.js rename to src/command-line/users/index.ts diff --git a/src/command-line/users/list.js b/src/command-line/users/list.ts similarity index 100% rename from src/command-line/users/list.js rename to src/command-line/users/list.ts diff --git a/src/command-line/users/remove.js b/src/command-line/users/remove.ts similarity index 100% rename from src/command-line/users/remove.js rename to src/command-line/users/remove.ts diff --git a/src/command-line/users/reset.js b/src/command-line/users/reset.ts similarity index 100% rename from src/command-line/users/reset.js rename to src/command-line/users/reset.ts From 5bb94d6428ce71a69c8f6b38f686789295441ecf Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Mon, 2 May 2022 00:50:13 -0700 Subject: [PATCH 10/88] ts progress --- .eslintrc.yml | 2 +- client/dist/components/MessageTypes/index.js | 11 + .../dist/components/MessageTypes/index.js.map | 1 + client/dist/js/auth.js | 10 + client/dist/js/auth.js.map | 1 + client/dist/js/autocompletion.js | 280 ++++ client/dist/js/autocompletion.js.map | 1 + client/dist/js/clipboard.js | 25 + client/dist/js/clipboard.js.map | 1 + client/dist/js/commands/collapse.js | 29 + client/dist/js/commands/collapse.js.map | 1 + client/dist/js/commands/expand.js | 29 + client/dist/js/commands/expand.js.map | 1 + client/dist/js/commands/index.js | 16 + client/dist/js/commands/index.js.map | 1 + client/dist/js/commands/join.js | 40 + client/dist/js/commands/join.js.map | 1 + client/dist/js/commands/search.js | 20 + client/dist/js/commands/search.js.map | 1 + client/dist/js/constants.js | 35 + client/dist/js/constants.js.map | 1 + client/dist/js/eventbus.js | 48 + client/dist/js/eventbus.js.map | 1 + client/dist/js/helpers/collapseNetwork.js | 13 + client/dist/js/helpers/collapseNetwork.js.map | 1 + client/dist/js/helpers/colorClass.js | 15 + client/dist/js/helpers/colorClass.js.map | 1 + client/dist/js/helpers/contextMenu.js | 378 +++++ client/dist/js/helpers/contextMenu.js.map | 1 + client/dist/js/helpers/distance.js | 5 + client/dist/js/helpers/distance.js.map | 1 + client/dist/js/helpers/friendlysize.js | 9 + client/dist/js/helpers/friendlysize.js.map | 1 + .../ircmessageparser/anyIntersection.js | 13 + .../ircmessageparser/anyIntersection.js.map | 1 + .../ircmessageparser/cleanIrcMessage.js | 5 + .../ircmessageparser/cleanIrcMessage.js.map | 1 + .../dist/js/helpers/ircmessageparser/fill.js | 30 + .../js/helpers/ircmessageparser/fill.js.map | 1 + .../helpers/ircmessageparser/findChannels.js | 37 + .../ircmessageparser/findChannels.js.map | 1 + .../js/helpers/ircmessageparser/findEmoji.js | 16 + .../helpers/ircmessageparser/findEmoji.js.map | 1 + .../js/helpers/ircmessageparser/findLinks.js | 25 +- .../helpers/ircmessageparser/findLinks.js.map | 1 + .../js/helpers/ircmessageparser/findNames.js | 22 + .../helpers/ircmessageparser/findNames.js.map | 1 + .../dist/js/helpers/ircmessageparser/merge.js | 44 + .../js/helpers/ircmessageparser/merge.js.map | 1 + .../js/helpers/ircmessageparser/parseStyle.js | 198 +++ .../ircmessageparser/parseStyle.js.map | 1 + client/dist/js/helpers/isChannelCollapsed.js | 12 + .../dist/js/helpers/isChannelCollapsed.js.map | 1 + client/dist/js/helpers/isIgnoredKeybind.js | 11 + .../dist/js/helpers/isIgnoredKeybind.js.map | 1 + .../js/helpers/listenForTwoFingerSwipes.js | 85 ++ .../helpers/listenForTwoFingerSwipes.js.map | 1 + client/dist/js/helpers/localetime.js | 4 + client/dist/js/helpers/localetime.js.map | 1 + client/dist/js/helpers/parse.js | 177 +++ client/dist/js/helpers/parse.js.map | 1 + client/dist/js/helpers/parseIrcUri.js | 43 + client/dist/js/helpers/parseIrcUri.js.map | 1 + client/dist/js/helpers/roundBadgeNumber.js | 8 + .../dist/js/helpers/roundBadgeNumber.js.map | 1 + client/dist/js/keybinds.js | 188 +++ client/dist/js/keybinds.js.map | 1 + client/dist/js/loading-error-handlers.js | 85 ++ client/dist/js/loading-error-handlers.js.map | 1 + client/dist/js/localStorage.js | 42 + client/dist/js/localStorage.js.map | 1 + client/dist/js/location.js | 9 + client/dist/js/location.js.map | 1 + client/dist/js/router.js | 180 +++ client/dist/js/router.js.map | 1 + client/dist/js/settings.js | 125 ++ client/dist/js/settings.js.map | 1 + client/dist/js/socket-events/auth.js | 81 ++ client/dist/js/socket-events/auth.js.map | 1 + client/dist/js/socket-events/changelog.js | 32 + client/dist/js/socket-events/changelog.js.map | 1 + client/dist/js/socket-events/commands.js | 8 + client/dist/js/socket-events/commands.js.map | 1 + client/dist/js/socket-events/configuration.js | 28 + .../js/socket-events/configuration.js.map | 1 + client/dist/js/socket-events/connection.js | 57 + .../dist/js/socket-events/connection.js.map | 1 + client/dist/js/socket-events/history_clear.js | 12 + .../js/socket-events/history_clear.js.map | 1 + client/dist/js/socket-events/index.js | 29 + client/dist/js/socket-events/index.js.map | 1 + client/dist/js/socket-events/init.js | 154 +++ client/dist/js/socket-events/init.js.map | 1 + client/dist/js/socket-events/join.js | 18 + client/dist/js/socket-events/join.js.map | 1 + client/dist/js/socket-events/mentions.js | 7 + client/dist/js/socket-events/mentions.js.map | 1 + client/dist/js/socket-events/more.js | 24 + client/dist/js/socket-events/more.js.map | 1 + client/dist/js/socket-events/msg.js | 166 +++ client/dist/js/socket-events/msg.js.map | 1 + client/dist/js/socket-events/msg_preview.js | 16 + .../dist/js/socket-events/msg_preview.js.map | 1 + client/dist/js/socket-events/msg_special.js | 10 + .../dist/js/socket-events/msg_special.js.map | 1 + client/dist/js/socket-events/mute_changed.js | 16 + .../dist/js/socket-events/mute_changed.js.map | 1 + client/dist/js/socket-events/names.js | 10 + client/dist/js/socket-events/names.js.map | 1 + client/dist/js/socket-events/network.js | 54 + client/dist/js/socket-events/network.js.map | 1 + client/dist/js/socket-events/nick.js | 10 + client/dist/js/socket-events/nick.js.map | 1 + client/dist/js/socket-events/open.js | 24 + client/dist/js/socket-events/open.js.map | 1 + client/dist/js/socket-events/part.js | 20 + client/dist/js/socket-events/part.js.map | 1 + client/dist/js/socket-events/quit.js | 20 + client/dist/js/socket-events/quit.js.map | 1 + client/dist/js/socket-events/search.js | 11 + client/dist/js/socket-events/search.js.map | 1 + client/dist/js/socket-events/sessions_list.js | 8 + .../js/socket-events/sessions_list.js.map | 1 + client/dist/js/socket-events/setting.js | 20 + client/dist/js/socket-events/setting.js.map | 1 + client/dist/js/socket-events/sign_out.js | 7 + client/dist/js/socket-events/sign_out.js.map | 1 + client/dist/js/socket-events/sync_sort.js | 20 + client/dist/js/socket-events/sync_sort.js.map | 1 + client/dist/js/socket-events/topic.js | 10 + client/dist/js/socket-events/topic.js.map | 1 + client/dist/js/socket-events/users.js | 15 + client/dist/js/socket-events/users.js.map | 1 + client/dist/js/socket.js | 14 + client/dist/js/socket.js.map | 1 + client/dist/js/store-settings.js | 97 ++ client/dist/js/store-settings.js.map | 1 + client/dist/js/store.js | 213 +++ client/dist/js/store.js.map | 1 + client/dist/js/upload.js | 227 +++ client/dist/js/upload.js.map | 1 + client/dist/js/vue.js | 106 ++ client/dist/js/vue.js.map | 1 + client/dist/js/webpush.js | 82 ++ client/dist/js/webpush.js.map | 1 + client/dist/service-worker.js | 155 +++ client/dist/service-worker.js.map | 1 + .../js/helpers/ircmessageparser/findLinks.ts | 81 ++ client/tsconfig.json | 10 +- package.json | 13 +- {src => scripts}/changelog.js | 0 src/command-line/index.ts | 12 +- src/command-line/upgrade.ts | 12 +- src/command-line/users/add.ts | 15 +- src/command-line/users/edit.ts | 15 +- src/command-line/users/list.ts | 9 +- src/command-line/users/remove.ts | 13 +- src/command-line/users/reset.ts | 15 +- src/helper.ts | 1 - src/log.ts | 2 +- src/plugins/{dev-server.js => dev-server.ts} | 8 +- src/plugins/inputs/action.ts | 2 +- .../{publicClient.js => publicClient.ts} | 16 +- src/plugins/{storage.js => storage.ts} | 23 +- src/tsconfig.json | 10 +- src/types/index.d.ts | 1 + test/plugins/sqlite.js | 2 +- tsconfig.json | 9 +- webpack.config.js | 12 +- yarn.lock | 1218 ++++++++++++++++- 170 files changed, 5527 insertions(+), 150 deletions(-) create mode 100644 client/dist/components/MessageTypes/index.js create mode 100644 client/dist/components/MessageTypes/index.js.map create mode 100644 client/dist/js/auth.js create mode 100644 client/dist/js/auth.js.map create mode 100644 client/dist/js/autocompletion.js create mode 100644 client/dist/js/autocompletion.js.map create mode 100644 client/dist/js/clipboard.js create mode 100644 client/dist/js/clipboard.js.map create mode 100644 client/dist/js/commands/collapse.js create mode 100644 client/dist/js/commands/collapse.js.map create mode 100644 client/dist/js/commands/expand.js create mode 100644 client/dist/js/commands/expand.js.map create mode 100644 client/dist/js/commands/index.js create mode 100644 client/dist/js/commands/index.js.map create mode 100644 client/dist/js/commands/join.js create mode 100644 client/dist/js/commands/join.js.map create mode 100644 client/dist/js/commands/search.js create mode 100644 client/dist/js/commands/search.js.map create mode 100644 client/dist/js/constants.js create mode 100644 client/dist/js/constants.js.map create mode 100644 client/dist/js/eventbus.js create mode 100644 client/dist/js/eventbus.js.map create mode 100644 client/dist/js/helpers/collapseNetwork.js create mode 100644 client/dist/js/helpers/collapseNetwork.js.map create mode 100644 client/dist/js/helpers/colorClass.js create mode 100644 client/dist/js/helpers/colorClass.js.map create mode 100644 client/dist/js/helpers/contextMenu.js create mode 100644 client/dist/js/helpers/contextMenu.js.map create mode 100644 client/dist/js/helpers/distance.js create mode 100644 client/dist/js/helpers/distance.js.map create mode 100644 client/dist/js/helpers/friendlysize.js create mode 100644 client/dist/js/helpers/friendlysize.js.map create mode 100644 client/dist/js/helpers/ircmessageparser/anyIntersection.js create mode 100644 client/dist/js/helpers/ircmessageparser/anyIntersection.js.map create mode 100644 client/dist/js/helpers/ircmessageparser/cleanIrcMessage.js create mode 100644 client/dist/js/helpers/ircmessageparser/cleanIrcMessage.js.map create mode 100644 client/dist/js/helpers/ircmessageparser/fill.js create mode 100644 client/dist/js/helpers/ircmessageparser/fill.js.map create mode 100644 client/dist/js/helpers/ircmessageparser/findChannels.js create mode 100644 client/dist/js/helpers/ircmessageparser/findChannels.js.map create mode 100644 client/dist/js/helpers/ircmessageparser/findEmoji.js create mode 100644 client/dist/js/helpers/ircmessageparser/findEmoji.js.map rename client/{ => dist}/js/helpers/ircmessageparser/findLinks.js (88%) create mode 100644 client/dist/js/helpers/ircmessageparser/findLinks.js.map create mode 100644 client/dist/js/helpers/ircmessageparser/findNames.js create mode 100644 client/dist/js/helpers/ircmessageparser/findNames.js.map create mode 100644 client/dist/js/helpers/ircmessageparser/merge.js create mode 100644 client/dist/js/helpers/ircmessageparser/merge.js.map create mode 100644 client/dist/js/helpers/ircmessageparser/parseStyle.js create mode 100644 client/dist/js/helpers/ircmessageparser/parseStyle.js.map create mode 100644 client/dist/js/helpers/isChannelCollapsed.js create mode 100644 client/dist/js/helpers/isChannelCollapsed.js.map create mode 100644 client/dist/js/helpers/isIgnoredKeybind.js create mode 100644 client/dist/js/helpers/isIgnoredKeybind.js.map create mode 100644 client/dist/js/helpers/listenForTwoFingerSwipes.js create mode 100644 client/dist/js/helpers/listenForTwoFingerSwipes.js.map create mode 100644 client/dist/js/helpers/localetime.js create mode 100644 client/dist/js/helpers/localetime.js.map create mode 100644 client/dist/js/helpers/parse.js create mode 100644 client/dist/js/helpers/parse.js.map create mode 100644 client/dist/js/helpers/parseIrcUri.js create mode 100644 client/dist/js/helpers/parseIrcUri.js.map create mode 100644 client/dist/js/helpers/roundBadgeNumber.js create mode 100644 client/dist/js/helpers/roundBadgeNumber.js.map create mode 100644 client/dist/js/keybinds.js create mode 100644 client/dist/js/keybinds.js.map create mode 100644 client/dist/js/loading-error-handlers.js create mode 100644 client/dist/js/loading-error-handlers.js.map create mode 100644 client/dist/js/localStorage.js create mode 100644 client/dist/js/localStorage.js.map create mode 100644 client/dist/js/location.js create mode 100644 client/dist/js/location.js.map create mode 100644 client/dist/js/router.js create mode 100644 client/dist/js/router.js.map create mode 100644 client/dist/js/settings.js create mode 100644 client/dist/js/settings.js.map create mode 100644 client/dist/js/socket-events/auth.js create mode 100644 client/dist/js/socket-events/auth.js.map create mode 100644 client/dist/js/socket-events/changelog.js create mode 100644 client/dist/js/socket-events/changelog.js.map create mode 100644 client/dist/js/socket-events/commands.js create mode 100644 client/dist/js/socket-events/commands.js.map create mode 100644 client/dist/js/socket-events/configuration.js create mode 100644 client/dist/js/socket-events/configuration.js.map create mode 100644 client/dist/js/socket-events/connection.js create mode 100644 client/dist/js/socket-events/connection.js.map create mode 100644 client/dist/js/socket-events/history_clear.js create mode 100644 client/dist/js/socket-events/history_clear.js.map create mode 100644 client/dist/js/socket-events/index.js create mode 100644 client/dist/js/socket-events/index.js.map create mode 100644 client/dist/js/socket-events/init.js create mode 100644 client/dist/js/socket-events/init.js.map create mode 100644 client/dist/js/socket-events/join.js create mode 100644 client/dist/js/socket-events/join.js.map create mode 100644 client/dist/js/socket-events/mentions.js create mode 100644 client/dist/js/socket-events/mentions.js.map create mode 100644 client/dist/js/socket-events/more.js create mode 100644 client/dist/js/socket-events/more.js.map create mode 100644 client/dist/js/socket-events/msg.js create mode 100644 client/dist/js/socket-events/msg.js.map create mode 100644 client/dist/js/socket-events/msg_preview.js create mode 100644 client/dist/js/socket-events/msg_preview.js.map create mode 100644 client/dist/js/socket-events/msg_special.js create mode 100644 client/dist/js/socket-events/msg_special.js.map create mode 100644 client/dist/js/socket-events/mute_changed.js create mode 100644 client/dist/js/socket-events/mute_changed.js.map create mode 100644 client/dist/js/socket-events/names.js create mode 100644 client/dist/js/socket-events/names.js.map create mode 100644 client/dist/js/socket-events/network.js create mode 100644 client/dist/js/socket-events/network.js.map create mode 100644 client/dist/js/socket-events/nick.js create mode 100644 client/dist/js/socket-events/nick.js.map create mode 100644 client/dist/js/socket-events/open.js create mode 100644 client/dist/js/socket-events/open.js.map create mode 100644 client/dist/js/socket-events/part.js create mode 100644 client/dist/js/socket-events/part.js.map create mode 100644 client/dist/js/socket-events/quit.js create mode 100644 client/dist/js/socket-events/quit.js.map create mode 100644 client/dist/js/socket-events/search.js create mode 100644 client/dist/js/socket-events/search.js.map create mode 100644 client/dist/js/socket-events/sessions_list.js create mode 100644 client/dist/js/socket-events/sessions_list.js.map create mode 100644 client/dist/js/socket-events/setting.js create mode 100644 client/dist/js/socket-events/setting.js.map create mode 100644 client/dist/js/socket-events/sign_out.js create mode 100644 client/dist/js/socket-events/sign_out.js.map create mode 100644 client/dist/js/socket-events/sync_sort.js create mode 100644 client/dist/js/socket-events/sync_sort.js.map create mode 100644 client/dist/js/socket-events/topic.js create mode 100644 client/dist/js/socket-events/topic.js.map create mode 100644 client/dist/js/socket-events/users.js create mode 100644 client/dist/js/socket-events/users.js.map create mode 100644 client/dist/js/socket.js create mode 100644 client/dist/js/socket.js.map create mode 100644 client/dist/js/store-settings.js create mode 100644 client/dist/js/store-settings.js.map create mode 100644 client/dist/js/store.js create mode 100644 client/dist/js/store.js.map create mode 100644 client/dist/js/upload.js create mode 100644 client/dist/js/upload.js.map create mode 100644 client/dist/js/vue.js create mode 100644 client/dist/js/vue.js.map create mode 100644 client/dist/js/webpush.js create mode 100644 client/dist/js/webpush.js.map create mode 100644 client/dist/service-worker.js create mode 100644 client/dist/service-worker.js.map create mode 100644 client/js/helpers/ircmessageparser/findLinks.ts rename {src => scripts}/changelog.js (100%) rename src/plugins/{dev-server.js => dev-server.ts} (84%) rename src/plugins/packages/{publicClient.js => publicClient.ts} (81%) rename src/plugins/{storage.js => storage.ts} (80%) diff --git a/.eslintrc.yml b/.eslintrc.yml index 7633ddbfbf..2f9a28ec7b 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -3,7 +3,7 @@ root: true parserOptions: ecmaVersion: 2022 - parser: "babel-eslint" + parser: "@babel/eslint-parser", env: es6: true diff --git a/client/dist/components/MessageTypes/index.js b/client/dist/components/MessageTypes/index.js new file mode 100644 index 0000000000..65e655e68f --- /dev/null +++ b/client/dist/components/MessageTypes/index.js @@ -0,0 +1,11 @@ +"use strict"; +// This creates a version of `require()` in the context of the current +// directory, so we iterate over its content, which is a map statically built by +// Webpack. +// Second argument says it's recursive, third makes sure we only load templates. +const requireViews = require.context(".", false, /\.vue$/); +export default requireViews.keys().reduce((acc, path) => { + acc["message-" + path.substring(2, path.length - 4)] = requireViews(path).default; + return acc; +}, {}); +//# sourceMappingURL=index.js.map diff --git a/client/dist/components/MessageTypes/index.js.map b/client/dist/components/MessageTypes/index.js.map new file mode 100644 index 0000000000..68d4c677a2 --- /dev/null +++ b/client/dist/components/MessageTypes/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../components/MessageTypes/index.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,sEAAsE;AACtE,gFAAgF;AAChF,WAAW;AACX,gFAAgF;AAChF,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AAE3D,eAAe,YAAY,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;IACvD,GAAG,CAAC,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC;IAElF,OAAO,GAAG,CAAC;AACZ,CAAC,EAAE,EAAE,CAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/auth.js b/client/dist/js/auth.js new file mode 100644 index 0000000000..8ff55573b4 --- /dev/null +++ b/client/dist/js/auth.js @@ -0,0 +1,10 @@ +"use strict"; +import storage from "./localStorage"; +import location from "./location"; +export default class Auth { + static signout() { + storage.clear(); + location.reload(); + } +} +//# sourceMappingURL=auth.js.map diff --git a/client/dist/js/auth.js.map b/client/dist/js/auth.js.map new file mode 100644 index 0000000000..e1ce58535f --- /dev/null +++ b/client/dist/js/auth.js.map @@ -0,0 +1 @@ +{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../js/auth.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,OAAO,MAAM,gBAAgB,CAAC;AACrC,OAAO,QAAQ,MAAM,YAAY,CAAC;AAElC,MAAM,CAAC,OAAO,OAAO,IAAI;IACxB,MAAM,CAAC,OAAO;QACb,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,QAAQ,CAAC,MAAM,EAAE,CAAC;IACnB,CAAC;CACD"} \ No newline at end of file diff --git a/client/dist/js/autocompletion.js b/client/dist/js/autocompletion.js new file mode 100644 index 0000000000..9d12a7ab54 --- /dev/null +++ b/client/dist/js/autocompletion.js @@ -0,0 +1,280 @@ +"use strict"; +const constants = require("./constants"); +import Mousetrap from "mousetrap"; +import {Textcomplete} from "@textcomplete/core/dist/Textcomplete"; +import {TextareaEditor} from "@textcomplete/textarea/dist/TextareaEditor"; +import fuzzy from "fuzzy"; +import emojiMap from "./helpers/simplemap.json"; +import store from "./store"; +export default enableAutocomplete; +const emojiSearchTerms = Object.keys(emojiMap); +const emojiStrategy = { + id: "emoji", + match: /(^|\s):([-+\w:?]{2,}):?$/, + search(term, callback) { + // Trim colon from the matched term, + // as we are unable to get a clean string from match regex + term = term.replace(/:$/, ""); + callback(fuzzyGrep(term, emojiSearchTerms)); + }, + template([string, original]) { + return `${emojiMap[original]} ${string}`; + }, + replace([, original]) { + return "$1" + emojiMap[original]; + }, + index: 2, +}; +const nicksStrategy = { + id: "nicks", + match: /(^|\s)(@([a-zA-Z_[\]\\^{}|`@][a-zA-Z0-9_[\]\\^{}|`-]*)?)$/, + search(term, callback) { + term = term.slice(1); + if (term[0] === "@") { + callback(completeNicks(term.slice(1), true).map((val) => ["@" + val[0], "@" + val[1]])); + } else { + callback(completeNicks(term, true)); + } + }, + template([string]) { + return string; + }, + replace([, original]) { + return "$1" + replaceNick(original); + }, + index: 2, +}; +const chanStrategy = { + id: "chans", + match: /(^|\s)((?:#|\+|&|![A-Z0-9]{5})(?:[^\s]+)?)$/, + search(term, callback) { + callback(completeChans(term)); + }, + template([string]) { + return string; + }, + replace([, original]) { + return "$1" + original; + }, + index: 2, +}; +const commandStrategy = { + id: "commands", + match: /^\/(\w*)$/, + search(term, callback) { + callback(completeCommands("/" + term)); + }, + template([string]) { + return string; + }, + replace([, original]) { + return original; + }, + index: 1, +}; +const foregroundColorStrategy = { + id: "foreground-colors", + match: /\x03(\d{0,2}|[A-Za-z ]{0,10})$/, + search(term, callback) { + term = term.toLowerCase(); + const matchingColorCodes = constants.colorCodeMap + .filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1])) + .map((i) => { + if (fuzzy.test(term, i[1])) { + return [ + i[0], + fuzzy.match(term, i[1], { + pre: "", + post: "", + }).rendered, + ]; + } + return i; + }); + callback(matchingColorCodes); + }, + template(value) { + return `${value[1]}`; + }, + replace(value) { + return "\x03" + value[0]; + }, + index: 1, +}; +const backgroundColorStrategy = { + id: "background-colors", + match: /\x03(\d{2}),(\d{0,2}|[A-Za-z ]{0,10})$/, + search(term, callback, match) { + term = term.toLowerCase(); + const matchingColorCodes = constants.colorCodeMap + .filter((i) => fuzzy.test(term, i[0]) || fuzzy.test(term, i[1])) + .map((pair) => { + if (fuzzy.test(term, pair[1])) { + return [ + pair[0], + fuzzy.match(term, pair[1], { + pre: "", + post: "", + }).rendered, + ]; + } + return pair; + }) + .map((pair) => pair.concat(match[1])); // Needed to pass fg color to `template`... + callback(matchingColorCodes); + }, + template(value) { + return `${value[1]}`; + }, + replace(value) { + return "\x03$1," + value[0]; + }, + index: 2, +}; +function enableAutocomplete(input) { + let tabCount = 0; + let lastMatch = ""; + let currentMatches = []; + input.addEventListener("input", (e) => { + if (e.detail === "autocomplete") { + return; + } + tabCount = 0; + currentMatches = []; + lastMatch = ""; + }); + Mousetrap(input).bind( + "tab", + (e) => { + if (store.state.isAutoCompleting) { + return; + } + e.preventDefault(); + const text = input.value; + if (tabCount === 0) { + lastMatch = text.substring(0, input.selectionStart).split(/\s/).pop(); + if (lastMatch.length === 0) { + return; + } + currentMatches = completeNicks(lastMatch, false); + if (currentMatches.length === 0) { + return; + } + } + const position = input.selectionStart - lastMatch.length; + const newMatch = replaceNick( + currentMatches[tabCount % currentMatches.length], + position + ); + const remainder = text.substr(input.selectionStart); + input.value = text.substr(0, position) + newMatch + remainder; + input.selectionStart -= remainder.length; + input.selectionEnd = input.selectionStart; + // Propagate change to Vue model + input.dispatchEvent( + new CustomEvent("input", { + detail: "autocomplete", + }) + ); + lastMatch = newMatch; + tabCount++; + }, + "keydown" + ); + const strategies = [ + emojiStrategy, + nicksStrategy, + chanStrategy, + commandStrategy, + foregroundColorStrategy, + backgroundColorStrategy, + ]; + const editor = new TextareaEditor(input); + const textcomplete = new Textcomplete(editor, strategies, { + dropdown: { + className: "textcomplete-menu", + placement: "top", + }, + }); + textcomplete.on("show", () => { + store.commit("isAutoCompleting", true); + }); + textcomplete.on("hidden", () => { + store.commit("isAutoCompleting", false); + }); + return { + hide() { + textcomplete.hide(); + }, + destroy() { + textcomplete.destroy(); + store.commit("isAutoCompleting", false); + }, + }; +} +function replaceNick(original, position = 1) { + // If no postfix specified, return autocompleted nick as-is + if (!store.state.settings.nickPostfix) { + return original; + } + // If there is whitespace in the input already, append space to nick + if (position > 0 && /\s/.test(store.state.activeChannel.channel.pendingMessage)) { + return original + " "; + } + // If nick is first in the input, append specified postfix + return original + store.state.settings.nickPostfix; +} +function fuzzyGrep(term, array) { + const results = fuzzy.filter(term, array, { + pre: "", + post: "", + }); + return results.map((el) => [el.string, el.original]); +} +function rawNicks() { + if (store.state.activeChannel.channel.users.length > 0) { + const users = store.state.activeChannel.channel.users.slice(); + return users.sort((a, b) => b.lastMessage - a.lastMessage).map((u) => u.nick); + } + const me = store.state.activeChannel.network.nick; + const otherUser = store.state.activeChannel.channel.name; + // If this is a query, add their name to autocomplete + if (me !== otherUser && store.state.activeChannel.channel.type === "query") { + return [otherUser, me]; + } + // Return our own name by default for anything that isn't a channel or query + return [me]; +} +function completeNicks(word, isFuzzy) { + const users = rawNicks(); + word = word.toLowerCase(); + if (isFuzzy) { + return fuzzyGrep(word, users); + } + return users.filter((w) => !w.toLowerCase().indexOf(word)); +} +function getCommands() { + let cmds = constants.commands.slice(); + if (!store.state.settings.searchEnabled) { + cmds = cmds.filter((c) => c !== "/search"); + } + return cmds; +} +function completeCommands(word) { + const commands = getCommands(); + return fuzzyGrep(word, commands); +} +function completeChans(word) { + const words = []; + for (const channel of store.state.activeChannel.network.channels) { + // Push all channels that start with the same CHANTYPE + if (channel.type === "channel" && channel.name[0] === word[0]) { + words.push(channel.name); + } + } + return fuzzyGrep(word, words); +} +//# sourceMappingURL=autocompletion.js.map diff --git a/client/dist/js/autocompletion.js.map b/client/dist/js/autocompletion.js.map new file mode 100644 index 0000000000..d90df641a0 --- /dev/null +++ b/client/dist/js/autocompletion.js.map @@ -0,0 +1 @@ +{"version":3,"file":"autocompletion.js","sourceRoot":"","sources":["../../js/autocompletion.js"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/client/dist/js/clipboard.js b/client/dist/js/clipboard.js new file mode 100644 index 0000000000..6a6a6bb815 --- /dev/null +++ b/client/dist/js/clipboard.js @@ -0,0 +1,25 @@ +"use strict"; +export default function (chat) { + // Disable in Firefox as it already copies flex text correctly + if (typeof window.InstallTrigger !== "undefined") { + return; + } + const selection = window.getSelection(); + // If selection does not span multiple elements, do nothing + if (selection.anchorNode === selection.focusNode) { + return; + } + const range = selection.getRangeAt(0); + const documentFragment = range.cloneContents(); + const div = document.createElement("div"); + div.id = "js-copy-hack"; + div.appendChild(documentFragment); + chat.appendChild(div); + selection.selectAllChildren(div); + window.setTimeout(() => { + chat.removeChild(div); + selection.removeAllRanges(); + selection.addRange(range); + }, 0); +} +//# sourceMappingURL=clipboard.js.map diff --git a/client/dist/js/clipboard.js.map b/client/dist/js/clipboard.js.map new file mode 100644 index 0000000000..d23987cb6e --- /dev/null +++ b/client/dist/js/clipboard.js.map @@ -0,0 +1 @@ +{"version":3,"file":"clipboard.js","sourceRoot":"","sources":["../../js/clipboard.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,MAAM,CAAC,OAAO,WAAW,IAAI;IAC5B,8DAA8D;IAC9D,IAAI,OAAO,MAAM,CAAC,cAAc,KAAK,WAAW,EAAE;QACjD,OAAO;KACP;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC;IAExC,2DAA2D;IAC3D,IAAI,SAAS,CAAC,UAAU,KAAK,SAAS,CAAC,SAAS,EAAE;QACjD,OAAO;KACP;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IACtC,MAAM,gBAAgB,GAAG,KAAK,CAAC,aAAa,EAAE,CAAC;IAC/C,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAE1C,GAAG,CAAC,EAAE,GAAG,cAAc,CAAC;IACxB,GAAG,CAAC,WAAW,CAAC,gBAAgB,CAAC,CAAC;IAClC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAEtB,SAAS,CAAC,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAEjC,MAAM,CAAC,UAAU,CAAC,GAAG,EAAE;QACtB,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACtB,SAAS,CAAC,eAAe,EAAE,CAAC;QAC5B,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC3B,CAAC,EAAE,CAAC,CAAC,CAAC;AACP,CAAC"} \ No newline at end of file diff --git a/client/dist/js/commands/collapse.js b/client/dist/js/commands/collapse.js new file mode 100644 index 0000000000..910fc24260 --- /dev/null +++ b/client/dist/js/commands/collapse.js @@ -0,0 +1,29 @@ +"use strict"; +import socket from "../socket"; +import store from "../store"; +function input() { + const messageIds = []; + for (const message of store.state.activeChannel.channel.messages) { + let toggled = false; + for (const preview of message.previews) { + if (preview.shown) { + preview.shown = false; + toggled = true; + } + } + if (toggled) { + messageIds.push(message.id); + } + } + // Tell the server we're toggling so it remembers at page reload + if (!document.body.classList.contains("public") && messageIds.length > 0) { + socket.emit("msg:preview:toggle", { + target: store.state.activeChannel.channel.id, + messageIds: messageIds, + shown: false, + }); + } + return true; +} +export default {input}; +//# sourceMappingURL=collapse.js.map diff --git a/client/dist/js/commands/collapse.js.map b/client/dist/js/commands/collapse.js.map new file mode 100644 index 0000000000..2781257a6e --- /dev/null +++ b/client/dist/js/commands/collapse.js.map @@ -0,0 +1 @@ +{"version":3,"file":"collapse.js","sourceRoot":"","sources":["../../../js/commands/collapse.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,MAAM,MAAM,WAAW,CAAC;AAC/B,OAAO,KAAK,MAAM,UAAU,CAAC;AAE7B,SAAS,KAAK;IACb,MAAM,UAAU,GAAG,EAAE,CAAC;IAEtB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE;QACjE,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE;YACvC,IAAI,OAAO,CAAC,KAAK,EAAE;gBAClB,OAAO,CAAC,KAAK,GAAG,KAAK,CAAC;gBACtB,OAAO,GAAG,IAAI,CAAC;aACf;SACD;QAED,IAAI,OAAO,EAAE;YACZ,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;SAC5B;KACD;IAED,gEAAgE;IAChE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE;QACzE,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;YACjC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE;YAC5C,UAAU,EAAE,UAAU;YACtB,KAAK,EAAE,KAAK;SACZ,CAAC,CAAC;KACH;IAED,OAAO,IAAI,CAAC;AACb,CAAC;AAED,eAAe,EAAC,KAAK,EAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/commands/expand.js b/client/dist/js/commands/expand.js new file mode 100644 index 0000000000..c1f8a1e940 --- /dev/null +++ b/client/dist/js/commands/expand.js @@ -0,0 +1,29 @@ +"use strict"; +import socket from "../socket"; +import store from "../store"; +function input() { + const messageIds = []; + for (const message of store.state.activeChannel.channel.messages) { + let toggled = false; + for (const preview of message.previews) { + if (!preview.shown) { + preview.shown = true; + toggled = true; + } + } + if (toggled) { + messageIds.push(message.id); + } + } + // Tell the server we're toggling so it remembers at page reload + if (!document.body.classList.contains("public") && messageIds.length > 0) { + socket.emit("msg:preview:toggle", { + target: store.state.activeChannel.channel.id, + messageIds: messageIds, + shown: true, + }); + } + return true; +} +export default {input}; +//# sourceMappingURL=expand.js.map diff --git a/client/dist/js/commands/expand.js.map b/client/dist/js/commands/expand.js.map new file mode 100644 index 0000000000..bba13c85e2 --- /dev/null +++ b/client/dist/js/commands/expand.js.map @@ -0,0 +1 @@ +{"version":3,"file":"expand.js","sourceRoot":"","sources":["../../../js/commands/expand.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,MAAM,MAAM,WAAW,CAAC;AAC/B,OAAO,KAAK,MAAM,UAAU,CAAC;AAE7B,SAAS,KAAK;IACb,MAAM,UAAU,GAAG,EAAE,CAAC;IAEtB,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,QAAQ,EAAE;QACjE,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE;YACvC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE;gBACnB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;gBACrB,OAAO,GAAG,IAAI,CAAC;aACf;SACD;QAED,IAAI,OAAO,EAAE;YACZ,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;SAC5B;KACD;IAED,gEAAgE;IAChE,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE;QACzE,MAAM,CAAC,IAAI,CAAC,oBAAoB,EAAE;YACjC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE;YAC5C,UAAU,EAAE,UAAU;YACtB,KAAK,EAAE,IAAI;SACX,CAAC,CAAC;KACH;IAED,OAAO,IAAI,CAAC;AACb,CAAC;AAED,eAAe,EAAC,KAAK,EAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/commands/index.js b/client/dist/js/commands/index.js new file mode 100644 index 0000000000..f326535064 --- /dev/null +++ b/client/dist/js/commands/index.js @@ -0,0 +1,16 @@ +"use strict"; +// Taken from views/index.js +// This creates a version of `require()` in the context of the current +// directory, so we iterate over its content, which is a map statically built by +// Webpack. +// Second argument says it's recursive, third makes sure we only load javascript. +const commands = require.context("./", true, /\.js$/); +export default commands.keys().reduce((acc, path) => { + const command = path.substring(2, path.length - 3); + if (command === "index") { + return acc; + } + acc[command] = commands(path).default; + return acc; +}, {}); +//# sourceMappingURL=index.js.map diff --git a/client/dist/js/commands/index.js.map b/client/dist/js/commands/index.js.map new file mode 100644 index 0000000000..53dace1ba7 --- /dev/null +++ b/client/dist/js/commands/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../js/commands/index.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,4BAA4B;AAE5B,sEAAsE;AACtE,gFAAgF;AAChF,WAAW;AACX,iFAAiF;AACjF,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;AAEtD,eAAe,QAAQ,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE;IACnD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAEnD,IAAI,OAAO,KAAK,OAAO,EAAE;QACxB,OAAO,GAAG,CAAC;KACX;IAED,GAAG,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC;IAEtC,OAAO,GAAG,CAAC;AACZ,CAAC,EAAE,EAAE,CAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/commands/join.js b/client/dist/js/commands/join.js new file mode 100644 index 0000000000..59dfb28111 --- /dev/null +++ b/client/dist/js/commands/join.js @@ -0,0 +1,40 @@ +"use strict"; +import socket from "../socket"; +import store from "../store"; +import {switchToChannel} from "../router"; +function input(args) { + if (args.length > 0) { + let channels = args[0]; + if (channels.length > 0) { + const chanTypes = store.state.activeChannel.network.serverOptions.CHANTYPES; + const channelList = args[0].split(","); + if (chanTypes && chanTypes.length > 0) { + for (let c = 0; c < channelList.length; c++) { + if (!chanTypes.includes(channelList[c][0])) { + channelList[c] = chanTypes[0] + channelList[c]; + } + } + } + channels = channelList.join(","); + const chan = store.getters.findChannelOnCurrentNetwork(channels); + if (chan) { + switchToChannel(chan); + } else { + socket.emit("input", { + text: `/join ${channels} ${args.length > 1 ? args[1] : ""}`, + target: store.state.activeChannel.channel.id, + }); + return true; + } + } + } else if (store.state.activeChannel.channel.type === "channel") { + // If `/join` command is used without any arguments, re-join current channel + socket.emit("input", { + target: store.state.activeChannel.channel.id, + text: `/join ${store.state.activeChannel.channel.name}`, + }); + return true; + } +} +export default {input}; +//# sourceMappingURL=join.js.map diff --git a/client/dist/js/commands/join.js.map b/client/dist/js/commands/join.js.map new file mode 100644 index 0000000000..8e105ed9d9 --- /dev/null +++ b/client/dist/js/commands/join.js.map @@ -0,0 +1 @@ +{"version":3,"file":"join.js","sourceRoot":"","sources":["../../../js/commands/join.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,MAAM,MAAM,WAAW,CAAC;AAC/B,OAAO,KAAK,MAAM,UAAU,CAAC;AAC7B,OAAO,EAAC,eAAe,EAAC,MAAM,WAAW,CAAC;AAE1C,SAAS,KAAK,CAAC,IAAI;IAClB,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE;QACpB,IAAI,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QAEvB,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;YACxB,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,aAAa,CAAC,SAAS,CAAC;YAC5E,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAEvC,IAAI,SAAS,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE;gBACtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;oBAC5C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;wBAC3C,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;qBAC/C;iBACD;aACD;YAED,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAEjC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,2BAA2B,CAAC,QAAQ,CAAC,CAAC;YAEjE,IAAI,IAAI,EAAE;gBACT,eAAe,CAAC,IAAI,CAAC,CAAC;aACtB;iBAAM;gBACN,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE;oBACpB,IAAI,EAAE,SAAS,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE;oBAC3D,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE;iBAC5C,CAAC,CAAC;gBAEH,OAAO,IAAI,CAAC;aACZ;SACD;KACD;SAAM,IAAI,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE;QAChE,4EAA4E;QAC5E,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE;YACpB,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE;YAC5C,IAAI,EAAE,SAAS,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE;SACvD,CAAC,CAAC;QAEH,OAAO,IAAI,CAAC;KACZ;AACF,CAAC;AAED,eAAe,EAAC,KAAK,EAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/commands/search.js b/client/dist/js/commands/search.js new file mode 100644 index 0000000000..8e2e72475e --- /dev/null +++ b/client/dist/js/commands/search.js @@ -0,0 +1,20 @@ +"use strict"; +import store from "../store"; +import {router} from "../router"; +function input(args) { + if (!store.state.settings.searchEnabled) { + return false; + } + router.push({ + name: "SearchResults", + params: { + id: store.state.activeChannel.channel.id, + }, + query: { + q: args.join(" "), + }, + }); + return true; +} +export default {input}; +//# sourceMappingURL=search.js.map diff --git a/client/dist/js/commands/search.js.map b/client/dist/js/commands/search.js.map new file mode 100644 index 0000000000..bad67269d8 --- /dev/null +++ b/client/dist/js/commands/search.js.map @@ -0,0 +1 @@ +{"version":3,"file":"search.js","sourceRoot":"","sources":["../../../js/commands/search.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,KAAK,MAAM,UAAU,CAAC;AAC7B,OAAO,EAAC,MAAM,EAAC,MAAM,WAAW,CAAC;AAEjC,SAAS,KAAK,CAAC,IAAI;IAClB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,EAAE;QACxC,OAAO,KAAK,CAAC;KACb;IAED,MAAM,CAAC,IAAI,CAAC;QACX,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE;YACP,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE;SACxC;QACD,KAAK,EAAE;YACN,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;SACjB;KACD,CAAC,CAAC;IAEH,OAAO,IAAI,CAAC;AACb,CAAC;AAED,eAAe,EAAC,KAAK,EAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/constants.js b/client/dist/js/constants.js new file mode 100644 index 0000000000..6dbfcf414f --- /dev/null +++ b/client/dist/js/constants.js @@ -0,0 +1,35 @@ +"use strict"; +const colorCodeMap = [ + ["00", "White"], + ["01", "Black"], + ["02", "Blue"], + ["03", "Green"], + ["04", "Red"], + ["05", "Brown"], + ["06", "Magenta"], + ["07", "Orange"], + ["08", "Yellow"], + ["09", "Light Green"], + ["10", "Cyan"], + ["11", "Light Cyan"], + ["12", "Light Blue"], + ["13", "Pink"], + ["14", "Grey"], + ["15", "Light Grey"], +]; +const condensedTypes = new Set(["chghost", "join", "part", "quit", "nick", "kick", "mode"]); +const timeFormats = { + msgDefault: "HH:mm", + msgWithSeconds: "HH:mm:ss", + msg12h: "hh:mm A", + msg12hWithSeconds: "hh:mm:ss A", +}; +export default { + colorCodeMap, + commands: [], + condensedTypes, + timeFormats, + // Same value as media query in CSS that forces sidebars to become overlays + mobileViewportPixels: 768, +}; +//# sourceMappingURL=constants.js.map diff --git a/client/dist/js/constants.js.map b/client/dist/js/constants.js.map new file mode 100644 index 0000000000..6b2257fefc --- /dev/null +++ b/client/dist/js/constants.js.map @@ -0,0 +1 @@ +{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../js/constants.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,MAAM,YAAY,GAAG;IACpB,CAAC,IAAI,EAAE,OAAO,CAAC;IACf,CAAC,IAAI,EAAE,OAAO,CAAC;IACf,CAAC,IAAI,EAAE,MAAM,CAAC;IACd,CAAC,IAAI,EAAE,OAAO,CAAC;IACf,CAAC,IAAI,EAAE,KAAK,CAAC;IACb,CAAC,IAAI,EAAE,OAAO,CAAC;IACf,CAAC,IAAI,EAAE,SAAS,CAAC;IACjB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAChB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAChB,CAAC,IAAI,EAAE,aAAa,CAAC;IACrB,CAAC,IAAI,EAAE,MAAM,CAAC;IACd,CAAC,IAAI,EAAE,YAAY,CAAC;IACpB,CAAC,IAAI,EAAE,YAAY,CAAC;IACpB,CAAC,IAAI,EAAE,MAAM,CAAC;IACd,CAAC,IAAI,EAAE,MAAM,CAAC;IACd,CAAC,IAAI,EAAE,YAAY,CAAC;CACpB,CAAC;AAEF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AAE5F,MAAM,WAAW,GAAG;IACnB,UAAU,EAAE,OAAO;IACnB,cAAc,EAAE,UAAU;IAC1B,MAAM,EAAE,SAAS;IACjB,iBAAiB,EAAE,YAAY;CAC/B,CAAC;AAEF,eAAe;IACd,YAAY;IACZ,QAAQ,EAAE,EAAE;IACZ,cAAc;IACd,WAAW;IACX,2EAA2E;IAC3E,oBAAoB,EAAE,GAAG;CACzB,CAAC"} \ No newline at end of file diff --git a/client/dist/js/eventbus.js b/client/dist/js/eventbus.js new file mode 100644 index 0000000000..a898b692f4 --- /dev/null +++ b/client/dist/js/eventbus.js @@ -0,0 +1,48 @@ +const events = new Map(); +class EventBus { + /** + * Register an event handler for the given type. + * + * @param {String} type Type of event to listen for. + * @param {Function} handler Function to call in response to given event. + */ + on(type, handler) { + if (events.has(type)) { + events.get(type).push(handler); + } else { + events.set(type, [handler]); + } + } + /** + * Remove an event handler for the given type. + * + * @param {String} type Type of event to unregister `handler` from. + * @param {Function} handler Handler function to remove. + */ + off(type, handler) { + if (events.has(type)) { + events.set( + type, + events.get(type).filter((item) => item !== handler) + ); + } + } + /** + * Invoke all handlers for the given type. + * + * @param {String} type The event type to invoke. + * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler. + */ + emit(type, ...evt) { + if (events.has(type)) { + events + .get(type) + .slice() + .map((handler) => { + handler(...evt); + }); + } + } +} +export default new EventBus(); +//# sourceMappingURL=eventbus.js.map diff --git a/client/dist/js/eventbus.js.map b/client/dist/js/eventbus.js.map new file mode 100644 index 0000000000..d66d7711e1 --- /dev/null +++ b/client/dist/js/eventbus.js.map @@ -0,0 +1 @@ +{"version":3,"file":"eventbus.js","sourceRoot":"","sources":["../../js/eventbus.js"],"names":[],"mappings":"AAAA,MAAM,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;AAEzB,MAAM,QAAQ;IACb;;;;;OAKG;IACH,EAAE,CAAC,IAAI,EAAE,OAAO;QACf,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YACrB,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;SAC/B;aAAM;YACN,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;SAC5B;IACF,CAAC;IAED;;;;;OAKG;IACH,GAAG,CAAC,IAAI,EAAE,OAAO;QAChB,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YACrB,MAAM,CAAC,GAAG,CACT,IAAI,EACJ,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,OAAO,CAAC,CACnD,CAAC;SACF;IACF,CAAC;IAED;;;;;OAKG;IACH,IAAI,CAAC,IAAI,EAAE,GAAG,GAAG;QAChB,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YACrB,MAAM;iBACJ,GAAG,CAAC,IAAI,CAAC;iBACT,KAAK,EAAE;iBACP,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;gBAChB,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;YACjB,CAAC,CAAC,CAAC;SACJ;IACF,CAAC;CACD;AAED,eAAe,IAAI,QAAQ,EAAE,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/collapseNetwork.js b/client/dist/js/helpers/collapseNetwork.js new file mode 100644 index 0000000000..1dbf507ed1 --- /dev/null +++ b/client/dist/js/helpers/collapseNetwork.js @@ -0,0 +1,13 @@ +"use strict"; +import storage from "../localStorage"; +export default (network, isCollapsed) => { + const networks = new Set(JSON.parse(storage.get("thelounge.networks.collapsed"))); + network.isCollapsed = isCollapsed; + if (isCollapsed) { + networks.add(network.uuid); + } else { + networks.delete(network.uuid); + } + storage.set("thelounge.networks.collapsed", JSON.stringify([...networks])); +}; +//# sourceMappingURL=collapseNetwork.js.map diff --git a/client/dist/js/helpers/collapseNetwork.js.map b/client/dist/js/helpers/collapseNetwork.js.map new file mode 100644 index 0000000000..08dd97fae6 --- /dev/null +++ b/client/dist/js/helpers/collapseNetwork.js.map @@ -0,0 +1 @@ +{"version":3,"file":"collapseNetwork.js","sourceRoot":"","sources":["../../../js/helpers/collapseNetwork.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,OAAO,MAAM,iBAAiB,CAAC;AAEtC,eAAe,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE;IACvC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC,CAAC,CAAC;IAClF,OAAO,CAAC,WAAW,GAAG,WAAW,CAAC;IAElC,IAAI,WAAW,EAAE;QAChB,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;KAC3B;SAAM;QACN,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;KAC9B;IAED,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC5E,CAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/colorClass.js b/client/dist/js/helpers/colorClass.js new file mode 100644 index 0000000000..2e62badb43 --- /dev/null +++ b/client/dist/js/helpers/colorClass.js @@ -0,0 +1,15 @@ +"use strict"; +// Generates a string from "color-1" to "color-32" based on an input string +export default (str) => { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash += str.charCodeAt(i); + } + /* + Modulo 32 lets us be case insensitive for ascii + due to A being ascii 65 (100 0001) + while a being ascii 97 (110 0001) + */ + return "color-" + (1 + (hash % 32)); +}; +//# sourceMappingURL=colorClass.js.map diff --git a/client/dist/js/helpers/colorClass.js.map b/client/dist/js/helpers/colorClass.js.map new file mode 100644 index 0000000000..0d382d86ac --- /dev/null +++ b/client/dist/js/helpers/colorClass.js.map @@ -0,0 +1 @@ +{"version":3,"file":"colorClass.js","sourceRoot":"","sources":["../../../js/helpers/colorClass.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,2EAA2E;AAC3E,eAAe,CAAC,GAAG,EAAE,EAAE;IACtB,IAAI,IAAI,GAAG,CAAC,CAAC;IAEb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACpC,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;KAC1B;IAED;;;;MAIE;IACF,OAAO,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;AACrC,CAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/contextMenu.js b/client/dist/js/helpers/contextMenu.js new file mode 100644 index 0000000000..ec18468680 --- /dev/null +++ b/client/dist/js/helpers/contextMenu.js @@ -0,0 +1,378 @@ +"use strict"; +import socket from "../socket"; +import eventbus from "../eventbus"; +export function generateChannelContextMenu($root, channel, network) { + const typeMap = { + lobby: "network", + channel: "chan", + query: "query", + special: "chan", + }; + const closeMap = { + lobby: "Remove", + channel: "Leave", + query: "Close", + special: "Close", + }; + let items = [ + { + label: channel.name, + type: "item", + class: typeMap[channel.type], + link: `/chan-${channel.id}`, + }, + { + type: "divider", + }, + ]; + // Add menu items for lobbies + if (channel.type === "lobby") { + items = [ + ...items, + { + label: "Edit this network…", + type: "item", + class: "edit", + link: `/edit-network/${network.uuid}`, + }, + { + label: "Join a channel…", + type: "item", + class: "join", + action: () => (network.isJoinChannelShown = true), + }, + { + label: "List all channels", + type: "item", + class: "list", + action: () => + socket.emit("input", { + target: channel.id, + text: "/list", + }), + }, + { + label: "List ignored users", + type: "item", + class: "list", + action: () => + socket.emit("input", { + target: channel.id, + text: "/ignorelist", + }), + }, + network.status.connected + ? { + label: "Disconnect", + type: "item", + class: "disconnect", + action: () => + socket.emit("input", { + target: channel.id, + text: "/disconnect", + }), + } + : { + label: "Connect", + type: "item", + class: "connect", + action: () => + socket.emit("input", { + target: channel.id, + text: "/connect", + }), + }, + ]; + } + // Add menu items for channels + if (channel.type === "channel") { + items.push({ + label: "Edit topic", + type: "item", + class: "edit", + action() { + channel.editTopic = true; + $root.switchToChannel(channel); + }, + }); + items.push({ + label: "List banned users", + type: "item", + class: "list", + action() { + socket.emit("input", { + target: channel.id, + text: "/banlist", + }); + }, + }); + } + // Add menu items for queries + if (channel.type === "query") { + items.push( + { + label: "User information", + type: "item", + class: "action-whois", + action() { + $root.switchToChannel(channel); + socket.emit("input", { + target: channel.id, + text: "/whois " + channel.name, + }); + }, + }, + { + label: "Ignore user", + type: "item", + class: "action-ignore", + action() { + socket.emit("input", { + target: channel.id, + text: "/ignore " + channel.name, + }); + }, + } + ); + } + if (channel.type === "channel" || channel.type === "query") { + items.push({ + label: "Clear history", + type: "item", + class: "clear-history", + action() { + eventbus.emit( + "confirm-dialog", + { + title: "Clear history", + text: `Are you sure you want to clear history for ${channel.name}? This cannot be undone.`, + button: "Clear history", + }, + (result) => { + if (!result) { + return; + } + socket.emit("history:clear", { + target: channel.id, + }); + } + ); + }, + }); + } + const humanFriendlyChanTypeMap = { + lobby: "network", + channel: "channel", + query: "conversation", + }; + // We don't allow the muting of ChanType.SPECIAL channels + const mutableChanTypes = Object.keys(humanFriendlyChanTypeMap); + if (mutableChanTypes.includes(channel.type)) { + const chanType = humanFriendlyChanTypeMap[channel.type]; + items.push({ + label: channel.muted ? `Unmute ${chanType}` : `Mute ${chanType}`, + type: "item", + class: "mute", + action() { + socket.emit("mute:change", { + target: channel.id, + setMutedTo: !channel.muted, + }); + }, + }); + } + // Add close menu item + items.push({ + label: closeMap[channel.type], + type: "item", + class: "close", + action() { + $root.closeChannel(channel); + }, + }); + return items; +} +export function generateInlineChannelContextMenu($root, chan, network) { + const join = () => { + const channel = network.channels.find((c) => c.name === chan); + if (channel) { + $root.switchToChannel(channel); + } + socket.emit("input", { + target: $root.$store.state.activeChannel.channel.id, + text: "/join " + chan, + }); + }; + const channel = network.channels.find((c) => c.name === chan); + if (channel) { + return [ + { + label: "Go to channel", + type: "item", + class: "chan", + link: `/chan-${channel.id}`, + }, + ]; + } + return [ + { + label: "Join channel", + type: "item", + class: "join", + action: join, + }, + ]; +} +export function generateUserContextMenu($root, channel, network, user) { + const currentChannelUser = channel + ? channel.users.find((u) => u.nick === network.nick) || {} + : {}; + const whois = () => { + const chan = network.channels.find((c) => c.name === user.nick); + if (chan) { + $root.switchToChannel(chan); + } + socket.emit("input", { + target: channel.id, + text: "/whois " + user.nick, + }); + }; + const items = [ + { + label: user.nick, + type: "item", + class: "user", + action: whois, + }, + { + type: "divider", + }, + { + label: "User information", + type: "item", + class: "action-whois", + action: whois, + }, + { + label: "Ignore user", + type: "item", + class: "action-ignore", + action() { + socket.emit("input", { + target: channel.id, + text: "/ignore " + user.nick, + }); + }, + }, + { + label: "Direct messages", + type: "item", + class: "action-query", + action() { + const chan = $root.$store.getters.findChannelOnCurrentNetwork(user.nick); + if (chan) { + $root.switchToChannel(chan); + } + socket.emit("input", { + target: channel.id, + text: "/query " + user.nick, + }); + }, + }, + ]; + // Bail because we're in a query or we don't have a special mode. + if (!currentChannelUser.modes || currentChannelUser.modes.length < 1) { + return items; + } + // Names of the standard modes we are able to change + const modeCharToName = { + "~": "owner", + "&": "admin", + "@": "operator", + "%": "half-op", + "+": "voice", + }; + // Labels for the mode changes. For example .rev({mode: "a", symbol: "&"}) => 'Revoke admin (-a)' + const modeTextTemplate = { + revoke(m) { + const name = modeCharToName[m.symbol]; + const res = name ? `Revoke ${name} (-${m.mode})` : `Mode -${m.mode}`; + return res; + }, + give(m) { + const name = modeCharToName[m.symbol]; + const res = name ? `Give ${name} (+${m.mode})` : `Mode +${m.mode}`; + return res; + }, + }; + const networkModeSymbols = network.serverOptions.PREFIX.symbols; + /** + * Determine whether the prefix of mode p1 has access to perform actions on p2. + * + * EXAMPLE: + * compare('@', '@') => true + * compare('&', '@') => true + * compare('+', '~') => false + * @param {string} p1 The mode performing an action + * @param {string} p2 The target mode + * + * @return {boolean} whether p1 can perform an action on p2 + */ + function compare(p1, p2) { + // The modes ~ and @ can perform actions on their own mode. The others on modes below. + return "~@".indexOf(p1) > -1 + ? networkModeSymbols.indexOf(p1) <= networkModeSymbols.indexOf(p2) + : networkModeSymbols.indexOf(p1) < networkModeSymbols.indexOf(p2); + } + network.serverOptions.PREFIX.prefix.forEach((mode) => { + if (!compare(currentChannelUser.modes[0], mode.symbol)) { + // Our highest mode is below the current mode. Bail. + return; + } + if (!user.modes.includes(mode.symbol)) { + // The target doesn't already have this mode, therefore we can set it. + items.push({ + label: modeTextTemplate.give(mode), + type: "item", + class: "action-set-mode", + action() { + socket.emit("input", { + target: channel.id, + text: "/mode +" + mode.mode + " " + user.nick, + }); + }, + }); + } else { + items.push({ + label: modeTextTemplate.revoke(mode), + type: "item", + class: "action-revoke-mode", + action() { + socket.emit("input", { + target: channel.id, + text: "/mode -" + mode.mode + " " + user.nick, + }); + }, + }); + } + }); + // Determine if we are half-op or op depending on the network modes so we can kick. + if (!compare(networkModeSymbols.indexOf("%") > -1 ? "%" : "@", currentChannelUser.modes[0])) { + // Check if the target user has no mode or a mode lower than ours. + if (user.modes.length === 0 || compare(currentChannelUser.modes[0], user.modes[0])) { + items.push({ + label: "Kick", + type: "item", + class: "action-kick", + action() { + socket.emit("input", { + target: channel.id, + text: "/kick " + user.nick, + }); + }, + }); + } + } + return items; +} +//# sourceMappingURL=contextMenu.js.map diff --git a/client/dist/js/helpers/contextMenu.js.map b/client/dist/js/helpers/contextMenu.js.map new file mode 100644 index 0000000000..7278317740 --- /dev/null +++ b/client/dist/js/helpers/contextMenu.js.map @@ -0,0 +1 @@ +{"version":3,"file":"contextMenu.js","sourceRoot":"","sources":["../../../js/helpers/contextMenu.js"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/client/dist/js/helpers/distance.js b/client/dist/js/helpers/distance.js new file mode 100644 index 0000000000..670693cad8 --- /dev/null +++ b/client/dist/js/helpers/distance.js @@ -0,0 +1,5 @@ +function distance([x1, y1], [x2, y2]) { + return Math.hypot(x1 - x2, y1 - y2); +} +export default distance; +//# sourceMappingURL=distance.js.map diff --git a/client/dist/js/helpers/distance.js.map b/client/dist/js/helpers/distance.js.map new file mode 100644 index 0000000000..02f5b8d92c --- /dev/null +++ b/client/dist/js/helpers/distance.js.map @@ -0,0 +1 @@ +{"version":3,"file":"distance.js","sourceRoot":"","sources":["../../../js/helpers/distance.js"],"names":[],"mappings":"AAAA,SAAS,QAAQ,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;IACnC,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,eAAe,QAAQ,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/friendlysize.js b/client/dist/js/helpers/friendlysize.js new file mode 100644 index 0000000000..db1a4686df --- /dev/null +++ b/client/dist/js/helpers/friendlysize.js @@ -0,0 +1,9 @@ +"use strict"; +const sizes = ["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB"]; +export default (size) => { + // Loosely inspired from https://stackoverflow.com/a/18650828/1935861 + const i = size > 0 ? Math.floor(Math.log(size) / Math.log(1024)) : 0; + const fixedSize = parseFloat((size / Math.pow(1024, i)).toFixed(1)); + return `${fixedSize} ${sizes[i]}`; +}; +//# sourceMappingURL=friendlysize.js.map diff --git a/client/dist/js/helpers/friendlysize.js.map b/client/dist/js/helpers/friendlysize.js.map new file mode 100644 index 0000000000..f7255291cb --- /dev/null +++ b/client/dist/js/helpers/friendlysize.js.map @@ -0,0 +1 @@ +{"version":3,"file":"friendlysize.js","sourceRoot":"","sources":["../../../js/helpers/friendlysize.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;AAE3D,eAAe,CAAC,IAAI,EAAE,EAAE;IACvB,qEAAqE;IACrE,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;IACpE,OAAO,GAAG,SAAS,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACnC,CAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/ircmessageparser/anyIntersection.js b/client/dist/js/helpers/ircmessageparser/anyIntersection.js new file mode 100644 index 0000000000..9c041e6ac6 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/anyIntersection.js @@ -0,0 +1,13 @@ +"use strict"; +// Return true if any section of "a" or "b" parts (defined by their start/end +// markers) intersect each other, false otherwise. +function anyIntersection(a, b) { + return ( + (a.start <= b.start && b.start < a.end) || + (a.start < b.end && b.end <= a.end) || + (b.start <= a.start && a.start < b.end) || + (b.start < a.end && a.end <= b.end) + ); +} +export default anyIntersection; +//# sourceMappingURL=anyIntersection.js.map diff --git a/client/dist/js/helpers/ircmessageparser/anyIntersection.js.map b/client/dist/js/helpers/ircmessageparser/anyIntersection.js.map new file mode 100644 index 0000000000..97b2bb3c3c --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/anyIntersection.js.map @@ -0,0 +1 @@ +{"version":3,"file":"anyIntersection.js","sourceRoot":"","sources":["../../../../js/helpers/ircmessageparser/anyIntersection.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,6EAA6E;AAC7E,kDAAkD;AAClD,SAAS,eAAe,CAAC,CAAC,EAAE,CAAC;IAC5B,OAAO,CACN,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC;QACvC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC;QACnC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC;QACvC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CACnC,CAAC;AACH,CAAC;AAED,eAAe,eAAe,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/ircmessageparser/cleanIrcMessage.js b/client/dist/js/helpers/ircmessageparser/cleanIrcMessage.js new file mode 100644 index 0000000000..c612fef66b --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/cleanIrcMessage.js @@ -0,0 +1,5 @@ +"use strict"; +const matchFormatting = + /\x02|\x1D|\x1F|\x16|\x0F|\x11|\x1E|\x03(?:[0-9]{1,2}(?:,[0-9]{1,2})?)?|\x04(?:[0-9a-f]{6}(?:,[0-9a-f]{6})?)?/gi; +module.exports = (message) => message.replace(matchFormatting, "").trim(); +//# sourceMappingURL=cleanIrcMessage.js.map diff --git a/client/dist/js/helpers/ircmessageparser/cleanIrcMessage.js.map b/client/dist/js/helpers/ircmessageparser/cleanIrcMessage.js.map new file mode 100644 index 0000000000..5bc423d4e6 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/cleanIrcMessage.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cleanIrcMessage.js","sourceRoot":"","sources":["../../../../js/helpers/ircmessageparser/cleanIrcMessage.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,MAAM,eAAe,GACpB,gHAAgH,CAAC;AAElH,MAAM,CAAC,OAAO,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/ircmessageparser/fill.js b/client/dist/js/helpers/ircmessageparser/fill.js new file mode 100644 index 0000000000..0c82cb2699 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/fill.js @@ -0,0 +1,30 @@ +"use strict"; +// Create plain text entries corresponding to areas of the text that match no +// existing entries. Returns an empty array if all parts of the text have been +// parsed into recognizable entries already. +function fill(existingEntries, text) { + let position = 0; + // Fill inner parts of the text. For example, if text is `foobarbaz` and both + // `foo` and `baz` have matched into an entry, this will return a dummy entry + // corresponding to `bar`. + const result = existingEntries.reduce((acc, textSegment) => { + if (textSegment.start > position) { + acc.push({ + start: position, + end: textSegment.start, + }); + } + position = textSegment.end; + return acc; + }, []); + // Complete the unmatched end of the text with a dummy entry + if (position < text.length) { + result.push({ + start: position, + end: text.length, + }); + } + return result; +} +export default fill; +//# sourceMappingURL=fill.js.map diff --git a/client/dist/js/helpers/ircmessageparser/fill.js.map b/client/dist/js/helpers/ircmessageparser/fill.js.map new file mode 100644 index 0000000000..c85edec671 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/fill.js.map @@ -0,0 +1 @@ +{"version":3,"file":"fill.js","sourceRoot":"","sources":["../../../../js/helpers/ircmessageparser/fill.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,6EAA6E;AAC7E,8EAA8E;AAC9E,4CAA4C;AAC5C,SAAS,IAAI,CAAC,eAAe,EAAE,IAAI;IAClC,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,6EAA6E;IAC7E,6EAA6E;IAC7E,0BAA0B;IAC1B,MAAM,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE;QAC1D,IAAI,WAAW,CAAC,KAAK,GAAG,QAAQ,EAAE;YACjC,GAAG,CAAC,IAAI,CAAC;gBACR,KAAK,EAAE,QAAQ;gBACf,GAAG,EAAE,WAAW,CAAC,KAAK;aACtB,CAAC,CAAC;SACH;QAED,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC;QAC3B,OAAO,GAAG,CAAC;IACZ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,4DAA4D;IAC5D,IAAI,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE;QAC3B,MAAM,CAAC,IAAI,CAAC;YACX,KAAK,EAAE,QAAQ;YACf,GAAG,EAAE,IAAI,CAAC,MAAM;SAChB,CAAC,CAAC;KACH;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED,eAAe,IAAI,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/ircmessageparser/findChannels.js b/client/dist/js/helpers/ircmessageparser/findChannels.js new file mode 100644 index 0000000000..694e187ff5 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/findChannels.js @@ -0,0 +1,37 @@ +"use strict"; +// Escapes the RegExp special characters "^", "$", "", ".", "*", "+", "?", "(", +// ")", "[", "]", "{", "}", and "|" in string. +// See https://lodash.com/docs/#escapeRegExp +import escapeRegExp from "lodash/escapeRegExp"; +// Given an array of channel prefixes (such as "#" and "&") and an array of user +// modes (such as "@" and "+"), this function extracts channels and nicks from a +// text. +// It returns an array of objects for each channel found with their start index, +// end index and channel name. +function findChannels(text, channelPrefixes, userModes) { + // `userModePattern` is necessary to ignore user modes in /whois responses. + // For example, a voiced user in #thelounge will have a /whois response of: + // > foo is on the following channels: +#thelounge + // We need to explicitly ignore user modes to parse such channels correctly. + const userModePattern = userModes.map(escapeRegExp).join(""); + const channelPrefixPattern = channelPrefixes.map(escapeRegExp).join(""); + const channelPattern = `(?:^|\\s)[${userModePattern}]*([${channelPrefixPattern}][^ \u0007]+)`; + const channelRegExp = new RegExp(channelPattern, "g"); + const result = []; + let match; + do { + // With global ("g") regexes, calling `exec` multiple times will find + // successive matches in the same string. + match = channelRegExp.exec(text); + if (match) { + result.push({ + start: match.index + match[0].length - match[1].length, + end: match.index + match[0].length, + channel: match[1], + }); + } + } while (match); + return result; +} +export default findChannels; +//# sourceMappingURL=findChannels.js.map diff --git a/client/dist/js/helpers/ircmessageparser/findChannels.js.map b/client/dist/js/helpers/ircmessageparser/findChannels.js.map new file mode 100644 index 0000000000..c26c9407c2 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/findChannels.js.map @@ -0,0 +1 @@ +{"version":3,"file":"findChannels.js","sourceRoot":"","sources":["../../../../js/helpers/ircmessageparser/findChannels.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,+EAA+E;AAC/E,8CAA8C;AAC9C,4CAA4C;AAC5C,OAAO,YAAY,MAAM,qBAAqB,CAAC;AAE/C,gFAAgF;AAChF,gFAAgF;AAChF,QAAQ;AACR,gFAAgF;AAChF,8BAA8B;AAC9B,SAAS,YAAY,CAAC,IAAI,EAAE,eAAe,EAAE,SAAS;IACrD,2EAA2E;IAC3E,2EAA2E;IAC3E,kDAAkD;IAClD,4EAA4E;IAC5E,MAAM,eAAe,GAAG,SAAS,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC7D,MAAM,oBAAoB,GAAG,eAAe,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACxE,MAAM,cAAc,GAAG,aAAa,eAAe,OAAO,oBAAoB,eAAe,CAAC;IAC9F,MAAM,aAAa,GAAG,IAAI,MAAM,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IAEtD,MAAM,MAAM,GAAG,EAAE,CAAC;IAClB,IAAI,KAAK,CAAC;IAEV,GAAG;QACF,qEAAqE;QACrE,yCAAyC;QACzC,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEjC,IAAI,KAAK,EAAE;YACV,MAAM,CAAC,IAAI,CAAC;gBACX,KAAK,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM;gBACtD,GAAG,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM;gBAClC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;aACjB,CAAC,CAAC;SACH;KACD,QAAQ,KAAK,EAAE;IAEhB,OAAO,MAAM,CAAC;AACf,CAAC;AAED,eAAe,YAAY,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/ircmessageparser/findEmoji.js b/client/dist/js/helpers/ircmessageparser/findEmoji.js new file mode 100644 index 0000000000..08bd843d74 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/findEmoji.js @@ -0,0 +1,16 @@ +"use strict"; +const emojiRegExp = require("emoji-regex")(); +function findEmoji(text) { + const result = []; + let match; + while ((match = emojiRegExp.exec(text))) { + result.push({ + start: match.index, + end: match.index + match[0].length, + emoji: match[0], + }); + } + return result; +} +export default findEmoji; +//# sourceMappingURL=findEmoji.js.map diff --git a/client/dist/js/helpers/ircmessageparser/findEmoji.js.map b/client/dist/js/helpers/ircmessageparser/findEmoji.js.map new file mode 100644 index 0000000000..cbfb0c730d --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/findEmoji.js.map @@ -0,0 +1 @@ +{"version":3,"file":"findEmoji.js","sourceRoot":"","sources":["../../../../js/helpers/ircmessageparser/findEmoji.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,MAAM,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;AAE7C,SAAS,SAAS,CAAC,IAAI;IACtB,MAAM,MAAM,GAAG,EAAE,CAAC;IAClB,IAAI,KAAK,CAAC;IAEV,OAAO,CAAC,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE;QACxC,MAAM,CAAC,IAAI,CAAC;YACX,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,GAAG,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM;YAClC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;SACf,CAAC,CAAC;KACH;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED,eAAe,SAAS,CAAC"} \ No newline at end of file diff --git a/client/js/helpers/ircmessageparser/findLinks.js b/client/dist/js/helpers/ircmessageparser/findLinks.js similarity index 88% rename from client/js/helpers/ircmessageparser/findLinks.js rename to client/dist/js/helpers/ircmessageparser/findLinks.js index b35a3efe05..00c3bc374e 100644 --- a/client/js/helpers/ircmessageparser/findLinks.js +++ b/client/dist/js/helpers/ircmessageparser/findLinks.js @@ -1,27 +1,23 @@ "use strict"; - -const LinkifyIt = require("linkify-it"); - +import LinkifyIt from "linkify-it"; LinkifyIt.prototype.normalize = function normalize(match) { if (!match.schema) { match.schema = "http:"; match.url = "http://" + match.url; + //@ts-ignore match.noschema = true; } - if (match.schema === "//") { match.schema = "http:"; match.url = "http:" + match.url; + //@ts-ignore match.noschema = true; } - if (match.schema === "mailto:" && !/^mailto:/i.test(match.url)) { match.url = "mailto:" + match.url; } }; - const linkify = LinkifyIt().tlds(require("tlds")).tlds("onion", true); - // Known schemes to detect in text const commonSchemes = [ "sftp", @@ -39,31 +35,23 @@ const commonSchemes = [ "gopher", "gemini", ]; - for (const schema of commonSchemes) { linkify.add(schema + ":", "http:"); } - function findLinks(text) { const matches = linkify.match(text); - if (!matches) { return []; } - return matches.map(returnUrl); } - function findLinksWithSchema(text) { const matches = linkify.match(text); - if (!matches) { return []; } - return matches.filter((url) => !url.noschema).map(returnUrl); } - function returnUrl(url) { return { start: url.index, @@ -71,8 +59,5 @@ function returnUrl(url) { link: url.url, }; } - -module.exports = { - findLinks, - findLinksWithSchema, -}; +export {findLinks, findLinksWithSchema}; +//# sourceMappingURL=findLinks.js.map diff --git a/client/dist/js/helpers/ircmessageparser/findLinks.js.map b/client/dist/js/helpers/ircmessageparser/findLinks.js.map new file mode 100644 index 0000000000..e939235ed2 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/findLinks.js.map @@ -0,0 +1 @@ +{"version":3,"file":"findLinks.js","sourceRoot":"","sources":["../../../../js/helpers/ircmessageparser/findLinks.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,SAAoB,MAAM,YAAY,CAAA;AAM7C,SAAS,CAAC,SAAS,CAAC,SAAS,GAAG,SAAS,SAAS,CAAC,KAAe;IACjE,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;QAClB,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC;QACvB,KAAK,CAAC,GAAG,GAAG,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC;QAClC,YAAY;QACZ,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;KACtB;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE;QAC1B,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC;QACvB,KAAK,CAAC,GAAG,GAAG,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC;QAChC,YAAY;QACZ,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;KACtB;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;QAC/D,KAAK,CAAC,GAAG,GAAG,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC;KAClC;AACF,CAAC,CAAC;AAEF,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AAEtE,kCAAkC;AAClC,MAAM,aAAa,GAAG;IACrB,MAAM;IACN,KAAK;IACL,MAAM;IACN,KAAK;IACL,MAAM;IACN,KAAK;IACL,KAAK;IACL,OAAO;IACP,QAAQ;IACR,WAAW;IACX,SAAS;IACT,KAAK;IACL,QAAQ;IACR,QAAQ;CACR,CAAC;AAEF,KAAK,MAAM,MAAM,IAAI,aAAa,EAAE;IACnC,OAAO,CAAC,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,SAAS,SAAS,CAAC,IAAY;IAC9B,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAe,CAAC;IAElD,IAAI,CAAC,OAAO,EAAE;QACb,OAAO,EAAE,CAAC;KACV;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC/B,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAY;IACxC,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAe,CAAC;IAElD,IAAI,CAAC,OAAO,EAAE;QACb,OAAO,EAAE,CAAC;KACV;IAED,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,SAAS,CAAC,GAAa;IAC/B,OAAO;QACN,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,GAAG,EAAE,GAAG,CAAC,SAAS;QAClB,IAAI,EAAE,GAAG,CAAC,GAAG;KACb,CAAC;AACH,CAAC;AAED,OAAO,EACN,SAAS,EACT,mBAAmB,GACnB,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/ircmessageparser/findNames.js b/client/dist/js/helpers/ircmessageparser/findNames.js new file mode 100644 index 0000000000..89688e2bd0 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/findNames.js @@ -0,0 +1,22 @@ +"use strict"; +const nickRegExp = /([\w[\]\\`^{|}-]+)/g; +function findNames(text, users) { + const result = []; + // Return early if we don't have any nicknames to find + if (users.length === 0) { + return result; + } + let match; + while ((match = nickRegExp.exec(text))) { + if (users.indexOf(match[1]) > -1) { + result.push({ + start: match.index, + end: match.index + match[1].length, + nick: match[1], + }); + } + } + return result; +} +export default findNames; +//# sourceMappingURL=findNames.js.map diff --git a/client/dist/js/helpers/ircmessageparser/findNames.js.map b/client/dist/js/helpers/ircmessageparser/findNames.js.map new file mode 100644 index 0000000000..bdddf9dce2 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/findNames.js.map @@ -0,0 +1 @@ +{"version":3,"file":"findNames.js","sourceRoot":"","sources":["../../../../js/helpers/ircmessageparser/findNames.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAEzC,SAAS,SAAS,CAAC,IAAI,EAAE,KAAK;IAC7B,MAAM,MAAM,GAAG,EAAE,CAAC;IAElB,sDAAsD;IACtD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;QACvB,OAAO,MAAM,CAAC;KACd;IAED,IAAI,KAAK,CAAC;IAEV,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE;QACvC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE;YACjC,MAAM,CAAC,IAAI,CAAC;gBACX,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,GAAG,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM;gBAClC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;aACd,CAAC,CAAC;SACH;KACD;IAED,OAAO,MAAM,CAAC;AACf,CAAC;AAED,eAAe,SAAS,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/ircmessageparser/merge.js b/client/dist/js/helpers/ircmessageparser/merge.js new file mode 100644 index 0000000000..8d0a6faa4f --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/merge.js @@ -0,0 +1,44 @@ +"use strict"; +import anyIntersection from "./anyIntersection"; +import fill from "./fill"; +// Merge text part information within a styling fragment +function assign(textPart, fragment) { + const fragStart = fragment.start; + const start = Math.max(fragment.start, textPart.start); + const end = Math.min(fragment.end, textPart.end); + const text = fragment.text.slice(start - fragStart, end - fragStart); + return Object.assign({}, fragment, {start, end, text}); +} +function sortParts(a, b) { + return a.start - b.start || b.end - a.end; +} +// Merge the style fragments within the text parts, taking into account +// boundaries and text sections that have not matched to links or channels. +// For example, given a string "foobar" where "foo" and "bar" have been +// identified as parts (channels, links, etc.) and "fo", "ob" and "ar" have 3 +// different styles, the first resulting part will contain fragments "fo" and +// "o", and the second resulting part will contain "b" and "ar". "o" and "b" +// fragments will contain duplicate styling attributes. +function merge(textParts, styleFragments, cleanText) { + // Remove overlapping parts + textParts = textParts.sort(sortParts).reduce((prev, curr) => { + const intersection = prev.some((p) => anyIntersection(p, curr)); + if (intersection) { + return prev; + } + return prev.concat([curr]); + }, []); + // Every section of the original text that has not been captured in a "part" + // is filled with "text" parts, dummy objects with start/end but no extra + // metadata. + const allParts = textParts.concat(fill(textParts, cleanText)).sort(sortParts); // Sort all parts identified based on their position in the original text + // Distribute the style fragments within the text parts + return allParts.map((textPart) => { + textPart.fragments = styleFragments + .filter((fragment) => anyIntersection(textPart, fragment)) + .map((fragment) => assign(textPart, fragment)); + return textPart; + }); +} +export default merge; +//# sourceMappingURL=merge.js.map diff --git a/client/dist/js/helpers/ircmessageparser/merge.js.map b/client/dist/js/helpers/ircmessageparser/merge.js.map new file mode 100644 index 0000000000..50e81779d2 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/merge.js.map @@ -0,0 +1 @@ +{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../../js/helpers/ircmessageparser/merge.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,IAAI,MAAM,QAAQ,CAAC;AAE1B,wDAAwD;AACxD,SAAS,MAAM,CAAC,QAAQ,EAAE,QAAQ;IACjC,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,SAAS,EAAE,GAAG,GAAG,SAAS,CAAC,CAAC;IAErE,OAAO,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,EAAE,EAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAC,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,SAAS,CAAC,CAAC,EAAE,CAAC;IACtB,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC;AAC3C,CAAC;AAED,uEAAuE;AACvE,2EAA2E;AAC3E,uEAAuE;AACvE,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,uDAAuD;AACvD,SAAS,KAAK,CAAC,SAAS,EAAE,cAAc,EAAE,SAAS;IAClD,2BAA2B;IAC3B,SAAS,GAAG,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QAC3D,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAEhE,IAAI,YAAY,EAAE;YACjB,OAAO,IAAI,CAAC;SACZ;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5B,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,4EAA4E;IAC5E,yEAAyE;IACzE,YAAY;IACZ,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,yEAAyE;IAExJ,uDAAuD;IACvD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;QAChC,QAAQ,CAAC,SAAS,GAAG,cAAc;aACjC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;aACzD,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;QAEhD,OAAO,QAAQ,CAAC;IACjB,CAAC,CAAC,CAAC;AACJ,CAAC;AAED,eAAe,KAAK,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/ircmessageparser/parseStyle.js b/client/dist/js/helpers/ircmessageparser/parseStyle.js new file mode 100644 index 0000000000..0b2faf54b3 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/parseStyle.js @@ -0,0 +1,198 @@ +"use strict"; +// Styling control codes +const BOLD = "\x02"; +const COLOR = "\x03"; +const HEX_COLOR = "\x04"; +const RESET = "\x0f"; +const REVERSE = "\x16"; +const ITALIC = "\x1d"; +const UNDERLINE = "\x1f"; +const STRIKETHROUGH = "\x1e"; +const MONOSPACE = "\x11"; +// Color code matcher, with format `XX,YY` where both `XX` and `YY` are +// integers, `XX` is the text color and `YY` is an optional background color. +const colorRx = /^(\d{1,2})(?:,(\d{1,2}))?/; +// 6-char Hex color code matcher +const hexColorRx = /^([0-9a-f]{6})(?:,([0-9a-f]{6}))?/i; +// Represents all other control codes that to be ignored/filtered from the text +// This regex allows line feed character +const controlCodesRx = /[\u0000-\u0009\u000B-\u001F]/g; +// Converts a given text into an array of objects, each of them representing a +// similarly styled section of the text. Each object carries the `text`, style +// information (`bold`, `textColor`, `bgcolor`, `italic`, +// `underline`, `strikethrough`, `monospace`), and `start`/`end` cursors. +function parseStyle(text) { + const result = []; + let start = 0; + let position = 0; + // At any given time, these carry style information since last time a styling + // control code was met. + let colorCodes, + bold, + textColor, + bgColor, + hexColor, + hexBgColor, + italic, + underline, + strikethrough, + monospace; + const resetStyle = () => { + bold = false; + textColor = undefined; + bgColor = undefined; + hexColor = undefined; + hexBgColor = undefined; + italic = false; + underline = false; + strikethrough = false; + monospace = false; + }; + resetStyle(); + // When called, this "closes" the current fragment by adding an entry to the + // `result` array using the styling information set last time a control code + // was met. + const emitFragment = () => { + // Uses the text fragment starting from the last control code position up to + // the current position + const textPart = text.slice(start, position); + // Filters out all non-style related control codes present in this text + const processedText = textPart.replace(controlCodesRx, " "); + if (processedText.length) { + // Current fragment starts where the previous one ends, or at 0 if none + const fragmentStart = result.length ? result[result.length - 1].end : 0; + result.push({ + bold, + textColor, + bgColor, + hexColor, + hexBgColor, + italic, + underline, + strikethrough, + monospace, + text: processedText, + start: fragmentStart, + end: fragmentStart + processedText.length, + }); + } + // Now that a fragment has been "closed", the next one will start after that + start = position + 1; + }; + // This loop goes through each character of the given text one by one by + // bumping the `position` cursor. Every time a new special "styling" character + // is met, an object gets created (with `emitFragment()`)information on text + // encountered since the previous styling character. + while (position < text.length) { + switch (text[position]) { + case RESET: + emitFragment(); + resetStyle(); + break; + // Meeting a BOLD character means that the ongoing text is either going to + // be in bold or that the previous one was in bold and the following one + // must be reset. + // This same behavior applies to COLOR, REVERSE, ITALIC, and UNDERLINE. + case BOLD: + emitFragment(); + bold = !bold; + break; + case COLOR: + emitFragment(); + // Go one step further to find the corresponding color + colorCodes = text.slice(position + 1).match(colorRx); + if (colorCodes) { + textColor = Number(colorCodes[1]); + if (colorCodes[2]) { + bgColor = Number(colorCodes[2]); + } + // Color code length is > 1, so bump the current position cursor by as + // much (and reset the start cursor for the current text block as well) + position += colorCodes[0].length; + start = position + 1; + } else { + // If no color codes were found, toggles back to no colors (like BOLD). + textColor = undefined; + bgColor = undefined; + } + break; + case HEX_COLOR: + emitFragment(); + colorCodes = text.slice(position + 1).match(hexColorRx); + if (colorCodes) { + hexColor = colorCodes[1].toUpperCase(); + if (colorCodes[2]) { + hexBgColor = colorCodes[2].toUpperCase(); + } + // Color code length is > 1, so bump the current position cursor by as + // much (and reset the start cursor for the current text block as well) + position += colorCodes[0].length; + start = position + 1; + } else { + // If no color codes were found, toggles back to no colors (like BOLD). + hexColor = undefined; + hexBgColor = undefined; + } + break; + case REVERSE: { + emitFragment(); + const tmp = bgColor; + bgColor = textColor; + textColor = tmp; + break; + } + case ITALIC: + emitFragment(); + italic = !italic; + break; + case UNDERLINE: + emitFragment(); + underline = !underline; + break; + case STRIKETHROUGH: + emitFragment(); + strikethrough = !strikethrough; + break; + case MONOSPACE: + emitFragment(); + monospace = !monospace; + break; + } + // Evaluate the next character at the next iteration + position += 1; + } + // The entire text has been parsed, so we finalize the current text fragment. + emitFragment(); + return result; +} +const properties = [ + "bold", + "textColor", + "bgColor", + "hexColor", + "hexBgColor", + "italic", + "underline", + "strikethrough", + "monospace", +]; +function prepare(text) { + return ( + parseStyle(text) + // This optimizes fragments by combining them together when all their values + // for the properties defined above are equal. + .reduce((prev, curr) => { + if (prev.length) { + const lastEntry = prev[prev.length - 1]; + if (properties.every((key) => curr[key] === lastEntry[key])) { + lastEntry.text += curr.text; + lastEntry.end += curr.text.length; + return prev; + } + } + return prev.concat([curr]); + }, []) + ); +} +export default prepare; +//# sourceMappingURL=parseStyle.js.map diff --git a/client/dist/js/helpers/ircmessageparser/parseStyle.js.map b/client/dist/js/helpers/ircmessageparser/parseStyle.js.map new file mode 100644 index 0000000000..92b24a6d49 --- /dev/null +++ b/client/dist/js/helpers/ircmessageparser/parseStyle.js.map @@ -0,0 +1 @@ +{"version":3,"file":"parseStyle.js","sourceRoot":"","sources":["../../../../js/helpers/ircmessageparser/parseStyle.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,wBAAwB;AACxB,MAAM,IAAI,GAAG,MAAM,CAAC;AACpB,MAAM,KAAK,GAAG,MAAM,CAAC;AACrB,MAAM,SAAS,GAAG,MAAM,CAAC;AACzB,MAAM,KAAK,GAAG,MAAM,CAAC;AACrB,MAAM,OAAO,GAAG,MAAM,CAAC;AACvB,MAAM,MAAM,GAAG,MAAM,CAAC;AACtB,MAAM,SAAS,GAAG,MAAM,CAAC;AACzB,MAAM,aAAa,GAAG,MAAM,CAAC;AAC7B,MAAM,SAAS,GAAG,MAAM,CAAC;AAEzB,uEAAuE;AACvE,6EAA6E;AAC7E,MAAM,OAAO,GAAG,2BAA2B,CAAC;AAE5C,gCAAgC;AAChC,MAAM,UAAU,GAAG,oCAAoC,CAAC;AAExD,+EAA+E;AAC/E,wCAAwC;AACxC,MAAM,cAAc,GAAG,+BAA+B,CAAC;AAEvD,8EAA8E;AAC9E,8EAA8E;AAC9E,yDAAyD;AACzD,yEAAyE;AACzE,SAAS,UAAU,CAAC,IAAI;IACvB,MAAM,MAAM,GAAG,EAAE,CAAC;IAClB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,6EAA6E;IAC7E,wBAAwB;IACxB,IAAI,UAAU,EACb,IAAI,EACJ,SAAS,EACT,OAAO,EACP,QAAQ,EACR,UAAU,EACV,MAAM,EACN,SAAS,EACT,aAAa,EACb,SAAS,CAAC;IAEX,MAAM,UAAU,GAAG,GAAG,EAAE;QACvB,IAAI,GAAG,KAAK,CAAC;QACb,SAAS,GAAG,SAAS,CAAC;QACtB,OAAO,GAAG,SAAS,CAAC;QACpB,QAAQ,GAAG,SAAS,CAAC;QACrB,UAAU,GAAG,SAAS,CAAC;QACvB,MAAM,GAAG,KAAK,CAAC;QACf,SAAS,GAAG,KAAK,CAAC;QAClB,aAAa,GAAG,KAAK,CAAC;QACtB,SAAS,GAAG,KAAK,CAAC;IACnB,CAAC,CAAC;IAEF,UAAU,EAAE,CAAC;IAEb,4EAA4E;IAC5E,4EAA4E;IAC5E,WAAW;IACX,MAAM,YAAY,GAAG,GAAG,EAAE;QACzB,4EAA4E;QAC5E,uBAAuB;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QAE7C,uEAAuE;QACvE,MAAM,aAAa,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;QAE5D,IAAI,aAAa,CAAC,MAAM,EAAE;YACzB,uEAAuE;YACvE,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAExE,MAAM,CAAC,IAAI,CAAC;gBACX,IAAI;gBACJ,SAAS;gBACT,OAAO;gBACP,QAAQ;gBACR,UAAU;gBACV,MAAM;gBACN,SAAS;gBACT,aAAa;gBACb,SAAS;gBACT,IAAI,EAAE,aAAa;gBACnB,KAAK,EAAE,aAAa;gBACpB,GAAG,EAAE,aAAa,GAAG,aAAa,CAAC,MAAM;aACzC,CAAC,CAAC;SACH;QAED,4EAA4E;QAC5E,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC;IACtB,CAAC,CAAC;IAEF,wEAAwE;IACxE,8EAA8E;IAC9E,4EAA4E;IAC5E,oDAAoD;IACpD,OAAO,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE;QAC9B,QAAQ,IAAI,CAAC,QAAQ,CAAC,EAAE;YACvB,KAAK,KAAK;gBACT,YAAY,EAAE,CAAC;gBACf,UAAU,EAAE,CAAC;gBACb,MAAM;YAEP,0EAA0E;YAC1E,wEAAwE;YACxE,iBAAiB;YACjB,uEAAuE;YACvE,KAAK,IAAI;gBACR,YAAY,EAAE,CAAC;gBACf,IAAI,GAAG,CAAC,IAAI,CAAC;gBACb,MAAM;YAEP,KAAK,KAAK;gBACT,YAAY,EAAE,CAAC;gBAEf,sDAAsD;gBACtD,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAErD,IAAI,UAAU,EAAE;oBACf,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;oBAElC,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE;wBAClB,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;qBAChC;oBAED,sEAAsE;oBACtE,uEAAuE;oBACvE,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;oBACjC,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC;iBACrB;qBAAM;oBACN,uEAAuE;oBACvE,SAAS,GAAG,SAAS,CAAC;oBACtB,OAAO,GAAG,SAAS,CAAC;iBACpB;gBAED,MAAM;YAEP,KAAK,SAAS;gBACb,YAAY,EAAE,CAAC;gBAEf,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;gBAExD,IAAI,UAAU,EAAE;oBACf,QAAQ,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;oBAEvC,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE;wBAClB,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;qBACzC;oBAED,sEAAsE;oBACtE,uEAAuE;oBACvE,QAAQ,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;oBACjC,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC;iBACrB;qBAAM;oBACN,uEAAuE;oBACvE,QAAQ,GAAG,SAAS,CAAC;oBACrB,UAAU,GAAG,SAAS,CAAC;iBACvB;gBAED,MAAM;YAEP,KAAK,OAAO,CAAC,CAAC;gBACb,YAAY,EAAE,CAAC;gBACf,MAAM,GAAG,GAAG,OAAO,CAAC;gBACpB,OAAO,GAAG,SAAS,CAAC;gBACpB,SAAS,GAAG,GAAG,CAAC;gBAChB,MAAM;aACN;YAED,KAAK,MAAM;gBACV,YAAY,EAAE,CAAC;gBACf,MAAM,GAAG,CAAC,MAAM,CAAC;gBACjB,MAAM;YAEP,KAAK,SAAS;gBACb,YAAY,EAAE,CAAC;gBACf,SAAS,GAAG,CAAC,SAAS,CAAC;gBACvB,MAAM;YAEP,KAAK,aAAa;gBACjB,YAAY,EAAE,CAAC;gBACf,aAAa,GAAG,CAAC,aAAa,CAAC;gBAC/B,MAAM;YAEP,KAAK,SAAS;gBACb,YAAY,EAAE,CAAC;gBACf,SAAS,GAAG,CAAC,SAAS,CAAC;gBACvB,MAAM;SACP;QAED,oDAAoD;QACpD,QAAQ,IAAI,CAAC,CAAC;KACd;IAED,6EAA6E;IAC7E,YAAY,EAAE,CAAC;IAEf,OAAO,MAAM,CAAC;AACf,CAAC;AAED,MAAM,UAAU,GAAG;IAClB,MAAM;IACN,WAAW;IACX,SAAS;IACT,UAAU;IACV,YAAY;IACZ,QAAQ;IACR,WAAW;IACX,eAAe;IACf,WAAW;CACX,CAAC;AAEF,SAAS,OAAO,CAAC,IAAI;IACpB,OAAO,CACN,UAAU,CAAC,IAAI,CAAC;QACf,4EAA4E;QAC5E,8CAA8C;SAC7C,MAAM,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE;QACtB,IAAI,IAAI,CAAC,MAAM,EAAE;YAChB,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YAExC,IAAI,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE;gBAC5D,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC;gBAC5B,SAAS,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;gBAClC,OAAO,IAAI,CAAC;aACZ;SACD;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5B,CAAC,EAAE,EAAE,CAAC,CACP,CAAC;AACH,CAAC;AAED,eAAe,OAAO,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/isChannelCollapsed.js b/client/dist/js/helpers/isChannelCollapsed.js new file mode 100644 index 0000000000..d0b6d3ffae --- /dev/null +++ b/client/dist/js/helpers/isChannelCollapsed.js @@ -0,0 +1,12 @@ +"use strict"; +import store from "../store"; +export default (network, channel) => { + if (!network.isCollapsed || channel.highlight || channel.type === "lobby") { + return false; + } + if (store.state.activeChannel && channel === store.state.activeChannel.channel) { + return false; + } + return true; +}; +//# sourceMappingURL=isChannelCollapsed.js.map diff --git a/client/dist/js/helpers/isChannelCollapsed.js.map b/client/dist/js/helpers/isChannelCollapsed.js.map new file mode 100644 index 0000000000..b56884ca39 --- /dev/null +++ b/client/dist/js/helpers/isChannelCollapsed.js.map @@ -0,0 +1 @@ +{"version":3,"file":"isChannelCollapsed.js","sourceRoot":"","sources":["../../../js/helpers/isChannelCollapsed.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,KAAK,MAAM,UAAU,CAAC;AAE7B,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE;IACnC,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE;QAC1E,OAAO,KAAK,CAAC;KACb;IAED,IAAI,KAAK,CAAC,KAAK,CAAC,aAAa,IAAI,OAAO,KAAK,KAAK,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,EAAE;QAC/E,OAAO,KAAK,CAAC;KACb;IAED,OAAO,IAAI,CAAC;AACb,CAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/isIgnoredKeybind.js b/client/dist/js/helpers/isIgnoredKeybind.js new file mode 100644 index 0000000000..47e710e8d1 --- /dev/null +++ b/client/dist/js/helpers/isIgnoredKeybind.js @@ -0,0 +1,11 @@ +"use strict"; +export default (event) => { + if (event.target.tagName !== "TEXTAREA" && event.target.tagName !== "INPUT") { + return false; + } + // If focus is in a textarea, do not handle keybinds if user has typed anything + // This is done to prevent keyboard layout binds conflicting with ours + // For example alt+shift+left on macos selects a word + return !!event.target.value; +}; +//# sourceMappingURL=isIgnoredKeybind.js.map diff --git a/client/dist/js/helpers/isIgnoredKeybind.js.map b/client/dist/js/helpers/isIgnoredKeybind.js.map new file mode 100644 index 0000000000..1a66dd7d91 --- /dev/null +++ b/client/dist/js/helpers/isIgnoredKeybind.js.map @@ -0,0 +1 @@ +{"version":3,"file":"isIgnoredKeybind.js","sourceRoot":"","sources":["../../../js/helpers/isIgnoredKeybind.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,eAAe,CAAC,KAAK,EAAE,EAAE;IACxB,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,KAAK,UAAU,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,KAAK,OAAO,EAAE;QAC5E,OAAO,KAAK,CAAC;KACb;IAED,+EAA+E;IAC/E,sEAAsE;IACtE,qDAAqD;IACrD,OAAO,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC;AAC7B,CAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/listenForTwoFingerSwipes.js b/client/dist/js/helpers/listenForTwoFingerSwipes.js new file mode 100644 index 0000000000..249fb96aae --- /dev/null +++ b/client/dist/js/helpers/listenForTwoFingerSwipes.js @@ -0,0 +1,85 @@ +"use strict"; +import distance from "./distance"; +// onTwoFingerSwipe will be called with a cardinal direction ("n", "e", "s" or +// "w") as its only argument. +function listenForTwoFingerSwipes(onTwoFingerSwipe) { + let history = []; + document.body.addEventListener( + "touchmove", + function (event) { + if (event.touches.length !== 2) { + return; + } + const a = event.touches.item(0); + const b = event.touches.item(1); + const timestamp = window.performance.now(); + const center = [(a.screenX + b.screenX) / 2, (a.screenY + b.screenY) / 2]; + if (history.length > 0) { + const last = history[history.length - 1]; + const centersAreEqual = + last.center[0] === center[0] && last.center[1] === center[1]; + if (last.timestamp === timestamp || centersAreEqual) { + // Touches with the same timestamps or center don't help us + // see the speed of movement. Ignore them. + return; + } + } + history.push({timestamp, center}); + }, + {passive: true} + ); + document.body.addEventListener( + "touchend", + function (event) { + if (event.touches.length >= 2) { + return; + } + try { + const direction = getSwipe(history); + if (direction) { + onTwoFingerSwipe(direction); + } + } finally { + history = []; + } + }, + {passive: true} + ); + document.body.addEventListener( + "touchcancel", + function () { + history = []; + }, + {passive: true} + ); +} +// Returns the cardinal direction of the swipe or null if there is no swipe. +function getSwipe(hist) { + // Speed is in pixels/millisecond. Must be maintained throughout swipe. + const MIN_SWIPE_SPEED = 0.2; + if (hist.length < 2) { + return null; + } + for (let i = 1; i < hist.length; ++i) { + const previous = hist[i - 1]; + const current = hist[i]; + const speed = + distance(previous.center, current.center) / + Math.abs(previous.timestamp - current.timestamp); + if (speed < MIN_SWIPE_SPEED) { + return null; + } + } + return getCardinalDirection(hist[0].center, hist[hist.length - 1].center); +} +function getCardinalDirection([x1, y1], [x2, y2]) { + // If θ is the angle of the vector then this is tan(θ) + const tangent = (y2 - y1) / (x2 - x1); + // All values of |tan(-45° to 45°)| are less than 1, same for 145° to 225° + if (Math.abs(tangent) < 1) { + return x1 < x2 ? "e" : "w"; + } + return y1 < y2 ? "s" : "n"; +} +export default listenForTwoFingerSwipes; +//# sourceMappingURL=listenForTwoFingerSwipes.js.map diff --git a/client/dist/js/helpers/listenForTwoFingerSwipes.js.map b/client/dist/js/helpers/listenForTwoFingerSwipes.js.map new file mode 100644 index 0000000000..44adb07bc0 --- /dev/null +++ b/client/dist/js/helpers/listenForTwoFingerSwipes.js.map @@ -0,0 +1 @@ +{"version":3,"file":"listenForTwoFingerSwipes.js","sourceRoot":"","sources":["../../../js/helpers/listenForTwoFingerSwipes.js"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/client/dist/js/helpers/localetime.js b/client/dist/js/helpers/localetime.js new file mode 100644 index 0000000000..a42f3f8dd0 --- /dev/null +++ b/client/dist/js/helpers/localetime.js @@ -0,0 +1,4 @@ +"use strict"; +import dayjs from "dayjs"; +export default (time) => dayjs(time).format("D MMMM YYYY, HH:mm:ss"); +//# sourceMappingURL=localetime.js.map diff --git a/client/dist/js/helpers/localetime.js.map b/client/dist/js/helpers/localetime.js.map new file mode 100644 index 0000000000..8d3e38b11d --- /dev/null +++ b/client/dist/js/helpers/localetime.js.map @@ -0,0 +1 @@ +{"version":3,"file":"localetime.js","sourceRoot":"","sources":["../../../js/helpers/localetime.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/parse.js b/client/dist/js/helpers/parse.js new file mode 100644 index 0000000000..86a7e17896 --- /dev/null +++ b/client/dist/js/helpers/parse.js @@ -0,0 +1,177 @@ +"use strict"; +import parseStyle from "./ircmessageparser/parseStyle"; +import findChannels from "./ircmessageparser/findChannels"; +import {findLinks} from "./ircmessageparser/findLinks"; +import findEmoji from "./ircmessageparser/findEmoji"; +import findNames from "./ircmessageparser/findNames"; +import merge from "./ircmessageparser/merge"; +import emojiMap from "./fullnamemap.json"; +import LinkPreviewToggle from "../../components/LinkPreviewToggle.vue"; +import LinkPreviewFileSize from "../../components/LinkPreviewFileSize.vue"; +import InlineChannel from "../../components/InlineChannel.vue"; +import Username from "../../components/Username.vue"; +const emojiModifiersRegex = /[\u{1f3fb}-\u{1f3ff}]|\u{fe0f}/gu; +// Create an HTML `span` with styling information for a given fragment +function createFragment(fragment, createElement) { + const classes = []; + if (fragment.bold) { + classes.push("irc-bold"); + } + if (fragment.textColor !== undefined) { + classes.push("irc-fg" + fragment.textColor); + } + if (fragment.bgColor !== undefined) { + classes.push("irc-bg" + fragment.bgColor); + } + if (fragment.italic) { + classes.push("irc-italic"); + } + if (fragment.underline) { + classes.push("irc-underline"); + } + if (fragment.strikethrough) { + classes.push("irc-strikethrough"); + } + if (fragment.monospace) { + classes.push("irc-monospace"); + } + const data = {}; + let hasData = false; + if (classes.length > 0) { + hasData = true; + data.class = classes; + } + if (fragment.hexColor) { + hasData = true; + data.style = { + color: `#${fragment.hexColor}`, + }; + if (fragment.hexBgColor) { + data.style["background-color"] = `#${fragment.hexBgColor}`; + } + } + return hasData ? createElement("span", data, fragment.text) : fragment.text; +} +// Transform an IRC message potentially filled with styling control codes, URLs, +// nicknames, and channels into a string of HTML elements to display on the client. +function parse(createElement, text, message = undefined, network = undefined) { + // Extract the styling information and get the plain text version from it + const styleFragments = parseStyle(text); + const cleanText = styleFragments.map((fragment) => fragment.text).join(""); + // On the plain text, find channels and URLs, returned as "parts". Parts are + // arrays of objects containing start and end markers, as well as metadata + // depending on what was found (channel or link). + const channelPrefixes = network ? network.serverOptions.CHANTYPES : ["#", "&"]; + const userModes = network?.serverOptions?.PREFIX.symbols || ["!", "@", "%", "+"]; + const channelParts = findChannels(cleanText, channelPrefixes, userModes); + const linkParts = findLinks(cleanText); + const emojiParts = findEmoji(cleanText); + const nameParts = findNames(cleanText, message ? message.users || [] : []); + const parts = channelParts.concat(linkParts).concat(emojiParts).concat(nameParts); + // The channel the message belongs to might not exist if the user isn't joined to it. + const messageChannel = message ? message.channel : null; + // Merge the styling information with the channels / URLs / nicks / text objects and + // generate HTML strings with the resulting fragments + return merge(parts, styleFragments, cleanText).map((textPart) => { + const fragments = textPart.fragments.map((fragment) => + createFragment(fragment, createElement) + ); + // Wrap these potentially styled fragments with links and channel buttons + if (textPart.link) { + const preview = + message && + message.previews && + message.previews.find((p) => p.link === textPart.link); + const link = createElement( + "a", + { + attrs: { + href: textPart.link, + dir: preview ? null : "auto", + target: "_blank", + rel: "noopener", + }, + }, + fragments + ); + if (!preview) { + return link; + } + const linkEls = [link]; + if (preview.size > 0) { + linkEls.push( + createElement(LinkPreviewFileSize, { + props: { + size: preview.size, + }, + }) + ); + } + linkEls.push( + createElement(LinkPreviewToggle, { + props: { + link: preview, + }, + }) + ); + // We wrap the link, size, and the toggle button into + // to correctly keep the left-to-right order of these elements + return createElement( + "span", + { + attrs: { + dir: "auto", + }, + }, + linkEls + ); + } else if (textPart.channel) { + return createElement( + InlineChannel, + { + props: { + channel: textPart.channel, + }, + }, + fragments + ); + } else if (textPart.emoji) { + const emojiWithoutModifiers = textPart.emoji.replace(emojiModifiersRegex, ""); + const title = emojiMap[emojiWithoutModifiers] + ? `Emoji: ${emojiMap[emojiWithoutModifiers]}` + : null; + return createElement( + "span", + { + class: ["emoji"], + attrs: { + role: "img", + "aria-label": title, + title: title, + }, + }, + fragments + ); + } else if (textPart.nick) { + return createElement( + Username, + { + props: { + user: { + nick: textPart.nick, + }, + channel: messageChannel, + network, + }, + attrs: { + dir: "auto", + }, + }, + fragments + ); + } + return fragments; + }); +} +export default parse; +//# sourceMappingURL=parse.js.map diff --git a/client/dist/js/helpers/parse.js.map b/client/dist/js/helpers/parse.js.map new file mode 100644 index 0000000000..66912fa811 --- /dev/null +++ b/client/dist/js/helpers/parse.js.map @@ -0,0 +1 @@ +{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../../js/helpers/parse.js"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/client/dist/js/helpers/parseIrcUri.js b/client/dist/js/helpers/parseIrcUri.js new file mode 100644 index 0000000000..909eacb14d --- /dev/null +++ b/client/dist/js/helpers/parseIrcUri.js @@ -0,0 +1,43 @@ +"use strict"; +export default (stringUri) => { + const data = {}; + try { + // https://tools.ietf.org/html/draft-butcher-irc-url-04 + const uri = new URL(stringUri); + // Replace protocol with a "special protocol" (that's what it's called in WHATWG spec) + // So that the uri can be properly parsed + if (uri.protocol === "irc:") { + uri.protocol = "http:"; + if (!uri.port) { + uri.port = 6667; + } + data.tls = false; + } else if (uri.protocol === "ircs:") { + uri.protocol = "https:"; + if (!uri.port) { + uri.port = 6697; + } + data.tls = true; + } else { + return; + } + if (!uri.hostname) { + return {}; + } + data.host = data.name = uri.hostname; + data.port = uri.port; + let channel = ""; + if (uri.pathname.length > 1) { + channel = uri.pathname.substr(1); // Remove slash + } + if (uri.hash.length > 1) { + channel += uri.hash; + } + // We don't split channels or append # here because the connect window takes care of that + data.join = channel; + } catch (e) { + // do nothing on invalid uri + } + return data; +}; +//# sourceMappingURL=parseIrcUri.js.map diff --git a/client/dist/js/helpers/parseIrcUri.js.map b/client/dist/js/helpers/parseIrcUri.js.map new file mode 100644 index 0000000000..8735220a75 --- /dev/null +++ b/client/dist/js/helpers/parseIrcUri.js.map @@ -0,0 +1 @@ +{"version":3,"file":"parseIrcUri.js","sourceRoot":"","sources":["../../../js/helpers/parseIrcUri.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,eAAe,CAAC,SAAS,EAAE,EAAE;IAC5B,MAAM,IAAI,GAAG,EAAE,CAAC;IAEhB,IAAI;QACH,uDAAuD;QACvD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;QAE/B,sFAAsF;QACtF,yCAAyC;QACzC,IAAI,GAAG,CAAC,QAAQ,KAAK,MAAM,EAAE;YAC5B,GAAG,CAAC,QAAQ,GAAG,OAAO,CAAC;YAEvB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;gBACd,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;aAChB;YAED,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC;SACjB;aAAM,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,EAAE;YACpC,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAExB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE;gBACd,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;aAChB;YAED,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC;SAChB;aAAM;YACN,OAAO;SACP;QAED,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE;YAClB,OAAO,EAAE,CAAC;SACV;QAED,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,QAAQ,CAAC;QACrC,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QAErB,IAAI,OAAO,GAAG,EAAE,CAAC;QAEjB,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;YAC5B,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe;SACjD;QAED,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE;YACxB,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC;SACpB;QAED,yFAAyF;QACzF,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC;KACpB;IAAC,OAAO,CAAC,EAAE;QACX,4BAA4B;KAC5B;IAED,OAAO,IAAI,CAAC;AACb,CAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/helpers/roundBadgeNumber.js b/client/dist/js/helpers/roundBadgeNumber.js new file mode 100644 index 0000000000..d8050c604a --- /dev/null +++ b/client/dist/js/helpers/roundBadgeNumber.js @@ -0,0 +1,8 @@ +"use strict"; +export default (count) => { + if (count < 1000) { + return count.toString(); + } + return (count / 1000).toFixed(2).slice(0, -1) + "k"; +}; +//# sourceMappingURL=roundBadgeNumber.js.map diff --git a/client/dist/js/helpers/roundBadgeNumber.js.map b/client/dist/js/helpers/roundBadgeNumber.js.map new file mode 100644 index 0000000000..3c77c631bf --- /dev/null +++ b/client/dist/js/helpers/roundBadgeNumber.js.map @@ -0,0 +1 @@ +{"version":3,"file":"roundBadgeNumber.js","sourceRoot":"","sources":["../../../js/helpers/roundBadgeNumber.js"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,eAAe,CAAC,KAAK,EAAE,EAAE;IACxB,IAAI,KAAK,GAAG,IAAI,EAAE;QACjB,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;KACxB;IAED,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;AACrD,CAAC,CAAC"} \ No newline at end of file diff --git a/client/dist/js/keybinds.js b/client/dist/js/keybinds.js new file mode 100644 index 0000000000..7263ffd708 --- /dev/null +++ b/client/dist/js/keybinds.js @@ -0,0 +1,188 @@ +"use strict"; +import Mousetrap from "mousetrap"; +import store from "./store"; +import {switchToChannel, router, navigate} from "./router"; +import isChannelCollapsed from "./helpers/isChannelCollapsed"; +import isIgnoredKeybind from "./helpers/isIgnoredKeybind"; +import listenForTwoFingerSwipes from "./helpers/listenForTwoFingerSwipes"; +// Switch to the next/previous window in the channel list. +Mousetrap.bind(["alt+up", "alt+down"], function (e, keys) { + if (isIgnoredKeybind(e)) { + return true; + } + navigateWindow(keys.split("+").pop() === "up" ? -1 : 1); + return false; +}); +listenForTwoFingerSwipes(function (cardinalDirection) { + if (cardinalDirection === "e" || cardinalDirection === "w") { + navigateWindow(cardinalDirection === "e" ? -1 : 1); + } +}); +function navigateWindow(direction) { + if (store.state.networks.length === 0) { + return; + } + const flatChannels = []; + let index = -1; + for (const network of store.state.networks) { + for (const channel of network.channels) { + if (isChannelCollapsed(network, channel)) { + continue; + } + if ( + index === -1 && + store.state.activeChannel && + store.state.activeChannel.channel === channel + ) { + index = flatChannels.length; + } + flatChannels.push(channel); + } + } + // Circular array, and a modulo bug workaround because in JS it stays negative + const length = flatChannels.length; + index = (((index + direction) % length) + length) % length; + jumpToChannel(flatChannels[index]); +} +// Switch to the next/previous lobby in the channel list +Mousetrap.bind(["alt+shift+up", "alt+shift+down"], function (e, keys) { + if (isIgnoredKeybind(e)) { + return true; + } + const length = store.state.networks.length; + if (length === 0) { + return false; + } + const direction = keys.split("+").pop() === "up" ? -1 : 1; + let index = 0; + // If we're in another window, jump to first lobby + if (store.state.activeChannel) { + index = store.state.networks.findIndex((n) => n === store.state.activeChannel.network); + // If we're in a channel, and it's not the lobby, jump to lobby of this network when going up + if (direction !== -1 || store.state.activeChannel.channel.type === "lobby") { + index = (((index + direction) % length) + length) % length; + } + } + jumpToChannel(store.state.networks[index].channels[0]); + return false; +}); +// Jump to the first window with a highlight in it, or the first with unread +// activity if there are none with highlights. +Mousetrap.bind(["alt+a"], function (e) { + if (isIgnoredKeybind(e)) { + return true; + } + let targetChannel; + outer_loop: for (const network of store.state.networks) { + for (const chan of network.channels) { + if (chan.highlight) { + targetChannel = chan; + break outer_loop; + } + if (chan.unread && !targetChannel) { + targetChannel = chan; + } + } + } + if (targetChannel) { + jumpToChannel(targetChannel); + } + return false; +}); +// Show the help menu. +Mousetrap.bind(["alt+/"], function (e) { + if (isIgnoredKeybind(e)) { + return true; + } + navigate("Help"); + return false; +}); +function jumpToChannel(targetChannel) { + switchToChannel(targetChannel); + const element = document.querySelector( + `#sidebar .channel-list-item[aria-controls="#chan-${targetChannel.id}"]` + ); + if (element) { + scrollIntoViewNicely(element); + } +} +// Ignored keys which should not automatically focus the input bar +const ignoredKeys = { + 8: true, + 9: true, + 12: true, + 16: true, + 17: true, + 18: true, + 19: true, + 20: true, + 27: true, + 35: true, + 36: true, + 37: true, + 38: true, + 39: true, + 40: true, + 45: true, + 46: true, + 112: true, + 113: true, + 114: true, + 115: true, + 116: true, + 117: true, + 118: true, + 119: true, + 120: true, + 121: true, + 122: true, + 123: true, + 144: true, + 145: true, + 224: true, // Meta +}; +document.addEventListener("keydown", (e) => { + // Allow navigating back to the previous page when on the help screen. + if (e.key === "Escape" && router.currentRoute.name === "Help") { + router.go(-1); + return; + } + // Ignore any key that uses alt modifier + // Ignore keys defined above + if (e.altKey || ignoredKeys[e.which]) { + return; + } + // Ignore all ctrl keys except for ctrl+v to allow pasting + if ((e.ctrlKey || e.metaKey) && e.which !== 86) { + return; + } + // Redirect pagedown/pageup keys to messages container so it scrolls + if (e.which === 33 || e.which === 34) { + const chat = document.querySelector(".window .chat-content .chat"); + if (chat) { + chat.focus(); + } + return; + } + const tagName = e.target.tagName; + // Ignore if we're already typing into or