@lordbex/thelounge 4.4.3-blowfish

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.thelounge_home +1 -0
  2. package/LICENSE +22 -0
  3. package/README.md +95 -0
  4. package/client/index.html.tpl +69 -0
  5. package/dist/defaults/config.js +465 -0
  6. package/dist/package.json +174 -0
  7. package/dist/server/client.js +678 -0
  8. package/dist/server/clientManager.js +220 -0
  9. package/dist/server/command-line/index.js +85 -0
  10. package/dist/server/command-line/install.js +123 -0
  11. package/dist/server/command-line/outdated.js +30 -0
  12. package/dist/server/command-line/start.js +34 -0
  13. package/dist/server/command-line/storage.js +103 -0
  14. package/dist/server/command-line/uninstall.js +40 -0
  15. package/dist/server/command-line/upgrade.js +64 -0
  16. package/dist/server/command-line/users/add.js +67 -0
  17. package/dist/server/command-line/users/edit.js +39 -0
  18. package/dist/server/command-line/users/index.js +17 -0
  19. package/dist/server/command-line/users/list.js +53 -0
  20. package/dist/server/command-line/users/remove.js +37 -0
  21. package/dist/server/command-line/users/reset.js +64 -0
  22. package/dist/server/command-line/utils.js +177 -0
  23. package/dist/server/config.js +138 -0
  24. package/dist/server/helper.js +161 -0
  25. package/dist/server/identification.js +139 -0
  26. package/dist/server/index.js +3 -0
  27. package/dist/server/log.js +35 -0
  28. package/dist/server/models/chan.js +275 -0
  29. package/dist/server/models/msg.js +92 -0
  30. package/dist/server/models/network.js +546 -0
  31. package/dist/server/models/prefix.js +31 -0
  32. package/dist/server/models/user.js +42 -0
  33. package/dist/server/plugins/auth/ldap.js +188 -0
  34. package/dist/server/plugins/auth/local.js +41 -0
  35. package/dist/server/plugins/auth.js +70 -0
  36. package/dist/server/plugins/changelog.js +103 -0
  37. package/dist/server/plugins/clientCertificate.js +115 -0
  38. package/dist/server/plugins/dev-server.js +33 -0
  39. package/dist/server/plugins/inputs/action.js +54 -0
  40. package/dist/server/plugins/inputs/away.js +20 -0
  41. package/dist/server/plugins/inputs/ban.js +45 -0
  42. package/dist/server/plugins/inputs/blow.js +44 -0
  43. package/dist/server/plugins/inputs/connect.js +41 -0
  44. package/dist/server/plugins/inputs/ctcp.js +29 -0
  45. package/dist/server/plugins/inputs/disconnect.js +15 -0
  46. package/dist/server/plugins/inputs/ignore.js +74 -0
  47. package/dist/server/plugins/inputs/ignorelist.js +50 -0
  48. package/dist/server/plugins/inputs/index.js +105 -0
  49. package/dist/server/plugins/inputs/invite.js +31 -0
  50. package/dist/server/plugins/inputs/kick.js +26 -0
  51. package/dist/server/plugins/inputs/kill.js +13 -0
  52. package/dist/server/plugins/inputs/list.js +12 -0
  53. package/dist/server/plugins/inputs/mode.js +55 -0
  54. package/dist/server/plugins/inputs/msg.js +106 -0
  55. package/dist/server/plugins/inputs/mute.js +56 -0
  56. package/dist/server/plugins/inputs/nick.js +55 -0
  57. package/dist/server/plugins/inputs/notice.js +42 -0
  58. package/dist/server/plugins/inputs/part.js +46 -0
  59. package/dist/server/plugins/inputs/quit.js +27 -0
  60. package/dist/server/plugins/inputs/raw.js +13 -0
  61. package/dist/server/plugins/inputs/rejoin.js +25 -0
  62. package/dist/server/plugins/inputs/topic.js +24 -0
  63. package/dist/server/plugins/inputs/whois.js +19 -0
  64. package/dist/server/plugins/irc-events/away.js +59 -0
  65. package/dist/server/plugins/irc-events/cap.js +62 -0
  66. package/dist/server/plugins/irc-events/chghost.js +29 -0
  67. package/dist/server/plugins/irc-events/connection.js +152 -0
  68. package/dist/server/plugins/irc-events/ctcp.js +72 -0
  69. package/dist/server/plugins/irc-events/error.js +80 -0
  70. package/dist/server/plugins/irc-events/help.js +21 -0
  71. package/dist/server/plugins/irc-events/info.js +21 -0
  72. package/dist/server/plugins/irc-events/invite.js +27 -0
  73. package/dist/server/plugins/irc-events/join.js +53 -0
  74. package/dist/server/plugins/irc-events/kick.js +39 -0
  75. package/dist/server/plugins/irc-events/link.js +442 -0
  76. package/dist/server/plugins/irc-events/list.js +47 -0
  77. package/dist/server/plugins/irc-events/message.js +187 -0
  78. package/dist/server/plugins/irc-events/mode.js +124 -0
  79. package/dist/server/plugins/irc-events/modelist.js +67 -0
  80. package/dist/server/plugins/irc-events/motd.js +29 -0
  81. package/dist/server/plugins/irc-events/names.js +21 -0
  82. package/dist/server/plugins/irc-events/nick.js +45 -0
  83. package/dist/server/plugins/irc-events/part.js +35 -0
  84. package/dist/server/plugins/irc-events/quit.js +32 -0
  85. package/dist/server/plugins/irc-events/sasl.js +26 -0
  86. package/dist/server/plugins/irc-events/topic.js +42 -0
  87. package/dist/server/plugins/irc-events/unhandled.js +31 -0
  88. package/dist/server/plugins/irc-events/welcome.js +22 -0
  89. package/dist/server/plugins/irc-events/whois.js +57 -0
  90. package/dist/server/plugins/messageStorage/sqlite.js +454 -0
  91. package/dist/server/plugins/messageStorage/text.js +124 -0
  92. package/dist/server/plugins/packages/index.js +200 -0
  93. package/dist/server/plugins/packages/publicClient.js +66 -0
  94. package/dist/server/plugins/packages/themes.js +61 -0
  95. package/dist/server/plugins/storage.js +88 -0
  96. package/dist/server/plugins/sts.js +85 -0
  97. package/dist/server/plugins/uploader.js +267 -0
  98. package/dist/server/plugins/webpush.js +99 -0
  99. package/dist/server/server.js +857 -0
  100. package/dist/server/storageCleaner.js +131 -0
  101. package/dist/server/utils/fish.js +432 -0
  102. package/dist/shared/irc.js +19 -0
  103. package/dist/shared/linkify.js +81 -0
  104. package/dist/shared/types/chan.js +22 -0
  105. package/dist/shared/types/changelog.js +2 -0
  106. package/dist/shared/types/config.js +2 -0
  107. package/dist/shared/types/mention.js +2 -0
  108. package/dist/shared/types/msg.js +34 -0
  109. package/dist/shared/types/network.js +2 -0
  110. package/dist/shared/types/storage.js +2 -0
  111. package/dist/shared/types/user.js +2 -0
  112. package/dist/webpack.config.js +224 -0
  113. package/index.js +38 -0
  114. package/package.json +174 -0
  115. package/public/audio/pop.wav +0 -0
  116. package/public/css/style.css +12 -0
  117. package/public/css/style.css.map +1 -0
  118. package/public/favicon.ico +0 -0
  119. package/public/fonts/fa-solid-900.woff +0 -0
  120. package/public/fonts/fa-solid-900.woff2 +0 -0
  121. package/public/img/favicon-alerted.ico +0 -0
  122. package/public/img/icon-alerted-black-transparent-bg-72x72px.png +0 -0
  123. package/public/img/icon-alerted-grey-bg-192x192px.png +0 -0
  124. package/public/img/icon-black-transparent-bg.svg +1 -0
  125. package/public/img/logo-grey-bg-120x120px.png +0 -0
  126. package/public/img/logo-grey-bg-152x152px.png +0 -0
  127. package/public/img/logo-grey-bg-167x167px.png +0 -0
  128. package/public/img/logo-grey-bg-180x180px.png +0 -0
  129. package/public/img/logo-grey-bg-192x192px.png +0 -0
  130. package/public/img/logo-grey-bg-512x512px.png +0 -0
  131. package/public/img/logo-grey-bg.svg +1 -0
  132. package/public/img/logo-horizontal-transparent-bg-inverted.svg +1 -0
  133. package/public/img/logo-horizontal-transparent-bg.svg +1 -0
  134. package/public/img/logo-transparent-bg-inverted.svg +1 -0
  135. package/public/img/logo-transparent-bg.svg +1 -0
  136. package/public/img/logo-vertical-transparent-bg-inverted.svg +1 -0
  137. package/public/img/logo-vertical-transparent-bg.svg +1 -0
  138. package/public/js/bundle.js +2 -0
  139. package/public/js/bundle.js.map +1 -0
  140. package/public/js/bundle.vendor.js +3 -0
  141. package/public/js/bundle.vendor.js.LICENSE.txt +18 -0
  142. package/public/js/bundle.vendor.js.map +1 -0
  143. package/public/js/loading-error-handlers.js +1 -0
  144. package/public/robots.txt +2 -0
  145. package/public/service-worker.js +1 -0
  146. package/public/thelounge.webmanifest +53 -0
  147. package/public/themes/default.css +35 -0
  148. package/public/themes/morning.css +183 -0
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const package_json_1 = __importDefault(require("../package.json"));
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const net_1 = __importDefault(require("net"));
11
+ const bcryptjs_1 = __importDefault(require("bcryptjs"));
12
+ const crypto_1 = __importDefault(require("crypto"));
13
+ const Helper = {
14
+ expandHome,
15
+ getVersion,
16
+ getVersionCacheBust,
17
+ getVersionNumber,
18
+ getGitCommit,
19
+ ip2hex,
20
+ parseHostmask,
21
+ compareHostmask,
22
+ compareWithWildcard,
23
+ catch_to_error,
24
+ password: {
25
+ hash: passwordHash,
26
+ compare: passwordCompare,
27
+ requiresUpdate: passwordRequiresUpdate,
28
+ },
29
+ };
30
+ exports.default = Helper;
31
+ function getVersion() {
32
+ const gitCommit = getGitCommit();
33
+ const version = `v${package_json_1.default.version}`;
34
+ return gitCommit ? `source (${gitCommit} / ${version})` : version;
35
+ }
36
+ function getVersionNumber() {
37
+ return package_json_1.default.version;
38
+ }
39
+ let _fetchedGitCommit = false;
40
+ let _gitCommit = null;
41
+ function getGitCommit() {
42
+ if (_fetchedGitCommit) {
43
+ return _gitCommit;
44
+ }
45
+ _fetchedGitCommit = true;
46
+ // --git-dir ".git" makes git only check current directory for `.git`, and not travel upwards
47
+ // We set cwd to the location of `index.js` as soon as the process is started
48
+ try {
49
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
50
+ _gitCommit = require("child_process")
51
+ .execSync('git --git-dir ".git" rev-parse --short HEAD', // Returns hash of current commit
52
+ { stdio: ["ignore", "pipe", "ignore"] })
53
+ .toString()
54
+ .trim();
55
+ return _gitCommit;
56
+ }
57
+ catch (e) {
58
+ // Not a git repository or git is not installed
59
+ _gitCommit = null;
60
+ return null;
61
+ }
62
+ }
63
+ function getVersionCacheBust() {
64
+ const hash = crypto_1.default.createHash("sha256").update(Helper.getVersion()).digest("hex");
65
+ return hash.substring(0, 10);
66
+ }
67
+ function ip2hex(address) {
68
+ // no ipv6 support
69
+ if (!net_1.default.isIPv4(address)) {
70
+ return "00000000";
71
+ }
72
+ return address
73
+ .split(".")
74
+ .map(function (octet) {
75
+ let hex = parseInt(octet, 10).toString(16);
76
+ if (hex.length === 1) {
77
+ hex = "0" + hex;
78
+ }
79
+ return hex;
80
+ })
81
+ .join("");
82
+ }
83
+ // Expand ~ into the current user home dir.
84
+ // This does *not* support `~other_user/tmp` => `/home/other_user/tmp`.
85
+ function expandHome(shortenedPath) {
86
+ if (!shortenedPath) {
87
+ return "";
88
+ }
89
+ const home = os_1.default.homedir().replace("$", "$$$$");
90
+ return path_1.default.resolve(shortenedPath.replace(/^~($|\/|\\)/, home + "$1"));
91
+ }
92
+ function passwordRequiresUpdate(password) {
93
+ return bcryptjs_1.default.getRounds(password) !== 11;
94
+ }
95
+ function passwordHash(password) {
96
+ return bcryptjs_1.default.hashSync(password, bcryptjs_1.default.genSaltSync(11));
97
+ }
98
+ function passwordCompare(password, expected) {
99
+ return bcryptjs_1.default.compare(password, expected);
100
+ }
101
+ function parseHostmask(hostmask) {
102
+ let nick = "";
103
+ let ident = "*";
104
+ let hostname = "*";
105
+ let parts = [];
106
+ // Parse hostname first, then parse the rest
107
+ parts = hostmask.split("@");
108
+ if (parts.length >= 2) {
109
+ hostname = parts[1] || "*";
110
+ hostmask = parts[0];
111
+ }
112
+ hostname = hostname.toLowerCase();
113
+ parts = hostmask.split("!");
114
+ if (parts.length >= 2) {
115
+ ident = parts[1] || "*";
116
+ hostmask = parts[0];
117
+ }
118
+ ident = ident.toLowerCase();
119
+ nick = hostmask.toLowerCase() || "*";
120
+ const result = {
121
+ nick: nick,
122
+ ident: ident,
123
+ hostname: hostname,
124
+ };
125
+ return result;
126
+ }
127
+ function compareHostmask(a, b) {
128
+ return (compareWithWildcard(a.nick, b.nick) &&
129
+ compareWithWildcard(a.ident, b.ident) &&
130
+ compareWithWildcard(a.hostname, b.hostname));
131
+ }
132
+ function compareWithWildcard(a, b) {
133
+ // we allow '*' and '?' wildcards in our comparison.
134
+ // this is mostly aligned with https://modern.ircdocs.horse/#wildcard-expressions
135
+ // but we do not support the escaping. The ABNF does not seem to be clear as to
136
+ // how to escape the escape char '\', which is valid in a nick,
137
+ // whereas the wildcards tend not to be (as per RFC1459).
138
+ // The "*" wildcard is ".*" in regex, "?" is "."
139
+ // so we tokenize and join with the proper char back together,
140
+ // escaping any other regex modifier
141
+ const wildmany_split = a.split("*").map((sub) => {
142
+ const wildone_split = sub.split("?").map((p) => lodash_1.default.escapeRegExp(p));
143
+ return wildone_split.join(".");
144
+ });
145
+ const user_regex = wildmany_split.join(".*");
146
+ const re = new RegExp(`^${user_regex}$`, "i"); // case insensitive
147
+ return re.test(b);
148
+ }
149
+ function catch_to_error(prefix, err) {
150
+ let msg;
151
+ if (err instanceof Error) {
152
+ msg = err.message;
153
+ }
154
+ else if (typeof err === "string") {
155
+ msg = err;
156
+ }
157
+ else {
158
+ msg = err.toString();
159
+ }
160
+ return new Error(`${prefix}: ${msg}`);
161
+ }
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const net_1 = __importDefault(require("net"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const helper_1 = __importDefault(require("./helper"));
10
+ const config_1 = __importDefault(require("./config"));
11
+ const log_1 = __importDefault(require("./log"));
12
+ class Identification {
13
+ connectionId;
14
+ connections;
15
+ oidentdFile;
16
+ constructor(startedCallback) {
17
+ this.connectionId = 0;
18
+ this.connections = new Map();
19
+ if (typeof config_1.default.values.oidentd === "string") {
20
+ this.oidentdFile = helper_1.default.expandHome(config_1.default.values.oidentd);
21
+ log_1.default.info(`Oidentd file: ${chalk_1.default.green(this.oidentdFile)}`);
22
+ this.refresh();
23
+ }
24
+ if (config_1.default.values.identd.enable) {
25
+ if (this.oidentdFile) {
26
+ log_1.default.warn("Using both identd and oidentd at the same time, this is most likely not intended.");
27
+ }
28
+ const server = net_1.default.createServer(this.serverConnection.bind(this));
29
+ server.on("error", (err) => {
30
+ startedCallback(this, err);
31
+ });
32
+ server.listen({
33
+ port: config_1.default.values.identd.port || 113,
34
+ host: config_1.default.values.bind,
35
+ }, () => {
36
+ const address = server.address();
37
+ if (typeof address === "string") {
38
+ log_1.default.info(`Identd server available on ${chalk_1.default.green(address)}`);
39
+ }
40
+ else if (address?.address) {
41
+ log_1.default.info(`Identd server available on ${chalk_1.default.green(address.address + ":" + address.port.toString())}`);
42
+ }
43
+ startedCallback(this);
44
+ });
45
+ }
46
+ else {
47
+ startedCallback(this);
48
+ }
49
+ }
50
+ serverConnection(socket) {
51
+ socket.on("error", (err) => log_1.default.error(`Identd socket error: ${err}`));
52
+ socket.setTimeout(5000, () => {
53
+ log_1.default.warn(`identd: no data received, closing connection to ${socket.remoteAddress || "undefined"}`);
54
+ socket.destroy();
55
+ });
56
+ socket.once("data", (data) => {
57
+ this.respondToIdent(socket, data);
58
+ socket.end();
59
+ });
60
+ }
61
+ respondToIdent(socket, buffer) {
62
+ if (!socket.remoteAddress) {
63
+ log_1.default.warn("identd: no remote address");
64
+ return;
65
+ }
66
+ const data = buffer.toString().split(",");
67
+ const lport = parseInt(data[0], 10) || 0;
68
+ const fport = parseInt(data[1], 10) || 0;
69
+ if (lport < 1 || fport < 1 || lport > 65535 || fport > 65535) {
70
+ log_1.default.warn(`identd: bogus request from ${socket.remoteAddress}`);
71
+ return;
72
+ }
73
+ log_1.default.debug(`identd: remote ${socket.remoteAddress} query ${lport}, ${fport}`);
74
+ for (const connection of this.connections.values()) {
75
+ // we only want to respond if all the ip,port tuples match, to avoid user enumeration
76
+ if (connection.socket.remotePort === fport &&
77
+ connection.socket.localPort === lport &&
78
+ socket.remoteAddress === connection.socket.remoteAddress &&
79
+ socket.localAddress === connection.socket.localAddress) {
80
+ const reply = `${lport}, ${fport} : USERID : TheLounge : ${connection.user}\r\n`;
81
+ log_1.default.debug(`identd: reply is ${reply.trimEnd()}`);
82
+ socket.write(reply);
83
+ return;
84
+ }
85
+ }
86
+ const reply = `${lport}, ${fport} : ERROR : NO-USER\r\n`;
87
+ log_1.default.debug(`identd: reply is ${reply.trimEnd()}`);
88
+ socket.write(reply);
89
+ }
90
+ addSocket(socket, user) {
91
+ const id = ++this.connectionId;
92
+ this.connections.set(id, { socket, user });
93
+ if (this.oidentdFile) {
94
+ this.refresh();
95
+ }
96
+ return id;
97
+ }
98
+ removeSocket(id) {
99
+ this.connections.delete(id);
100
+ if (this.oidentdFile) {
101
+ this.refresh();
102
+ }
103
+ }
104
+ refresh() {
105
+ let file = "# Warning: file generated by The Lounge: changes will be overwritten!\n";
106
+ this.connections.forEach((connection, id) => {
107
+ if (!connection.socket.remotePort || !connection.socket.localPort) {
108
+ // Race condition: this can happen when more than one socket gets disconnected at
109
+ // once, since we `refresh()` for each one being added/removed. This results
110
+ // in there possibly being one or more disconnected sockets remaining when we get here.
111
+ log_1.default.warn(`oidentd: socket has no remote or local port (id=${id}). See https://github.com/thelounge/thelounge/pull/4695.`);
112
+ return;
113
+ }
114
+ if (!connection.socket.remoteAddress) {
115
+ log_1.default.warn(`oidentd: socket has no remote address, will not respond to queries`);
116
+ return;
117
+ }
118
+ if (!connection.socket.localAddress) {
119
+ log_1.default.warn(`oidentd: socket has no local address, will not respond to queries`);
120
+ return;
121
+ }
122
+ // we only want to respond if all the ip,port tuples match, to avoid user enumeration
123
+ file +=
124
+ `to ${connection.socket.remoteAddress}` +
125
+ ` fport ${connection.socket.remotePort}` +
126
+ ` from ${connection.socket.localAddress}` +
127
+ ` lport ${connection.socket.localPort}` +
128
+ ` { reply "${connection.user}" }\n`;
129
+ });
130
+ if (this.oidentdFile) {
131
+ fs_1.default.writeFile(this.oidentdFile, file, { flag: "w+" }, function (err) {
132
+ if (err) {
133
+ log_1.default.error("Failed to update oidentd file!", err.message);
134
+ }
135
+ });
136
+ }
137
+ }
138
+ }
139
+ exports.default = Identification;
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./command-line");
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const chalk_1 = __importDefault(require("chalk"));
7
+ const read_1 = __importDefault(require("read"));
8
+ function timestamp() {
9
+ const datetime = new Date().toISOString().split(".")[0].replace("T", " ");
10
+ return chalk_1.default.dim(datetime);
11
+ }
12
+ const log = {
13
+ /* eslint-disable no-console */
14
+ error(...args) {
15
+ console.error(timestamp(), chalk_1.default.red("[ERROR]"), ...args);
16
+ },
17
+ warn(...args) {
18
+ console.error(timestamp(), chalk_1.default.yellow("[WARN]"), ...args);
19
+ },
20
+ info(...args) {
21
+ console.log(timestamp(), chalk_1.default.blue("[INFO]"), ...args);
22
+ },
23
+ debug(...args) {
24
+ console.log(timestamp(), chalk_1.default.green("[DEBUG]"), ...args);
25
+ },
26
+ raw(...args) {
27
+ console.log(...args);
28
+ },
29
+ /* eslint-enable no-console */
30
+ prompt(options, callback) {
31
+ options.prompt = [timestamp(), chalk_1.default.cyan("[PROMPT]"), options.text].join(" ");
32
+ (0, read_1.default)(options, callback);
33
+ },
34
+ };
35
+ exports.default = log;
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const lodash_1 = __importDefault(require("lodash"));
7
+ const log_1 = __importDefault(require("../log"));
8
+ const config_1 = __importDefault(require("../config"));
9
+ const user_1 = __importDefault(require("./user"));
10
+ const storage_1 = __importDefault(require("../plugins/storage"));
11
+ const prefix_1 = __importDefault(require("./prefix"));
12
+ const msg_1 = require("../../shared/types/msg");
13
+ const chan_1 = require("../../shared/types/chan");
14
+ class Chan {
15
+ // TODO: don't force existence, figure out how to make TS infer it.
16
+ id;
17
+ messages;
18
+ name;
19
+ key;
20
+ topic;
21
+ firstUnread;
22
+ unread;
23
+ highlight;
24
+ users;
25
+ muted;
26
+ type;
27
+ state;
28
+ // mIRC FiSH Blowfish key, stored server-side only
29
+ blowfishKey;
30
+ userAway;
31
+ special;
32
+ data;
33
+ closed;
34
+ num_users;
35
+ constructor(attr) {
36
+ lodash_1.default.defaults(this, attr, {
37
+ id: 0,
38
+ messages: [],
39
+ name: "",
40
+ key: "",
41
+ topic: "",
42
+ type: chan_1.ChanType.CHANNEL,
43
+ state: chan_1.ChanState.PARTED,
44
+ firstUnread: 0,
45
+ unread: 0,
46
+ highlight: 0,
47
+ users: new Map(),
48
+ muted: false,
49
+ });
50
+ }
51
+ destroy() {
52
+ this.dereferencePreviews(this.messages);
53
+ }
54
+ pushMessage(client, msg, increasesUnread = false) {
55
+ const chanId = this.id;
56
+ msg.id = client.idMsg++;
57
+ // If this channel is open in any of the clients, do not increase unread counter
58
+ const isOpen = lodash_1.default.find(client.attachedClients, { openChannel: chanId }) !== undefined;
59
+ if (msg.self) {
60
+ // reset counters/markers when receiving self-/echo-message
61
+ this.unread = 0;
62
+ this.firstUnread = msg.id;
63
+ this.highlight = 0;
64
+ }
65
+ else if (!isOpen) {
66
+ if (!this.firstUnread) {
67
+ this.firstUnread = msg.id;
68
+ }
69
+ if (increasesUnread || msg.highlight) {
70
+ this.unread++;
71
+ }
72
+ if (msg.highlight) {
73
+ this.highlight++;
74
+ }
75
+ }
76
+ client.emit("msg", { chan: chanId, msg, unread: this.unread, highlight: this.highlight });
77
+ // Never store messages in public mode as the session
78
+ // is completely destroyed when the page gets closed
79
+ if (config_1.default.values.public) {
80
+ return;
81
+ }
82
+ // showInActive is only processed on "msg", don't need it on page reload
83
+ if (msg.showInActive) {
84
+ delete msg.showInActive;
85
+ }
86
+ this.writeUserLog(client, msg);
87
+ if (config_1.default.values.maxHistory >= 0 && this.messages.length > config_1.default.values.maxHistory) {
88
+ const deleted = this.messages.splice(0, this.messages.length - config_1.default.values.maxHistory);
89
+ // If maxHistory is 0, image would be dereferenced before client had a chance to retrieve it,
90
+ // so for now, just don't implement dereferencing for this edge case.
91
+ if (config_1.default.values.maxHistory > 0) {
92
+ this.dereferencePreviews(deleted);
93
+ }
94
+ }
95
+ }
96
+ dereferencePreviews(messages) {
97
+ if (!config_1.default.values.prefetch || !config_1.default.values.prefetchStorage) {
98
+ return;
99
+ }
100
+ messages.forEach((message) => {
101
+ if (message.previews) {
102
+ message.previews.forEach((preview) => {
103
+ if (preview.thumb) {
104
+ storage_1.default.dereference(preview.thumb);
105
+ preview.thumb = "";
106
+ }
107
+ });
108
+ }
109
+ });
110
+ }
111
+ getSortedUsers(irc) {
112
+ const users = Array.from(this.users.values());
113
+ if (!irc || !irc.network || !irc.network.options || !irc.network.options.PREFIX) {
114
+ return users;
115
+ }
116
+ const userModeSortPriority = {};
117
+ irc.network.options.PREFIX.forEach((prefix, index) => {
118
+ userModeSortPriority[prefix.symbol] = index;
119
+ });
120
+ userModeSortPriority[""] = 99; // No mode is lowest
121
+ return users.sort(function (a, b) {
122
+ if (a.mode === b.mode) {
123
+ return a.nick.toLowerCase() < b.nick.toLowerCase() ? -1 : 1;
124
+ }
125
+ return userModeSortPriority[a.mode] - userModeSortPriority[b.mode];
126
+ });
127
+ }
128
+ findMessage(msgId) {
129
+ return this.messages.find((message) => message.id === msgId);
130
+ }
131
+ findUser(nick) {
132
+ return this.users.get(nick.toLowerCase());
133
+ }
134
+ getUser(nick) {
135
+ return this.findUser(nick) || new user_1.default({ nick }, new prefix_1.default([]));
136
+ }
137
+ setUser(user) {
138
+ this.users.set(user.nick.toLowerCase(), user);
139
+ }
140
+ removeUser(user) {
141
+ this.users.delete(user.nick.toLowerCase());
142
+ }
143
+ /**
144
+ * Get a clean clone of this channel that will be sent to the client.
145
+ * This function performs manual cloning of channel object for
146
+ * better control of performance and memory usage.
147
+ *
148
+ * @param {(int|bool)} lastActiveChannel - Last known active user channel id (needed to control how many messages are sent)
149
+ * If true, channel is assumed active.
150
+ * @param {int} lastMessage - Last message id seen by active client to avoid sending duplicates.
151
+ */
152
+ getFilteredClone(lastActiveChannel, lastMessage) {
153
+ let msgs;
154
+ // If client is reconnecting, only send new messages that client has not seen yet
155
+ if (lastMessage && lastMessage > -1) {
156
+ // When reconnecting, always send up to 100 messages to prevent message gaps on the client
157
+ // See https://github.com/thelounge/thelounge/issues/1883
158
+ msgs = this.messages.filter((m) => m.id > lastMessage).slice(-100);
159
+ }
160
+ else {
161
+ // If channel is active, send up to 100 last messages, for all others send just 1
162
+ // Client will automatically load more messages whenever needed based on last seen messages
163
+ const messagesToSend = lastActiveChannel === true || this.id === lastActiveChannel ? 100 : 1;
164
+ msgs = this.messages.slice(-messagesToSend);
165
+ }
166
+ return {
167
+ id: this.id,
168
+ messages: msgs,
169
+ totalMessages: this.messages.length,
170
+ name: this.name,
171
+ key: this.key,
172
+ topic: this.topic,
173
+ firstUnread: this.firstUnread,
174
+ unread: this.unread,
175
+ highlight: this.highlight,
176
+ muted: this.muted,
177
+ type: this.type,
178
+ state: this.state,
179
+ special: this.special,
180
+ data: this.data,
181
+ closed: this.closed,
182
+ num_users: this.num_users,
183
+ };
184
+ // TODO: funny array mutation below might need to be reproduced
185
+ // static optionalProperties = ["userAway", "special", "data", "closed", "num_users"];
186
+ // return Object.keys(this).reduce((newChannel, prop) => {
187
+ // if (Chan.optionalProperties.includes(prop)) {
188
+ // if (this[prop] !== undefined || (Array.isArray(this[prop]) && this[prop].length)) {
189
+ // newChannel[prop] = this[prop];
190
+ // }
191
+ // }
192
+ }
193
+ writeUserLog(client, msg) {
194
+ this.messages.push(msg);
195
+ // Are there any logs enabled
196
+ if (client.messageStorage.length === 0) {
197
+ return;
198
+ }
199
+ const targetChannel = this;
200
+ // Is this particular message or channel loggable
201
+ if (!msg.isLoggable() || !this.isLoggable()) {
202
+ // Because notices are nasty and can be shown in active channel on the client
203
+ // if there is no open query, we want to always log notices in the sender's name
204
+ if (msg.type === msg_1.MessageType.NOTICE && msg.showInActive) {
205
+ targetChannel.name = msg.from.nick || ""; // TODO: check if || works
206
+ }
207
+ else {
208
+ return;
209
+ }
210
+ }
211
+ // Find the parent network where this channel is in
212
+ const target = client.find(this.id);
213
+ if (!target) {
214
+ return;
215
+ }
216
+ for (const messageStorage of client.messageStorage) {
217
+ messageStorage.index(target.network, targetChannel, msg).catch((e) => log_1.default.error(e));
218
+ }
219
+ }
220
+ loadMessages(client, network) {
221
+ if (!this.isLoggable()) {
222
+ return;
223
+ }
224
+ if (!network.irc) {
225
+ // Network created, but misconfigured
226
+ log_1.default.warn(`Failed to load messages for ${client.name}, network ${network.name} is not initialized.`);
227
+ return;
228
+ }
229
+ if (!client.messageProvider) {
230
+ if (network.irc.network.cap.isEnabled("znc.in/playback")) {
231
+ // if we do have a message provider we might be able to only fetch partial history,
232
+ // so delay the cap in this case.
233
+ requestZncPlayback(this, network, 0);
234
+ }
235
+ return;
236
+ }
237
+ client.messageProvider
238
+ .getMessages(network, this, () => client.idMsg++)
239
+ .then((messages) => {
240
+ if (messages.length === 0) {
241
+ if (network.irc.network.cap.isEnabled("znc.in/playback")) {
242
+ requestZncPlayback(this, network, 0);
243
+ }
244
+ return;
245
+ }
246
+ this.messages.unshift(...messages);
247
+ if (!this.firstUnread) {
248
+ this.firstUnread = messages[messages.length - 1].id;
249
+ }
250
+ client.emit("more", {
251
+ chan: this.id,
252
+ messages: messages.slice(-100),
253
+ totalMessages: messages.length,
254
+ });
255
+ if (network.irc.network.cap.isEnabled("znc.in/playback")) {
256
+ const from = Math.floor(messages[messages.length - 1].time.getTime() / 1000);
257
+ requestZncPlayback(this, network, from);
258
+ }
259
+ })
260
+ .catch((err) => log_1.default.error(`Failed to load messages for ${client.name}: ${err.toString()}`));
261
+ }
262
+ isLoggable() {
263
+ return this.type === chan_1.ChanType.CHANNEL || this.type === chan_1.ChanType.QUERY;
264
+ }
265
+ setMuteStatus(muted) {
266
+ this.muted = !!muted;
267
+ }
268
+ }
269
+ function requestZncPlayback(channel, network, from) {
270
+ if (!network.irc) {
271
+ throw new Error(`requestZncPlayback: no irc field on network "${network.name}", this is a bug`);
272
+ }
273
+ network.irc.raw("ZNC", "*playback", "PLAY", channel.name, from.toString());
274
+ }
275
+ exports.default = Chan;