serverless-ircd 0.6.0 → 0.7.0
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.
- package/.github/workflows/ci.yml +6 -1
- package/.gitmodules +3 -0
- package/CHANGELOG.md +214 -0
- package/README.md +110 -23
- package/apps/aws-stack/package.json +1 -1
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-worker/package.json +1 -1
- package/apps/cf-worker/src/origin-allowlist.ts +99 -0
- package/apps/cf-worker/src/worker.ts +89 -9
- package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
- package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
- package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
- package/apps/cf-worker/tests/smoke.test.ts +28 -2
- package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
- package/apps/cf-worker/wrangler.test.toml +23 -0
- package/apps/cf-worker/wrangler.toml +54 -0
- package/apps/local-cli/package.json +1 -1
- package/apps/web/landing/index.html +217 -0
- package/apps/web/package.json +28 -0
- package/apps/web/scripts/build.mjs +125 -0
- package/apps/web/src/build-env.ts +43 -0
- package/apps/web/src/config-schema.ts +138 -0
- package/apps/web/static/config.json +28 -0
- package/apps/web/static/config.prod.json +28 -0
- package/apps/web/static/config.staging.json +28 -0
- package/apps/web/tests/build-env.test.ts +63 -0
- package/apps/web/tests/build-smoke.test.ts +103 -0
- package/apps/web/tests/config-schema.test.ts +432 -0
- package/apps/web/tests/workspace.test.ts +12 -0
- package/apps/web/tsconfig.json +10 -0
- package/apps/web/upstream/.browserslistrc +3 -0
- package/apps/web/upstream/.editorconfig +9 -0
- package/apps/web/upstream/.eslintignore +3 -0
- package/apps/web/upstream/.eslintrc.js +90 -0
- package/apps/web/upstream/.github/FUNDING.yml +12 -0
- package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
- package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/apps/web/upstream/.github/workflows/push.yml +26 -0
- package/apps/web/upstream/.github/workflows/release.yml +33 -0
- package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
- package/apps/web/upstream/.prettierrc.js +9 -0
- package/apps/web/upstream/.stylelintignore +5 -0
- package/apps/web/upstream/.stylelintrc.js +21 -0
- package/apps/web/upstream/LICENSE +201 -0
- package/apps/web/upstream/README.md +76 -0
- package/apps/web/upstream/babel.config.js +25 -0
- package/apps/web/upstream/crowdin.yml +3 -0
- package/apps/web/upstream/index.html +35 -0
- package/apps/web/upstream/jest.config.js +7 -0
- package/apps/web/upstream/jsdoc.json +22 -0
- package/apps/web/upstream/package.json +80 -0
- package/apps/web/upstream/scripts/pre-commit +43 -0
- package/apps/web/upstream/src/components/App.vue +516 -0
- package/apps/web/upstream/src/components/AppSettings.vue +587 -0
- package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
- package/apps/web/upstream/src/components/Avatar.vue +105 -0
- package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
- package/apps/web/upstream/src/components/BufferKey.vue +121 -0
- package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
- package/apps/web/upstream/src/components/Captcha.vue +85 -0
- package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
- package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
- package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
- package/apps/web/upstream/src/components/ChannelList.vue +345 -0
- package/apps/web/upstream/src/components/Container.vue +401 -0
- package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
- package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
- package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
- package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
- package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
- package/apps/web/upstream/src/components/MessageList.vue +1028 -0
- package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
- package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
- package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
- package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
- package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
- package/apps/web/upstream/src/components/Nicklist.vue +309 -0
- package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
- package/apps/web/upstream/src/components/NotConnected.vue +271 -0
- package/apps/web/upstream/src/components/SelfUser.vue +283 -0
- package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
- package/apps/web/upstream/src/components/ServerView.vue +126 -0
- package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
- package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
- package/apps/web/upstream/src/components/Sidebar.vue +429 -0
- package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
- package/apps/web/upstream/src/components/SidebarState.vue +145 -0
- package/apps/web/upstream/src/components/StartupError.vue +35 -0
- package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
- package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
- package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
- package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
- package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
- package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
- package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
- package/apps/web/upstream/src/components/UserBox.vue +728 -0
- package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
- package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
- package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
- package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
- package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
- package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
- package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
- package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
- package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
- package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
- package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
- package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
- package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
- package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
- package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
- package/apps/web/upstream/src/helpers/Colours.js +128 -0
- package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
- package/apps/web/upstream/src/helpers/Md5.js +193 -0
- package/apps/web/upstream/src/helpers/Misc.js +401 -0
- package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
- package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
- package/apps/web/upstream/src/libs/AudioManager.js +60 -0
- package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
- package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
- package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
- package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
- package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
- package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
- package/apps/web/upstream/src/libs/IPC.js +51 -0
- package/apps/web/upstream/src/libs/InputHandler.js +921 -0
- package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
- package/apps/web/upstream/src/libs/Logger.js +71 -0
- package/apps/web/upstream/src/libs/Message.js +169 -0
- package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
- package/apps/web/upstream/src/libs/MessageParser.js +249 -0
- package/apps/web/upstream/src/libs/Notifications.js +78 -0
- package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
- package/apps/web/upstream/src/libs/ServerSession.js +102 -0
- package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
- package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
- package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
- package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
- package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
- package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
- package/apps/web/upstream/src/libs/bufferTools.js +177 -0
- package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
- package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
- package/apps/web/upstream/src/libs/settingTools.js +31 -0
- package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
- package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
- package/apps/web/upstream/src/libs/state/UserState.js +89 -0
- package/apps/web/upstream/src/libs/state/common.js +26 -0
- package/apps/web/upstream/src/libs/state.js +961 -0
- package/apps/web/upstream/src/libs/storage/Local.js +51 -0
- package/apps/web/upstream/src/main.js +566 -0
- package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
- package/apps/web/upstream/src/res/configTemplates.js +373 -0
- package/apps/web/upstream/src/res/globalStyle.css +277 -0
- package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
- package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
- package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
- package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
- package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
- package/apps/web/upstream/src/res/localesList.json +27 -0
- package/apps/web/upstream/src/res/logo.png +0 -0
- package/apps/web/upstream/src/thirdparty/about.html +43 -0
- package/apps/web/upstream/src/thirdparty/index.js +5 -0
- package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
- package/apps/web/upstream/static/config.json +28 -0
- package/apps/web/upstream/static/emoticons/smile.png +0 -0
- package/apps/web/upstream/static/favicon.png +0 -0
- package/apps/web/upstream/static/highlight.mp3 +0 -0
- package/apps/web/upstream/static/highlight.ogg +0 -0
- package/apps/web/upstream/static/locales/.gitignore +2 -0
- package/apps/web/upstream/static/plugins/customise.html.example +10 -0
- package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
- package/apps/web/upstream/static/themes/common/base.css +1064 -0
- package/apps/web/upstream/static/themes/dark/theme.css +196 -0
- package/apps/web/upstream/static/themes/default/theme.css +92 -0
- package/apps/web/upstream/static/themes/elite/theme.css +248 -0
- package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
- package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
- package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
- package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
- package/apps/web/upstream/static/themes/sky/theme.css +107 -0
- package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
- package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
- package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
- package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
- package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
- package/apps/web/upstream/vue.config.js +150 -0
- package/apps/web/upstream/yarn.lock +9767 -0
- package/apps/web/vitest.config.ts +13 -0
- package/biome.json +1 -0
- package/docs/Home.md +1 -0
- package/docs/WebClientGuide.md +535 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/cf-adapter/package.json +1 -1
- package/packages/cf-adapter/tests/cf-harness.ts +12 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/reports/mutation/mutation.html +342 -0
- package/packages/irc-core/tests/batch.test.ts +15 -0
- package/packages/irc-core/tests/numerics.test.ts +13 -0
- package/packages/irc-core/tests/parser.test.ts +109 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/scenarios.ts +24 -2
- package/tools/ci-hardening/package.json +1 -1
- package/tools/load-test/package.json +1 -1
- package/tools/tcp-ws-forwarder/package.json +1 -1
|
@@ -0,0 +1,1506 @@
|
|
|
1
|
+
'kiwi public';
|
|
2
|
+
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import strftime from 'strftime';
|
|
5
|
+
import Irc from 'irc-framework';
|
|
6
|
+
import * as TextFormatting from '@/helpers/TextFormatting';
|
|
7
|
+
import typingMiddleware from './TypingMiddleware';
|
|
8
|
+
import chathistoryMiddleware from './ChathistoryMiddleware';
|
|
9
|
+
import * as ServerConnection from './ServerConnection';
|
|
10
|
+
|
|
11
|
+
export function create(state, network) {
|
|
12
|
+
let networkid = network.id;
|
|
13
|
+
|
|
14
|
+
let ircClient = new Irc.Client({
|
|
15
|
+
// Most options are set under the overloaded .connect()
|
|
16
|
+
version: null,
|
|
17
|
+
enable_chghost: true,
|
|
18
|
+
enable_setname: true,
|
|
19
|
+
message_max_length: 350,
|
|
20
|
+
});
|
|
21
|
+
ircClient.requestCap('znc.in/self-message');
|
|
22
|
+
ircClient.use(chathistoryMiddleware());
|
|
23
|
+
ircClient.use(clientMiddleware(state, network));
|
|
24
|
+
ircClient.use(typingMiddleware());
|
|
25
|
+
|
|
26
|
+
// Overload the connect() function to make sure we are connecting with the
|
|
27
|
+
// most recent connection details from the network state
|
|
28
|
+
let originalIrcClientConnect = ircClient.connect;
|
|
29
|
+
ircClient.connect = function connect(...args) {
|
|
30
|
+
// Set some defaults if we don't have eveything
|
|
31
|
+
if (!network.connection.nick) {
|
|
32
|
+
network.connection.nick = 'Guest' + Math.floor(Math.random() * 100);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
ircClient.options.host = network.connection.server;
|
|
36
|
+
ircClient.options.port = network.connection.port;
|
|
37
|
+
ircClient.options.tls = network.connection.tls;
|
|
38
|
+
ircClient.options.path = network.connection.path;
|
|
39
|
+
ircClient.options.password = network.connection.password;
|
|
40
|
+
if (network.password) {
|
|
41
|
+
ircClient.options.account = {
|
|
42
|
+
account: network.connection.nick,
|
|
43
|
+
password: network.password,
|
|
44
|
+
};
|
|
45
|
+
} else {
|
|
46
|
+
// No password so give an empty account config. This forces irc-framework to keep
|
|
47
|
+
// the server password (options.password) separate from SASL
|
|
48
|
+
ircClient.options.account = { };
|
|
49
|
+
}
|
|
50
|
+
ircClient.options.nick = network.connection.nick;
|
|
51
|
+
ircClient.options.username = network.username || network.connection.nick;
|
|
52
|
+
ircClient.options.gecos = network.gecos || 'https://kiwiirc.com/';
|
|
53
|
+
ircClient.options.encoding = network.connection.encoding;
|
|
54
|
+
ircClient.options.auto_reconnect = !!state.setting('autoReconnect');
|
|
55
|
+
ircClient.options.sasl_disconnect_on_fail = !!state.setting('disconnectOnSaslFail');
|
|
56
|
+
|
|
57
|
+
// Apply any irc-fw options specified in kiwiirc config
|
|
58
|
+
let configOptions = state.setting('ircFramework');
|
|
59
|
+
if (configOptions) {
|
|
60
|
+
Object.assign(ircClient.options, configOptions);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let eventObj = { network, transport: null };
|
|
64
|
+
state.$emit('network.connecting', eventObj);
|
|
65
|
+
|
|
66
|
+
if (eventObj.transport) {
|
|
67
|
+
// A plugin might use its own transport of some kind
|
|
68
|
+
ircClient.options.transport = eventObj.transport;
|
|
69
|
+
} else if (!network.connection.direct) {
|
|
70
|
+
// A direct connection uses a websocket to connect (note: some browsers limit
|
|
71
|
+
// the number of connections to the same host!).
|
|
72
|
+
// A non-direct connection will connect via the configured kiwi server using
|
|
73
|
+
// with our own irc-framework compatible transport.
|
|
74
|
+
ircClient.options.transport = ServerConnection.createChannelConstructor(
|
|
75
|
+
state.settings.kiwiServer,
|
|
76
|
+
(window.location.hash || '').substr(1),
|
|
77
|
+
networkid
|
|
78
|
+
);
|
|
79
|
+
} else {
|
|
80
|
+
// Use the irc-framework default transport
|
|
81
|
+
ircClient.options.transport = undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
originalIrcClientConnect.apply(ircClient, args);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Overload the raw() function so that we can emit outgoing IRC messages to plugins
|
|
88
|
+
let originalIrcClientRaw = ircClient.raw;
|
|
89
|
+
ircClient.raw = function raw(...args) {
|
|
90
|
+
let message = null;
|
|
91
|
+
|
|
92
|
+
if (args[0] instanceof Irc.Message) {
|
|
93
|
+
message = args[0];
|
|
94
|
+
} else {
|
|
95
|
+
let rawString = ircClient.rawString(...args);
|
|
96
|
+
message = Irc.ircLineParser(rawString);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let eventObj = { network, message, handled: false };
|
|
100
|
+
state.$emit('ircout', eventObj);
|
|
101
|
+
if (!eventObj.handled) {
|
|
102
|
+
originalIrcClientRaw.apply(ircClient, [message]);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
ircClient.on('raw', (event) => {
|
|
107
|
+
if (!network.setting('show_raw') && !state.setting('showRaw')) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let buffer = state.getOrAddBufferByName(networkid, '*raw');
|
|
112
|
+
state.addMessage(buffer, {
|
|
113
|
+
time: Date.now(),
|
|
114
|
+
nick: '',
|
|
115
|
+
message: (event.from_server ? '[S] ' : '[C] ') + event.line,
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
ircClient.on('typing', (event) => {
|
|
120
|
+
let user = state.getUser(network.id, event.nick);
|
|
121
|
+
if (user) {
|
|
122
|
+
user.typingStatus(event.target, event.status);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return ircClient;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function clientMiddleware(state, network) {
|
|
130
|
+
let networkid = network.id;
|
|
131
|
+
// eslint-disable-next-line
|
|
132
|
+
let numConnects = 0;
|
|
133
|
+
let isRegistered = false;
|
|
134
|
+
|
|
135
|
+
return function middlewareFn(client, rawEvents, parsedEvents) {
|
|
136
|
+
parsedEvents.use(parsedEventsHandler);
|
|
137
|
+
rawEvents.use(rawEventsHandler);
|
|
138
|
+
|
|
139
|
+
client.on('connecting', () => {
|
|
140
|
+
network.state_error = '';
|
|
141
|
+
network.state = 'connecting';
|
|
142
|
+
network.last_error = '';
|
|
143
|
+
network.last_error_numeric = 0;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
client.on('connected', () => {
|
|
147
|
+
network.state_error = '';
|
|
148
|
+
network.state = 'connected';
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
client.on('socket close', (err) => {
|
|
152
|
+
isRegistered = false;
|
|
153
|
+
network.state = 'disconnected';
|
|
154
|
+
network.state_error = err || '';
|
|
155
|
+
|
|
156
|
+
network.buffers.forEach((buffer) => {
|
|
157
|
+
if (!buffer) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
buffer.joined = false;
|
|
162
|
+
buffer.clearUsers();
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
function rawEventsHandler(command, event, rawLine, client, next) {
|
|
168
|
+
// Allow plugins to override raw IRC events
|
|
169
|
+
let eventObj = { ...event, raw: rawLine, handled: false };
|
|
170
|
+
state.$emit('irc.raw', command, eventObj, network);
|
|
171
|
+
if (eventObj.handled) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
state.$emit('irc.raw.' + command, command, eventObj, network);
|
|
176
|
+
if (eventObj.handled) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (command === '002') {
|
|
181
|
+
// Your host is server.example.net, running version InspIRCd-2.0
|
|
182
|
+
let param = event.params[1] || '';
|
|
183
|
+
let m = param.match(/running version (.*)$/);
|
|
184
|
+
network.ircd = m ?
|
|
185
|
+
m[1] :
|
|
186
|
+
'';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (command === 'CAP' && network.setting('show_raw_caps')) {
|
|
190
|
+
let params = [...event.params];
|
|
191
|
+
if (params[params.length - 1].indexOf(' ') > -1) {
|
|
192
|
+
params[params.length - 1] = ':' + params[params.length - 1];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let buffer = network.serverBuffer();
|
|
196
|
+
state.addMessage(buffer, {
|
|
197
|
+
time: Date.now(),
|
|
198
|
+
nick: '',
|
|
199
|
+
message: event.command + ' ' + params.join(' '),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
next();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function parsedEventsHandler(command, event, client, next) {
|
|
207
|
+
// Trigger this event through the state object first. If it's been handled
|
|
208
|
+
// somewhere else then we ignore it.
|
|
209
|
+
let ircEventObj = { handled: false };
|
|
210
|
+
state.$emit('irc.' + command, event, network, ircEventObj);
|
|
211
|
+
if (ircEventObj.handled) {
|
|
212
|
+
next();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Ignore any of the control messages. They're transport related to kiwi internals
|
|
217
|
+
if (event && event.command === 'CONTROL') {
|
|
218
|
+
next();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// If there is a time difference between this client and the server, convert it
|
|
223
|
+
// to match our local time so it makes sense to the user
|
|
224
|
+
let eventTime = (event && event.time) ?
|
|
225
|
+
network.ircClient.network.timeToLocal(event.time) :
|
|
226
|
+
Date.now();
|
|
227
|
+
let serverTime = (event && event.time) || 0;
|
|
228
|
+
|
|
229
|
+
if (command === 'channel_redirect') {
|
|
230
|
+
let b = network.bufferByName(event.from);
|
|
231
|
+
if (b) {
|
|
232
|
+
b.flags.redirect_to = event.to;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (command === 'registered') {
|
|
237
|
+
isRegistered = true;
|
|
238
|
+
network.nick = event.nick;
|
|
239
|
+
state.addUser(networkid, { nick: event.nick, username: client.user.username });
|
|
240
|
+
|
|
241
|
+
let serverBuffer = network.serverBuffer();
|
|
242
|
+
state.addMessage(serverBuffer, {
|
|
243
|
+
time: eventTime,
|
|
244
|
+
server_time: serverTime,
|
|
245
|
+
nick: '',
|
|
246
|
+
message: TextFormatting.t('connected_to', { network: client.network.name }),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Get some extra info about ourselves
|
|
250
|
+
client.raw('WHO ' + event.nick);
|
|
251
|
+
|
|
252
|
+
if (network.auto_commands) {
|
|
253
|
+
network.auto_commands.split('\n').forEach((line) => {
|
|
254
|
+
state.$emit('input.raw', line[0] === '/' ? line : `/${line}`, {
|
|
255
|
+
network: network,
|
|
256
|
+
buffer: serverBuffer,
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Join our channels
|
|
262
|
+
// If under bouncer mode, the bouncer will send the channels were joined to instead.
|
|
263
|
+
if (!network.connection.bncnetid) {
|
|
264
|
+
network.buffers.forEach((buffer) => {
|
|
265
|
+
if (buffer.isChannel() && buffer.enabled) {
|
|
266
|
+
client.join(buffer.name, buffer.key);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
numConnects++;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (command === 'server options') {
|
|
275
|
+
// If the network name has changed from the irc-framework default, update ours
|
|
276
|
+
// Also if it isn't a BNC network as the name is then derived from the BNC info instead
|
|
277
|
+
if (client.network.name !== 'Network' && !network.connection.bncnetid) {
|
|
278
|
+
network.name = client.network.name;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Show unhandled data from the server in the servers tab
|
|
283
|
+
if (command === 'unknown command') {
|
|
284
|
+
let buffer = network.serverBuffer();
|
|
285
|
+
let containsNick = event.params[0] === network.ircClient.user.nick;
|
|
286
|
+
let isChannelMessage = network.isChannelName(event.params[1]);
|
|
287
|
+
|
|
288
|
+
let message = '';
|
|
289
|
+
if (['486', '477'].includes(event.command)) {
|
|
290
|
+
// Messages from these events don't need the nick from params[0]
|
|
291
|
+
message = event.params[2];
|
|
292
|
+
} else if (containsNick && isChannelMessage) {
|
|
293
|
+
// This error is aimed at us and has a target channel
|
|
294
|
+
// replace the target buffer and remove unneeded params
|
|
295
|
+
let channelBuffer = network.bufferByName(event.params[1]);
|
|
296
|
+
if (channelBuffer) {
|
|
297
|
+
buffer = channelBuffer;
|
|
298
|
+
}
|
|
299
|
+
message = event.params.slice(2).join(', ');
|
|
300
|
+
} else if (containsNick) {
|
|
301
|
+
// Strip out our nick if it's the first params (many commands include this)
|
|
302
|
+
message = event.params.slice(1).join(', ');
|
|
303
|
+
} else {
|
|
304
|
+
message = event.params.join(', ');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Numerics for restrictions on sending messages to channels/users
|
|
308
|
+
const restrictedMessages = [
|
|
309
|
+
'486', // ERR_NONONREG old numeric used by unreal42
|
|
310
|
+
'477', // ERR_NEEDREGGEDNICK
|
|
311
|
+
'716', // RPL_TARGUMODEG
|
|
312
|
+
'717', // RPL_TARGNOTIFY
|
|
313
|
+
];
|
|
314
|
+
if (restrictedMessages.includes(event.command)) {
|
|
315
|
+
// You must log in with services to message this user/channel
|
|
316
|
+
// or CallerID +g
|
|
317
|
+
buffer = state.getOrAddBufferByName(network.id, event.params[1]);
|
|
318
|
+
|
|
319
|
+
// Only add this message if it does not match the previous message
|
|
320
|
+
// Typing status messages can cause a spam of this error type
|
|
321
|
+
state.addMessageNoRepeat(buffer, {
|
|
322
|
+
time: eventTime,
|
|
323
|
+
server_time: serverTime,
|
|
324
|
+
nick: '*',
|
|
325
|
+
message: message,
|
|
326
|
+
type: 'error',
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
if (
|
|
330
|
+
event.command === '477' &&
|
|
331
|
+
buffer.isChannel() &&
|
|
332
|
+
buffer.enabled &&
|
|
333
|
+
!buffer.joined
|
|
334
|
+
) {
|
|
335
|
+
// The buffer we tried to join requires a registered nick,
|
|
336
|
+
// disable it until the user manually tries to rejoin
|
|
337
|
+
buffer.enabled = false;
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
// Only show non-numeric commands
|
|
341
|
+
if (!event.command.match(/^\d+$/)) {
|
|
342
|
+
message += event.command + ' ';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
state.addMessage(buffer, {
|
|
346
|
+
time: eventTime,
|
|
347
|
+
server_time: serverTime,
|
|
348
|
+
nick: '',
|
|
349
|
+
message: message,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (command.toLowerCase() === 'batch start chathistory' && client.chathistory) {
|
|
355
|
+
// We have a new batch of messages. To prevent duplicate messages being shown, we remove
|
|
356
|
+
// all messages we have locally in the range of these new messages so that the new block
|
|
357
|
+
// of messages we recieved are displayed accurately. Each message in the block will
|
|
358
|
+
// trigger a 'message' event after this.
|
|
359
|
+
let startTime = 0;
|
|
360
|
+
let endTime = 0;
|
|
361
|
+
event.commands.forEach((message) => {
|
|
362
|
+
if (message.time && message.time > endTime) {
|
|
363
|
+
endTime = message.time;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (message.time && message.time < startTime) {
|
|
367
|
+
startTime = message.time;
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
if (!startTime || !endTime) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let buffer = state.getBufferByName(networkid, event.params[0]);
|
|
376
|
+
if (buffer) {
|
|
377
|
+
buffer.clearMessageRange(startTime, endTime);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (command === 'message') {
|
|
382
|
+
let isPrivateMessage = false;
|
|
383
|
+
let bufferName = event.from_server ? '*' : event.target;
|
|
384
|
+
|
|
385
|
+
// If the message came from a batch then params[0] is the bufferName
|
|
386
|
+
if (event.batch && event.batch.type === 'chathistory' && event.batch.params[0]) {
|
|
387
|
+
bufferName = event.batch.params[0];
|
|
388
|
+
isPrivateMessage = !network.isChannelName(bufferName);
|
|
389
|
+
} else if (!event.from_server && event.target === client.user.nick) {
|
|
390
|
+
// PMs should go to a buffer with the name of the other user
|
|
391
|
+
isPrivateMessage = true;
|
|
392
|
+
bufferName = event.nick;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Chanserv sometimes PMs messages about a channel on join in the format of
|
|
396
|
+
// [#channel] welcome!
|
|
397
|
+
// Redirect these to #channel
|
|
398
|
+
if (
|
|
399
|
+
event.nick.toLowerCase() === 'chanserv' &&
|
|
400
|
+
isPrivateMessage &&
|
|
401
|
+
event.message[0] === '['
|
|
402
|
+
) {
|
|
403
|
+
bufferName = event.message.substr(1, event.message.indexOf(']') - 1);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Notices from somewhere when we don't have an existing buffer for them should go into
|
|
407
|
+
// the server tab. ie. notices from servers
|
|
408
|
+
if (event.type === 'notice') {
|
|
409
|
+
let existingBuffer = state.getBufferByName(networkid, bufferName);
|
|
410
|
+
let noticeActiveBuffer = state.setting('noticeActiveBuffer');
|
|
411
|
+
let activeBuffer = state.getActiveBuffer();
|
|
412
|
+
let hasActiveBuffer = activeBuffer && activeBuffer.networkid === networkid;
|
|
413
|
+
|
|
414
|
+
// If we don't have a buffer for this notice sender, either show it in our active
|
|
415
|
+
// buffer or the server buffer
|
|
416
|
+
if (!existingBuffer) {
|
|
417
|
+
if (noticeActiveBuffer && hasActiveBuffer && !activeBuffer.isSpecial()) {
|
|
418
|
+
bufferName = activeBuffer.name;
|
|
419
|
+
} else {
|
|
420
|
+
bufferName = '*';
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const PM_BLOCK_BLOCKED = false;
|
|
426
|
+
// const PM_BLOCK_NOT_BLOCKED = true;
|
|
427
|
+
const PM_BLOCK_REQUIRES_CHECK = null;
|
|
428
|
+
|
|
429
|
+
let pmBlock = network.isNickExemptFromPmBlocks(event.nick);
|
|
430
|
+
let blockNewPms = state.setting('buffers.block_pms');
|
|
431
|
+
let buffer = state.getBufferByName(networkid, bufferName);
|
|
432
|
+
|
|
433
|
+
let textFormatType = 'privmsg';
|
|
434
|
+
if (event.type === 'action') {
|
|
435
|
+
textFormatType = 'action';
|
|
436
|
+
} else if (event.type === 'notice') {
|
|
437
|
+
textFormatType = 'notice';
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let messageBody = TextFormatting.formatText(textFormatType, {
|
|
441
|
+
nick: event.nick,
|
|
442
|
+
username: event.ident,
|
|
443
|
+
host: event.hostname,
|
|
444
|
+
text: event.message,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
let message = {
|
|
448
|
+
time: eventTime,
|
|
449
|
+
server_time: serverTime,
|
|
450
|
+
nick: event.nick,
|
|
451
|
+
message: messageBody,
|
|
452
|
+
type: event.type,
|
|
453
|
+
tags: event.tags || {},
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// If this is a new PM and the sending user is not exempt from blocks, ignore it
|
|
457
|
+
if (blockNewPms && isPrivateMessage && !buffer && pmBlock === PM_BLOCK_BLOCKED) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// If we need to manually check if this user is blocked..
|
|
462
|
+
// PM_BLOCK_REQUIRES_CHECK means we should whois the user to get their oper status. We
|
|
463
|
+
// allways allow messages from opers.
|
|
464
|
+
if (blockNewPms && isPrivateMessage && !buffer && pmBlock === PM_BLOCK_REQUIRES_CHECK) {
|
|
465
|
+
// if the nick is in pendingPms it has already issued a whois request
|
|
466
|
+
let awaitingWhois = !!_.find(network.pendingPms, { nick: event.nick });
|
|
467
|
+
network.pendingPms.push({ bufferName, message });
|
|
468
|
+
|
|
469
|
+
// Don't send another whois if we are already awaiting another
|
|
470
|
+
if (awaitingWhois) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
network.ircClient.whois(event.nick, event.nick, (whoisData) => {
|
|
475
|
+
network.pendingPms.forEach((pm, idx, obj) => {
|
|
476
|
+
let nickLower = pm.message.nick.toLowerCase();
|
|
477
|
+
if (nickLower === whoisData.nick.toLowerCase()) {
|
|
478
|
+
if (whoisData.operator) {
|
|
479
|
+
buffer = state.getOrAddBufferByName(network.id, pm.bufferName);
|
|
480
|
+
state.addMessage(buffer, pm.message);
|
|
481
|
+
}
|
|
482
|
+
obj.splice(idx, 1);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Make sure we have a buffer for our message
|
|
491
|
+
if (!buffer) {
|
|
492
|
+
buffer = state.getOrAddBufferByName(networkid, bufferName);
|
|
493
|
+
}
|
|
494
|
+
state.addMessage(buffer, message);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (command === 'wallops') {
|
|
498
|
+
let buffer = state.getOrAddBufferByName(networkid, '*');
|
|
499
|
+
let messageBody = TextFormatting.formatText('wallops', {
|
|
500
|
+
text: event.message,
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
state.addMessage(buffer, {
|
|
504
|
+
time: eventTime,
|
|
505
|
+
server_time: serverTime,
|
|
506
|
+
nick: event.nick,
|
|
507
|
+
message: messageBody,
|
|
508
|
+
type: 'wallops',
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (command === 'join') {
|
|
513
|
+
// If we have any buffers marked as being redirected to this new channel, update
|
|
514
|
+
// that buffer instead of creating a new one
|
|
515
|
+
if (event.nick === client.user.nick) {
|
|
516
|
+
network.buffers.forEach((b) => {
|
|
517
|
+
if ((b.flags.redirect_to || '').toLowerCase() === event.channel.toLowerCase()) {
|
|
518
|
+
state.$delete(b.flags, 'redirect_to');
|
|
519
|
+
b.rename(event.channel);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
let buffer = state.getOrAddBufferByName(networkid, event.channel);
|
|
525
|
+
|
|
526
|
+
// The case does not match, update buffer.name to the casing sent by the server
|
|
527
|
+
if (buffer.name !== event.channel) {
|
|
528
|
+
buffer.rename(event.channel);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
state.addUserToBuffer(buffer, {
|
|
532
|
+
nick: event.nick,
|
|
533
|
+
username: event.ident,
|
|
534
|
+
host: event.hostname,
|
|
535
|
+
realname: event.gecos,
|
|
536
|
+
account: event.account || '',
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
if (event.nick === client.user.nick) {
|
|
540
|
+
buffer.enabled = true;
|
|
541
|
+
buffer.joined = true;
|
|
542
|
+
buffer.flags.channel_badkey = false;
|
|
543
|
+
network.ircClient.raw('MODE', event.channel);
|
|
544
|
+
network.ircClient.who(event.channel);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
let ignoreEvent = state.setting('skipHiddenMessages') && !buffer.setting('show_joinparts');
|
|
548
|
+
if (!ignoreEvent || event.nick === client.user.nick) {
|
|
549
|
+
let nick = buffer.setting('show_hostnames') ?
|
|
550
|
+
TextFormatting.formatUserFull(event) :
|
|
551
|
+
TextFormatting.formatUser(event);
|
|
552
|
+
|
|
553
|
+
let messageBody = TextFormatting.formatAndT(
|
|
554
|
+
'channel_join',
|
|
555
|
+
null,
|
|
556
|
+
'has_joined',
|
|
557
|
+
{ nick: nick }
|
|
558
|
+
);
|
|
559
|
+
|
|
560
|
+
let typeExtra = (event.nick === client.user.nick) ? 'join_self' : 'join';
|
|
561
|
+
|
|
562
|
+
state.addMessage(buffer, {
|
|
563
|
+
time: eventTime,
|
|
564
|
+
server_time: serverTime,
|
|
565
|
+
nick: event.nick,
|
|
566
|
+
message: messageBody,
|
|
567
|
+
type: 'traffic',
|
|
568
|
+
type_extra: typeExtra,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (command === 'kick') {
|
|
573
|
+
let buffer = state.getOrAddBufferByName(networkid, event.channel);
|
|
574
|
+
state.removeUserFromBuffer(buffer, event.kicked);
|
|
575
|
+
|
|
576
|
+
if (event.kicked === client.user.nick) {
|
|
577
|
+
buffer.joined = false;
|
|
578
|
+
buffer.enabled = false;
|
|
579
|
+
buffer.clearUsers();
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
let messageBody = '';
|
|
583
|
+
let ignoreEvent = state.setting('skipHiddenMessages') && !buffer.setting('show_joinparts');
|
|
584
|
+
let isUserInvolved = [event.kicked, event.nick].includes(client.user.nick);
|
|
585
|
+
if (!ignoreEvent || isUserInvolved) {
|
|
586
|
+
if (event.kicked === client.user.nick) {
|
|
587
|
+
messageBody = TextFormatting.formatAndT(
|
|
588
|
+
'channel_selfkick',
|
|
589
|
+
{ reason: event.message },
|
|
590
|
+
'kicked_you_from',
|
|
591
|
+
{
|
|
592
|
+
nick: TextFormatting.formatUser(event),
|
|
593
|
+
channel: event.channel,
|
|
594
|
+
}
|
|
595
|
+
);
|
|
596
|
+
} else {
|
|
597
|
+
messageBody = TextFormatting.formatAndT(
|
|
598
|
+
'channel_kicked',
|
|
599
|
+
{ reason: event.message },
|
|
600
|
+
'was_kicked_from',
|
|
601
|
+
{
|
|
602
|
+
nick: event.kicked,
|
|
603
|
+
channel: event.channel,
|
|
604
|
+
chanop: TextFormatting.formatUser(event.nick),
|
|
605
|
+
}
|
|
606
|
+
);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
let typeExtra = (isUserInvolved) ? 'kick_self' : 'kick';
|
|
610
|
+
|
|
611
|
+
state.addMessage(buffer, {
|
|
612
|
+
time: eventTime,
|
|
613
|
+
server_time: serverTime,
|
|
614
|
+
nick: event.nick,
|
|
615
|
+
message: messageBody,
|
|
616
|
+
type: 'traffic',
|
|
617
|
+
type_extra: typeExtra,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (command === 'part') {
|
|
622
|
+
let buffer = state.getBufferByName(networkid, event.channel);
|
|
623
|
+
if (!buffer) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
state.removeUserFromBuffer(buffer, event.nick);
|
|
628
|
+
if (event.nick === client.user.nick) {
|
|
629
|
+
buffer.joined = false;
|
|
630
|
+
buffer.enabled = false;
|
|
631
|
+
buffer.clearUsers();
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Remove the user from network state if no remaining common channels
|
|
635
|
+
let remainingBuffers = state.getBuffersWithUser(networkid, event.nick);
|
|
636
|
+
if (remainingBuffers.length === 0) {
|
|
637
|
+
state.removeUser(networkid, {
|
|
638
|
+
nick: event.nick,
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
let ignoreEvent = state.setting('skipHiddenMessages') && !buffer.setting('show_joinparts');
|
|
643
|
+
if (!ignoreEvent || event.nick === client.user.nick) {
|
|
644
|
+
let nick = buffer.setting('show_hostnames') ?
|
|
645
|
+
TextFormatting.formatUserFull(event) :
|
|
646
|
+
TextFormatting.formatUser(event);
|
|
647
|
+
|
|
648
|
+
let messageBody = TextFormatting.formatAndT(
|
|
649
|
+
'channel_part',
|
|
650
|
+
{ reason: event.message },
|
|
651
|
+
'has_left',
|
|
652
|
+
{ nick: nick },
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
let typeExtra = (event.nick === client.user.nick) ? 'part_self' : 'part';
|
|
656
|
+
|
|
657
|
+
state.addMessage(buffer, {
|
|
658
|
+
time: eventTime,
|
|
659
|
+
server_time: serverTime,
|
|
660
|
+
nick: event.nick,
|
|
661
|
+
message: messageBody,
|
|
662
|
+
type: 'traffic',
|
|
663
|
+
type_extra: typeExtra,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (command === 'quit') {
|
|
668
|
+
let buffers = state.getBuffersWithUser(networkid, event.nick);
|
|
669
|
+
|
|
670
|
+
buffers.forEach((buffer) => {
|
|
671
|
+
if (!buffer) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (event.nick === client.user.nick) {
|
|
676
|
+
buffer.joined = false;
|
|
677
|
+
buffer.clearUsers();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
let ignoreEvent = state.setting('skipHiddenMessages') && !buffer.setting('show_joinparts');
|
|
681
|
+
if (ignoreEvent && event.nick !== client.user.nick) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
let nick = buffer.setting('show_hostnames') ?
|
|
686
|
+
TextFormatting.formatUserFull(event) :
|
|
687
|
+
TextFormatting.formatUser(event);
|
|
688
|
+
|
|
689
|
+
let messageBody = TextFormatting.formatAndT(
|
|
690
|
+
'channel_quit',
|
|
691
|
+
{ reason: event.message },
|
|
692
|
+
'has_left',
|
|
693
|
+
{ nick: nick }
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
let typeExtra = (event.nick === client.user.nick) ? 'quit_self' : 'quit';
|
|
697
|
+
|
|
698
|
+
state.addMessage(buffer, {
|
|
699
|
+
time: eventTime,
|
|
700
|
+
server_time: serverTime,
|
|
701
|
+
nick: event.nick,
|
|
702
|
+
message: messageBody,
|
|
703
|
+
type: 'traffic',
|
|
704
|
+
type_extra: typeExtra,
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
// Set the user as away before removing so away status indicators are updated
|
|
709
|
+
let user = state.getUser(networkid, event.nick);
|
|
710
|
+
if (user) {
|
|
711
|
+
user.away = 'offline';
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
state.removeUser(networkid, {
|
|
715
|
+
nick: event.nick,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (command === 'invite') {
|
|
720
|
+
let buffer = network.serverBuffer();
|
|
721
|
+
let activeNetwork = state.getActiveNetwork();
|
|
722
|
+
let activeBuffer = state.getActiveBuffer();
|
|
723
|
+
if (network === activeNetwork && !activeBuffer.isSpecial()) {
|
|
724
|
+
buffer = activeBuffer;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
let translationKey = 'invited_other';
|
|
728
|
+
if (event.invited === network.currentUser().nick) {
|
|
729
|
+
translationKey = 'invited_you';
|
|
730
|
+
} else if (event.nick === network.currentUser().nick) {
|
|
731
|
+
translationKey = 'invited_inviter';
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
state.addMessage(buffer, {
|
|
735
|
+
nick: '',
|
|
736
|
+
time: eventTime,
|
|
737
|
+
server_time: serverTime,
|
|
738
|
+
type: 'invite',
|
|
739
|
+
message: TextFormatting.t(translationKey, {
|
|
740
|
+
nick: event.nick,
|
|
741
|
+
invited: event.invited,
|
|
742
|
+
channel: event.channel,
|
|
743
|
+
}),
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (command === 'account') {
|
|
748
|
+
state.addUser(networkid, { nick: event.nick, account: event.account || '' });
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
if (command === 'whois') {
|
|
752
|
+
let obj = {
|
|
753
|
+
nick: event.nick,
|
|
754
|
+
host: event.hostname,
|
|
755
|
+
username: event.ident,
|
|
756
|
+
away: event.away || '',
|
|
757
|
+
realname: event.real_name,
|
|
758
|
+
hasWhois: true,
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
// Some other optional bits of info
|
|
762
|
+
[
|
|
763
|
+
'actual_host',
|
|
764
|
+
'helpop',
|
|
765
|
+
'bot',
|
|
766
|
+
'server',
|
|
767
|
+
'server_info',
|
|
768
|
+
'operator',
|
|
769
|
+
'channels',
|
|
770
|
+
'modes',
|
|
771
|
+
'idle',
|
|
772
|
+
'logon',
|
|
773
|
+
'registered_nick',
|
|
774
|
+
'account',
|
|
775
|
+
'secure',
|
|
776
|
+
'certfp',
|
|
777
|
+
'special',
|
|
778
|
+
].forEach((prop) => {
|
|
779
|
+
if (typeof event[prop] !== 'undefined') {
|
|
780
|
+
obj[prop] = event[prop];
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
state.addUser(networkid, obj);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
if (command === 'away') {
|
|
788
|
+
state.addUser(networkid, {
|
|
789
|
+
nick: event.nick,
|
|
790
|
+
away: event.message || '',
|
|
791
|
+
});
|
|
792
|
+
let buffer = state.getActiveBuffer();
|
|
793
|
+
if (buffer && event.nick === client.user.nick) {
|
|
794
|
+
network.away = 'away';
|
|
795
|
+
state.addMessage(buffer, {
|
|
796
|
+
time: eventTime,
|
|
797
|
+
server_time: serverTime,
|
|
798
|
+
nick: '*',
|
|
799
|
+
type: 'presence',
|
|
800
|
+
message: event.message,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (command === 'back') {
|
|
806
|
+
state.addUser(networkid, {
|
|
807
|
+
nick: event.nick,
|
|
808
|
+
away: '',
|
|
809
|
+
});
|
|
810
|
+
let buffer = state.getActiveBuffer();
|
|
811
|
+
if (buffer && event.nick === client.user.nick) {
|
|
812
|
+
network.away = '';
|
|
813
|
+
state.addMessage(buffer, {
|
|
814
|
+
time: eventTime,
|
|
815
|
+
server_time: serverTime,
|
|
816
|
+
nick: '*',
|
|
817
|
+
type: 'presence',
|
|
818
|
+
message: event.message,
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (command === 'wholist') {
|
|
824
|
+
state.usersTransaction(networkid, (users) => {
|
|
825
|
+
event.users.forEach((eventUser) => {
|
|
826
|
+
let userObj = {
|
|
827
|
+
nick: eventUser.nick,
|
|
828
|
+
host: eventUser.hostname || undefined,
|
|
829
|
+
username: eventUser.ident || undefined,
|
|
830
|
+
away: eventUser.away ? 'Away' : '',
|
|
831
|
+
realname: eventUser.real_name,
|
|
832
|
+
account: eventUser.account || undefined,
|
|
833
|
+
};
|
|
834
|
+
let user = state.addUser(networkid, userObj, users);
|
|
835
|
+
if (!user) {
|
|
836
|
+
// Should never happen as this network should always exist
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
let buffer = network.bufferByName(eventUser.channel);
|
|
841
|
+
if (!buffer || !user.buffers[buffer.id]) {
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Add all the user channel modes
|
|
846
|
+
let modes = user.buffers[buffer.id].modes;
|
|
847
|
+
eventUser.channel_modes.forEach((mode) => {
|
|
848
|
+
if (modes.indexOf(mode) === -1) {
|
|
849
|
+
modes.push(mode);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
});
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
if (command === 'channel list start') {
|
|
857
|
+
network.channel_list_cache = [];
|
|
858
|
+
network.channel_list_state = 'updating';
|
|
859
|
+
}
|
|
860
|
+
if (command === 'channel list') {
|
|
861
|
+
network.channel_list_state = 'updating';
|
|
862
|
+
// Filter private channels from the channel list
|
|
863
|
+
let filteredEvent = _.filter(event, (o) => o.channel !== '*');
|
|
864
|
+
// Store the channels in channel_list_cache before moving it all to
|
|
865
|
+
// channel_list at the end. This gives a huge performance boost since
|
|
866
|
+
// it doesn't need to be all reactive for every update
|
|
867
|
+
network.channel_list_cache = (network.channel_list_cache || []).concat(filteredEvent);
|
|
868
|
+
}
|
|
869
|
+
if (command === 'channel list end') {
|
|
870
|
+
network.channel_list = network.channel_list_cache || [];
|
|
871
|
+
network.channel_list_state = 'updated';
|
|
872
|
+
delete network.channel_list_cache;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (command === 'motd') {
|
|
876
|
+
let buffer = network.serverBuffer();
|
|
877
|
+
let messageBody = TextFormatting.formatText('motd', {
|
|
878
|
+
text: event.motd,
|
|
879
|
+
});
|
|
880
|
+
state.addMessage(buffer, {
|
|
881
|
+
time: eventTime,
|
|
882
|
+
server_time: serverTime,
|
|
883
|
+
nick: '',
|
|
884
|
+
message: messageBody,
|
|
885
|
+
type: 'motd',
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
if (command === 'nick in use') {
|
|
890
|
+
let shouldChangeNick = !client.connection.registered && state.setting('changeNickOnCollision');
|
|
891
|
+
let newNick = client.user.nick.replace(/\d+$/, '') + rand(1, 99);
|
|
892
|
+
|
|
893
|
+
let translationKey = shouldChangeNick ? 'nick_in_use_retrying' : 'error_nick_in_use';
|
|
894
|
+
let translationVars = { nick: client.user.nick };
|
|
895
|
+
if (shouldChangeNick) {
|
|
896
|
+
translationVars.newnick = newNick;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
let messageBody = TextFormatting.formatAndT(
|
|
900
|
+
'nickname_alreadyinuse',
|
|
901
|
+
null,
|
|
902
|
+
translationKey,
|
|
903
|
+
translationVars,
|
|
904
|
+
);
|
|
905
|
+
|
|
906
|
+
let message = {
|
|
907
|
+
time: eventTime,
|
|
908
|
+
server_time: serverTime,
|
|
909
|
+
nick: '',
|
|
910
|
+
message: messageBody,
|
|
911
|
+
type: 'error',
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
let activeBuffer = state.getActiveBuffer();
|
|
915
|
+
let buffer = network.serverBuffer();
|
|
916
|
+
|
|
917
|
+
if (activeBuffer && activeBuffer.networkid === network.id) {
|
|
918
|
+
buffer = activeBuffer;
|
|
919
|
+
}
|
|
920
|
+
if (buffer) {
|
|
921
|
+
state.addMessage(buffer, message);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (shouldChangeNick) {
|
|
925
|
+
client.changeNick(newNick);
|
|
926
|
+
} else if (!client.connection.registered) {
|
|
927
|
+
network.last_error = messageBody;
|
|
928
|
+
network.ircClient.connection.end();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (command === 'nick') {
|
|
933
|
+
if (event.nick === client.user.nick) {
|
|
934
|
+
network.nick = event.new_nick;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
state.changeUserNick(networkid, event.nick, event.new_nick);
|
|
938
|
+
|
|
939
|
+
let messageBody = TextFormatting.formatAndT(
|
|
940
|
+
'nick_changed',
|
|
941
|
+
null,
|
|
942
|
+
'now_known_as',
|
|
943
|
+
{ nick: event.nick, newnick: event.new_nick },
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
let typeExtra = (event.nick === client.user.nick) ? 'nick_self' : '';
|
|
947
|
+
|
|
948
|
+
let buffers = state.getBuffersWithUser(networkid, event.new_nick);
|
|
949
|
+
buffers.forEach((buffer) => {
|
|
950
|
+
let ignoreEvent = state.setting('skipHiddenMessages') && !buffer.setting('show_nick_changes');
|
|
951
|
+
if (ignoreEvent && event.nick !== client.user.nick) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
state.addMessage(buffer, {
|
|
956
|
+
time: eventTime,
|
|
957
|
+
server_time: serverTime,
|
|
958
|
+
nick: '',
|
|
959
|
+
message: messageBody,
|
|
960
|
+
type: 'nick',
|
|
961
|
+
type_extra: typeExtra,
|
|
962
|
+
});
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (command === 'userlist') {
|
|
967
|
+
let buffer = state.getOrAddBufferByName(networkid, event.channel);
|
|
968
|
+
let ucNick = client.user.nick.toUpperCase();
|
|
969
|
+
let hadExistingUsers = Object.keys(buffer.users)
|
|
970
|
+
.filter((u) => u !== ucNick)
|
|
971
|
+
.length > 0;
|
|
972
|
+
let users = [];
|
|
973
|
+
event.users.forEach((user) => {
|
|
974
|
+
users.push({
|
|
975
|
+
user: {
|
|
976
|
+
nick: user.nick,
|
|
977
|
+
username: user.ident,
|
|
978
|
+
hostname: user.hostname,
|
|
979
|
+
},
|
|
980
|
+
modes: user.modes,
|
|
981
|
+
});
|
|
982
|
+
});
|
|
983
|
+
state.addMultipleUsersToBuffer(buffer, users);
|
|
984
|
+
|
|
985
|
+
if (!hadExistingUsers && network.ircClient.chathistory.isSupported()) {
|
|
986
|
+
// TODO: If this is a reconnect (numConnects > 1) then paginate backwards
|
|
987
|
+
// until we reach our last message.
|
|
988
|
+
// OR
|
|
989
|
+
// Add a marker at the gap between this new chathistory block starts and when
|
|
990
|
+
// the existing messages end so that we can add a "load missing messages"
|
|
991
|
+
// button there or have it auto request them when it scrolls into view
|
|
992
|
+
if (
|
|
993
|
+
buffer.isChannel()
|
|
994
|
+
&& ['all', 'channels'].includes(buffer.setting('auto_request_history'))
|
|
995
|
+
) {
|
|
996
|
+
buffer.requestLatestScrollback();
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
if (command === 'user updated') {
|
|
1002
|
+
const user = network.userByName(event.nick);
|
|
1003
|
+
if (user) {
|
|
1004
|
+
Object.entries(event).forEach(([key, val]) => {
|
|
1005
|
+
if (key.indexOf('new_') !== 0) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const paramName = key.substr(4);
|
|
1010
|
+
switch (paramName) {
|
|
1011
|
+
case 'gecos':
|
|
1012
|
+
user.realname = val;
|
|
1013
|
+
break;
|
|
1014
|
+
case 'ident':
|
|
1015
|
+
user.username = val;
|
|
1016
|
+
break;
|
|
1017
|
+
case 'hostname':
|
|
1018
|
+
user.host = val;
|
|
1019
|
+
break;
|
|
1020
|
+
default:
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
if (command === 'channel info') {
|
|
1027
|
+
let buffer = network.bufferByName(event.channel);
|
|
1028
|
+
if (!buffer) {
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
if (event.modes) {
|
|
1033
|
+
let modeStrs = [];
|
|
1034
|
+
|
|
1035
|
+
event.modes.forEach((mode) => {
|
|
1036
|
+
let adding = mode.mode[0] === '+';
|
|
1037
|
+
let modeChar = mode.mode.substr(1);
|
|
1038
|
+
|
|
1039
|
+
if (adding) {
|
|
1040
|
+
state.$set(buffer.modes, modeChar, mode.param);
|
|
1041
|
+
} else if (!adding) {
|
|
1042
|
+
state.$delete(buffer.modes, modeChar);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
modeStrs.push(mode.mode + (mode.param ? ' ' + mode.param : ''));
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
if (buffer.flags.requested_modes) {
|
|
1049
|
+
state.addMessage(buffer, {
|
|
1050
|
+
time: eventTime,
|
|
1051
|
+
server_time: serverTime,
|
|
1052
|
+
nick: '*',
|
|
1053
|
+
message: buffer.name + ' ' + modeStrs.join(', '),
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
if (event.created_at) {
|
|
1059
|
+
buffer.created_at = new Date(event.created_at * 1000);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
if (event.created_at && buffer.flags.requested_modes) {
|
|
1063
|
+
let tFormat = buffer.setting('timestamp_full_format');
|
|
1064
|
+
let timeCreated = tFormat ?
|
|
1065
|
+
strftime(tFormat, new Date(event.created_at * 1000)) :
|
|
1066
|
+
(new Date(event.created_at * 1000)).toLocaleString();
|
|
1067
|
+
|
|
1068
|
+
state.addMessage(buffer, {
|
|
1069
|
+
time: eventTime,
|
|
1070
|
+
server_time: serverTime,
|
|
1071
|
+
nick: '*',
|
|
1072
|
+
message: buffer.name + ' ' + timeCreated,
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (command === 'mode') {
|
|
1078
|
+
let buffer = network.bufferByName(event.target);
|
|
1079
|
+
let modeStrs = {};
|
|
1080
|
+
if (buffer) {
|
|
1081
|
+
// Join all the same mode changes together so they can be shown on one
|
|
1082
|
+
// line such as "prawnsalad sets +b on nick1, nick2"
|
|
1083
|
+
event.modes.forEach((mode) => {
|
|
1084
|
+
modeStrs[mode.mode] = modeStrs[mode.mode] || [];
|
|
1085
|
+
|
|
1086
|
+
// If this mode has a user prefix then we need to update the user object
|
|
1087
|
+
let prefix = _.find(network.ircClient.network.options.PREFIX, {
|
|
1088
|
+
mode: mode.mode[1],
|
|
1089
|
+
});
|
|
1090
|
+
|
|
1091
|
+
if (prefix) {
|
|
1092
|
+
let user = state.getUser(network.id, mode.param);
|
|
1093
|
+
if (user) {
|
|
1094
|
+
let adding = mode.mode[0] === '+';
|
|
1095
|
+
let modes = user.buffers[buffer.id].modes;
|
|
1096
|
+
let modeIdx = modes.indexOf(prefix.mode);
|
|
1097
|
+
|
|
1098
|
+
// Add or remove the mode from the users mode list
|
|
1099
|
+
if (adding && modeIdx === -1) {
|
|
1100
|
+
modes.push(prefix.mode);
|
|
1101
|
+
} else if (!adding && modeIdx > -1) {
|
|
1102
|
+
modes.splice(modeIdx, 1);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
modeStrs[mode.mode].push({ target: mode.param });
|
|
1107
|
+
} else {
|
|
1108
|
+
// Not a user prefix, add it as a channel mode
|
|
1109
|
+
// TODO: Why are these not appearing as the 'channel info' command?
|
|
1110
|
+
let adding = mode.mode[0] === '+';
|
|
1111
|
+
let modeChar = mode.mode.substr(1);
|
|
1112
|
+
|
|
1113
|
+
if (adding) {
|
|
1114
|
+
state.$set(buffer.modes, modeChar, mode.param);
|
|
1115
|
+
} else if (!adding) {
|
|
1116
|
+
state.$delete(buffer.modes, modeChar);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
modeStrs[mode.mode].push({ target: buffer.name, param: mode.param });
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
// Mode -> locale ID mappings
|
|
1124
|
+
// If a mode isn't found here, the local ID modes_other is used
|
|
1125
|
+
let modeLocaleIds = {
|
|
1126
|
+
'+o': 'modes_give_ops',
|
|
1127
|
+
'-o': 'modes_take_ops',
|
|
1128
|
+
'+h': 'modes_give_halfops',
|
|
1129
|
+
'-h': 'modes_take_halfops',
|
|
1130
|
+
'+v': 'modes_give_voice',
|
|
1131
|
+
'-v': 'modes_take_voice',
|
|
1132
|
+
'+a': 'modes_give_admin',
|
|
1133
|
+
'-a': 'modes_take_admin',
|
|
1134
|
+
'+q': 'modes_give_owner',
|
|
1135
|
+
'-q': 'modes_take_owner',
|
|
1136
|
+
'+b': 'modes_gives_ban',
|
|
1137
|
+
'-b': 'modes_takes_ban',
|
|
1138
|
+
};
|
|
1139
|
+
|
|
1140
|
+
let prefixes = network.ircClient.network.options.PREFIX;
|
|
1141
|
+
Object.keys(modeLocaleIds).forEach((mode) => {
|
|
1142
|
+
let supported = mode[1] === 'b' || prefixes.find((p) => mode[1] === p.mode);
|
|
1143
|
+
if (!supported) {
|
|
1144
|
+
delete modeLocaleIds[mode];
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
// Some modes have specific data for its locale data while most
|
|
1149
|
+
// use a default. The returned objects are passed to the translation
|
|
1150
|
+
// functions to build the translation
|
|
1151
|
+
let modeLocaleDataBuilders = {
|
|
1152
|
+
default(targets, mode) {
|
|
1153
|
+
return {
|
|
1154
|
+
mode: mode + (targets[0].param ? ' ' + targets[0].param : ''),
|
|
1155
|
+
target: targets.map((t) => t.target).join(', '),
|
|
1156
|
+
nick: event.nick,
|
|
1157
|
+
};
|
|
1158
|
+
},
|
|
1159
|
+
b(targets, mode) {
|
|
1160
|
+
return {
|
|
1161
|
+
mode: mode,
|
|
1162
|
+
target: targets[0].param ? targets[0].param : '',
|
|
1163
|
+
nick: event.nick,
|
|
1164
|
+
};
|
|
1165
|
+
},
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
// Show one line per mode, listing each effecting user
|
|
1169
|
+
_.each(modeStrs, (targets, mode) => {
|
|
1170
|
+
let targetNicks = targets.map((t) => t.target);
|
|
1171
|
+
let ignoreEvent = state.setting('skipHiddenMessages') && !buffer.setting('show_mode_changes');
|
|
1172
|
+
let isUserInvolved = [event.nick, ...targetNicks].includes(client.user.nick);
|
|
1173
|
+
if (ignoreEvent && !isUserInvolved) {
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// Find a locale data builder for this mode
|
|
1178
|
+
let builders = modeLocaleDataBuilders;
|
|
1179
|
+
let localeDataFn = builders[mode[1]] || builders.default;
|
|
1180
|
+
let localeData = localeDataFn(targets, mode);
|
|
1181
|
+
|
|
1182
|
+
// Translate using the built locale data
|
|
1183
|
+
let localeKey = modeLocaleIds[mode] || 'modes_other';
|
|
1184
|
+
let text = TextFormatting.t(localeKey, localeData);
|
|
1185
|
+
|
|
1186
|
+
let messageBody = TextFormatting.formatText('mode', {
|
|
1187
|
+
nick: event.nick,
|
|
1188
|
+
username: event.ident,
|
|
1189
|
+
host: event.hostname,
|
|
1190
|
+
target: targetNicks.join(', '),
|
|
1191
|
+
text,
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
let typeExtra = (isUserInvolved) ?
|
|
1195
|
+
'mode_self' :
|
|
1196
|
+
'';
|
|
1197
|
+
|
|
1198
|
+
state.addMessage(buffer, {
|
|
1199
|
+
time: eventTime,
|
|
1200
|
+
server_time: serverTime,
|
|
1201
|
+
nick: '',
|
|
1202
|
+
message: messageBody,
|
|
1203
|
+
type: 'mode',
|
|
1204
|
+
type_extra: typeExtra,
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
} else {
|
|
1208
|
+
// target is not a channel buffer (user mode ?)
|
|
1209
|
+
// if mode had param, show in a new line
|
|
1210
|
+
let modeslines = {};
|
|
1211
|
+
|
|
1212
|
+
// Group each - or + modes to each of their own message lines
|
|
1213
|
+
event.modes.forEach((mode) => {
|
|
1214
|
+
if (mode.param) {
|
|
1215
|
+
modeslines[mode.mode] = ' ' + mode.param;
|
|
1216
|
+
} else if (mode.mode[0] === '-') {
|
|
1217
|
+
if (!modeslines['-']) {
|
|
1218
|
+
modeslines['-'] = '';
|
|
1219
|
+
}
|
|
1220
|
+
modeslines['-'] += mode.mode.slice(1);
|
|
1221
|
+
} else {
|
|
1222
|
+
if (!modeslines['+']) {
|
|
1223
|
+
modeslines['+'] = '';
|
|
1224
|
+
}
|
|
1225
|
+
if (mode.mode[0] === '+') {
|
|
1226
|
+
modeslines['+'] += mode.mode.slice(1);
|
|
1227
|
+
} else {
|
|
1228
|
+
modeslines['+'] += mode.mode;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
});
|
|
1232
|
+
|
|
1233
|
+
let serverBuffer = network.serverBuffer();
|
|
1234
|
+
_.each(modeslines, (mode, value) => {
|
|
1235
|
+
let text = TextFormatting.t('modes_other', {
|
|
1236
|
+
nick: event.nick,
|
|
1237
|
+
target: event.target,
|
|
1238
|
+
mode: value + mode,
|
|
1239
|
+
});
|
|
1240
|
+
let messageBody = TextFormatting.formatText('mode', {
|
|
1241
|
+
nick: event.nick,
|
|
1242
|
+
username: event.ident,
|
|
1243
|
+
host: event.hostname,
|
|
1244
|
+
target: event.target,
|
|
1245
|
+
text,
|
|
1246
|
+
});
|
|
1247
|
+
state.addMessage(serverBuffer, {
|
|
1248
|
+
time: eventTime,
|
|
1249
|
+
server_time: serverTime,
|
|
1250
|
+
nick: '',
|
|
1251
|
+
message: messageBody,
|
|
1252
|
+
type: 'mode',
|
|
1253
|
+
type_extra: 'mode_self',
|
|
1254
|
+
});
|
|
1255
|
+
});
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
if (command === 'banlist') {
|
|
1260
|
+
let buffer = state.getBufferByName(networkid, event.channel);
|
|
1261
|
+
if (buffer && buffer.flags.requested_banlist) {
|
|
1262
|
+
if (!event.bans || event.bans.length === 0) {
|
|
1263
|
+
state.addMessage(buffer, {
|
|
1264
|
+
time: eventTime,
|
|
1265
|
+
server_time: serverTime,
|
|
1266
|
+
nick: '',
|
|
1267
|
+
message: TextFormatting.t('bans_nobody'),
|
|
1268
|
+
type: 'banlist',
|
|
1269
|
+
});
|
|
1270
|
+
} else {
|
|
1271
|
+
let banText = '';
|
|
1272
|
+
_.each(event.bans, (ban) => {
|
|
1273
|
+
let dateStr = (new Date(ban.banned_at * 1000)).toDateString();
|
|
1274
|
+
banText += `+b ${ban.banned} [by ${ban.banned_by}, ${dateStr}]\n`;
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
state.addMessage(buffer, {
|
|
1278
|
+
time: eventTime,
|
|
1279
|
+
server_time: serverTime,
|
|
1280
|
+
nick: '*',
|
|
1281
|
+
message: banText,
|
|
1282
|
+
type: 'banlist',
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
buffer.flags.requested_banlist = false;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
if (command === 'topic') {
|
|
1290
|
+
let buffer = state.getOrAddBufferByName(networkid, event.channel);
|
|
1291
|
+
buffer.topic = event.topic || '';
|
|
1292
|
+
|
|
1293
|
+
let typeExtra = '';
|
|
1294
|
+
let messageBody = '';
|
|
1295
|
+
|
|
1296
|
+
if (event.nick) {
|
|
1297
|
+
typeExtra = 'topic_change';
|
|
1298
|
+
messageBody = TextFormatting.formatAndT(
|
|
1299
|
+
'channel_topic',
|
|
1300
|
+
null,
|
|
1301
|
+
'changed_topic_to',
|
|
1302
|
+
{ nick: event.nick, topic: buffer.topic },
|
|
1303
|
+
);
|
|
1304
|
+
} else if (buffer.topic.trim()) {
|
|
1305
|
+
typeExtra = 'topic_join';
|
|
1306
|
+
messageBody = TextFormatting.formatText('channel_topic', buffer.topic);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
if (messageBody) {
|
|
1310
|
+
state.addMessage(buffer, {
|
|
1311
|
+
time: eventTime,
|
|
1312
|
+
server_time: serverTime,
|
|
1313
|
+
nick: '',
|
|
1314
|
+
message: messageBody,
|
|
1315
|
+
type: 'topic',
|
|
1316
|
+
type_extra: typeExtra,
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
if (command === 'help') {
|
|
1322
|
+
let buffer = state.getOrAddBufferByName(networkid, '*help');
|
|
1323
|
+
state.addMessage(buffer, {
|
|
1324
|
+
time: eventTime,
|
|
1325
|
+
server_time: serverTime,
|
|
1326
|
+
nick: '',
|
|
1327
|
+
message: event.help,
|
|
1328
|
+
type: 'help',
|
|
1329
|
+
tags: event.tags || {},
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
if (command === 'ctcp response' || command === 'ctcp request') {
|
|
1334
|
+
let buffer = network.bufferByName(event.target) || network.serverBuffer();
|
|
1335
|
+
let textFormatId = command === 'ctcp response' ?
|
|
1336
|
+
'ctcp_response' :
|
|
1337
|
+
'ctcp_request';
|
|
1338
|
+
let messageBody = TextFormatting.formatText(textFormatId, {
|
|
1339
|
+
nick: event.nick,
|
|
1340
|
+
message: event.message,
|
|
1341
|
+
type: event.type,
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
state.addMessage(buffer, {
|
|
1345
|
+
time: eventTime,
|
|
1346
|
+
server_time: serverTime,
|
|
1347
|
+
nick: '',
|
|
1348
|
+
message: messageBody,
|
|
1349
|
+
type: 'error',
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
if (command === 'ctcp request' && event.type === 'VERSION') {
|
|
1353
|
+
let target = event.from_server ?
|
|
1354
|
+
event.hostname :
|
|
1355
|
+
event.nick;
|
|
1356
|
+
client.ctcpResponse(target, 'VERSION', 'Kiwi IRC');
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
if (command === 'nick invalid') {
|
|
1361
|
+
let messageBody = TextFormatting.formatText('general_error', {
|
|
1362
|
+
text: event.reason,
|
|
1363
|
+
});
|
|
1364
|
+
let buffer = state.getActiveBuffer();
|
|
1365
|
+
state.addMessage(buffer, {
|
|
1366
|
+
time: eventTime,
|
|
1367
|
+
server_time: serverTime,
|
|
1368
|
+
nick: '',
|
|
1369
|
+
message: messageBody,
|
|
1370
|
+
type: 'error',
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
if (!isRegistered) {
|
|
1374
|
+
network.last_error_numeric = 432;
|
|
1375
|
+
network.last_error = event.reason;
|
|
1376
|
+
network.ircClient.quit();
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
if (command === 'loggedin' || command === 'loggedout') {
|
|
1381
|
+
let translationKey = command === 'loggedin' ? 'logged_in' : 'logged_out';
|
|
1382
|
+
|
|
1383
|
+
let buffers = [network.serverBuffer()];
|
|
1384
|
+
let activeBuffer = state.getActiveBuffer();
|
|
1385
|
+
if (activeBuffer?.networkid === network.id && activeBuffer !== buffers[0]) {
|
|
1386
|
+
buffers.push(activeBuffer);
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
let messageBody = TextFormatting.formatAndT(
|
|
1390
|
+
'notice',
|
|
1391
|
+
null,
|
|
1392
|
+
translationKey,
|
|
1393
|
+
{ account: event.account }
|
|
1394
|
+
);
|
|
1395
|
+
|
|
1396
|
+
buffers.forEach((buffer) => {
|
|
1397
|
+
state.addMessage(buffer, {
|
|
1398
|
+
time: Date.now(),
|
|
1399
|
+
nick: '',
|
|
1400
|
+
message: messageBody,
|
|
1401
|
+
type: 'notice',
|
|
1402
|
+
});
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
if (command === 'sasl failed') {
|
|
1407
|
+
let failMessage = TextFormatting.formatAndT(
|
|
1408
|
+
'general_error',
|
|
1409
|
+
null,
|
|
1410
|
+
'login_failed'
|
|
1411
|
+
);
|
|
1412
|
+
|
|
1413
|
+
let buffers = [network.serverBuffer()];
|
|
1414
|
+
let activeBuffer = state.getActiveBuffer();
|
|
1415
|
+
if (activeBuffer?.networkid === network.id && activeBuffer !== buffers[0]) {
|
|
1416
|
+
buffers.push(activeBuffer);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
let disconnectOnSaslFail = network.ircClient.connection.options.sasl_disconnect_on_fail;
|
|
1420
|
+
|
|
1421
|
+
if (disconnectOnSaslFail && network.state !== 'connected') {
|
|
1422
|
+
// The client is going to disconnect set last_error so it is show to the user
|
|
1423
|
+
network.last_error = failMessage;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
if (!disconnectOnSaslFail) {
|
|
1427
|
+
// Only add messages if the client is not going to disconnect on failure
|
|
1428
|
+
buffers.forEach((buffer) => {
|
|
1429
|
+
state.addMessage(buffer, {
|
|
1430
|
+
time: Date.now(),
|
|
1431
|
+
nick: '',
|
|
1432
|
+
message: failMessage,
|
|
1433
|
+
type: 'error',
|
|
1434
|
+
});
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
if (command === 'irc error') {
|
|
1440
|
+
let buffer;
|
|
1441
|
+
if (event.channel || event.nick) {
|
|
1442
|
+
buffer = state.getOrAddBufferByName(network.id, event.channel || event.nick);
|
|
1443
|
+
}
|
|
1444
|
+
if (!buffer) {
|
|
1445
|
+
buffer = network.serverBuffer();
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
if (!buffer) {
|
|
1449
|
+
// we could not find a buffer, this is likely because the network was removed
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// TODO: Some of these errors contain a .error property which we can match against,
|
|
1454
|
+
// ie. password_mismatch.
|
|
1455
|
+
|
|
1456
|
+
if (event.error === 'bad_channel_key') {
|
|
1457
|
+
buffer.flags.channel_badkey = true;
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// ignore error 432 (erroneous nickname) as it is handled above
|
|
1461
|
+
if (event.reason && network.last_error_numeric !== 432) {
|
|
1462
|
+
if (!isRegistered) {
|
|
1463
|
+
network.last_error = event.reason;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
let messageRaw = event.reason || event.error;
|
|
1467
|
+
if (event.error === 'unknown_command') {
|
|
1468
|
+
messageRaw = `${messageRaw} "${event.command}"`;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
let messageBody = TextFormatting.formatText('general_error', {
|
|
1472
|
+
text: messageRaw,
|
|
1473
|
+
});
|
|
1474
|
+
|
|
1475
|
+
let message = {
|
|
1476
|
+
time: eventTime,
|
|
1477
|
+
server_time: serverTime,
|
|
1478
|
+
nick: '',
|
|
1479
|
+
message: messageBody,
|
|
1480
|
+
type: 'error',
|
|
1481
|
+
};
|
|
1482
|
+
|
|
1483
|
+
if (event.error === 'cannot_send_to_channel') {
|
|
1484
|
+
// Only add this message if it does not match the previous message
|
|
1485
|
+
// Typing status messages can cause a spam of this error type
|
|
1486
|
+
state.addMessageNoRepeat(buffer, message);
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
state.addMessage(buffer, message);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// Getting an error about a channel while we are not joined means that we couldn't join
|
|
1494
|
+
// or do some action on it. Disable it until we manually reattempt to join.
|
|
1495
|
+
if (buffer.isChannel() && !buffer.joined) {
|
|
1496
|
+
buffer.enabled = false;
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
next();
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
function rand(min, max) {
|
|
1505
|
+
return Math.floor(Math.random() * (max - min + 1) + min);
|
|
1506
|
+
}
|