UNPKG

5.41 kBJavaScriptView Raw
1"use strict";
2
3var _ = require("lodash");
4var Helper = require("../helper");
5const User = require("./user");
6const userLog = require("../userLog");
7const storage = require("../plugins/storage");
8
9module.exports = Chan;
10
11Chan.Type = {
12 CHANNEL: "channel",
13 LOBBY: "lobby",
14 QUERY: "query",
15 SPECIAL: "special",
16};
17
18Chan.State = {
19 PARTED: 0,
20 JOINED: 1,
21};
22
23let id = 1;
24
25function Chan(attr) {
26 _.defaults(this, attr, {
27 id: id++,
28 messages: [],
29 name: "",
30 key: "",
31 topic: "",
32 type: Chan.Type.CHANNEL,
33 state: Chan.State.PARTED,
34 firstUnread: 0,
35 unread: 0,
36 highlight: 0,
37 users: new Map(),
38 });
39}
40
41Chan.prototype.destroy = function() {
42 this.dereferencePreviews(this.messages);
43};
44
45Chan.prototype.pushMessage = function(client, msg, increasesUnread) {
46 var obj = {
47 chan: this.id,
48 msg: msg,
49 };
50
51 // If this channel is open in any of the clients, do not increase unread counter
52 const isOpen = _.find(client.attachedClients, {openChannel: this.id}) !== undefined;
53
54 if ((increasesUnread || msg.highlight) && !isOpen) {
55 obj.unread = ++this.unread;
56 }
57
58 client.emit("msg", obj);
59
60 // Never store messages in public mode as the session
61 // is completely destroyed when the page gets closed
62 if (Helper.config.public) {
63 return;
64 }
65
66 this.messages.push(msg);
67
68 if (client.config.log === true) {
69 writeUserLog.call(this, client, msg);
70 }
71
72 if (Helper.config.maxHistory >= 0 && this.messages.length > Helper.config.maxHistory) {
73 const deleted = this.messages.splice(0, this.messages.length - Helper.config.maxHistory);
74
75 // If maxHistory is 0, image would be dereferenced before client had a chance to retrieve it,
76 // so for now, just don't implement dereferencing for this edge case.
77 if (Helper.config.prefetch && Helper.config.prefetchStorage && Helper.config.maxHistory > 0) {
78 this.dereferencePreviews(deleted);
79 }
80 }
81
82 if (msg.self) {
83 // reset counters/markers when receiving self-/echo-message
84 this.firstUnread = 0;
85 this.highlight = 0;
86 } else if (!isOpen) {
87 if (!this.firstUnread) {
88 this.firstUnread = msg.id;
89 }
90
91 if (msg.highlight) {
92 this.highlight++;
93 }
94 }
95};
96
97Chan.prototype.dereferencePreviews = function(messages) {
98 messages.forEach((message) => {
99 if (message.preview && message.preview.thumb) {
100 storage.dereference(message.preview.thumb);
101 message.preview.thumb = null;
102 }
103 });
104};
105
106Chan.prototype.getSortedUsers = function(irc) {
107 const users = Array.from(this.users.values());
108
109 if (!irc || !irc.network || !irc.network.options || !irc.network.options.PREFIX) {
110 return users;
111 }
112
113 var userModeSortPriority = {};
114 irc.network.options.PREFIX.forEach((prefix, index) => {
115 userModeSortPriority[prefix.symbol] = index;
116 });
117
118 userModeSortPriority[""] = 99; // No mode is lowest
119
120 return users.sort(function(a, b) {
121 if (a.mode === b.mode) {
122 return a.nick.toLowerCase() < b.nick.toLowerCase() ? -1 : 1;
123 }
124
125 return userModeSortPriority[a.mode] - userModeSortPriority[b.mode];
126 });
127};
128
129Chan.prototype.findMessage = function(msgId) {
130 return this.messages.find((message) => message.id === msgId);
131};
132
133Chan.prototype.findUser = function(nick) {
134 return this.users.get(nick.toLowerCase());
135};
136
137Chan.prototype.getUser = function(nick) {
138 return this.findUser(nick) || new User({nick: nick});
139};
140
141Chan.prototype.setUser = function(user) {
142 this.users.set(user.nick.toLowerCase(), user);
143};
144
145Chan.prototype.removeUser = function(user) {
146 this.users.delete(user.nick.toLowerCase());
147};
148
149/**
150 * Get a clean clone of this channel that will be sent to the client.
151 * This function performs manual cloning of channel object for
152 * better control of performance and memory usage.
153 *
154 * @param {(int|bool)} lastActiveChannel - Last known active user channel id (needed to control how many messages are sent)
155 * If true, channel is assumed active.
156 * @param {int} lastMessage - Last message id seen by active client to avoid sending duplicates.
157 */
158Chan.prototype.getFilteredClone = function(lastActiveChannel, lastMessage) {
159 return Object.keys(this).reduce((newChannel, prop) => {
160 if (prop === "users") {
161 // Do not send users, client requests updated user list whenever needed
162 newChannel[prop] = [];
163 } else if (prop === "messages") {
164 // If client is reconnecting, only send new messages that client has not seen yet
165 if (lastMessage > -1) {
166 // When reconnecting, always send up to 100 messages to prevent message gaps on the client
167 // See https://github.com/thelounge/lounge/issues/1883
168 newChannel[prop] = this[prop]
169 .filter((m) => m.id > lastMessage)
170 .slice(-100);
171 } else {
172 // If channel is active, send up to 100 last messages, for all others send just 1
173 // Client will automatically load more messages whenever needed based on last seen messages
174 const messagesToSend = lastActiveChannel === true || this.id === lastActiveChannel ? -100 : -1;
175
176 newChannel[prop] = this[prop].slice(messagesToSend);
177 }
178 } else {
179 newChannel[prop] = this[prop];
180 }
181
182 return newChannel;
183 }, {});
184};
185
186function writeUserLog(client, msg) {
187 if (!msg.isLoggable()) {
188 return false;
189 }
190
191 const target = client.find(this.id);
192
193 if (!target) {
194 return false;
195 }
196
197 userLog.write(
198 client.name,
199 target.network.host, // TODO: Fix #1392, multiple connections to same server results in duplicate logs
200 this.type === Chan.Type.LOBBY ? target.network.host : this.name,
201 msg
202 );
203}