serverless-ircd 0.5.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.
Files changed (394) hide show
  1. package/.github/workflows/ci.yml +40 -3
  2. package/.github/workflows/deploy-aws.yml +1 -2
  3. package/.github/workflows/deploy-cf-tcp.yml +1 -5
  4. package/.github/workflows/deploy-cf.yml +1 -2
  5. package/.gitmodules +3 -0
  6. package/CHANGELOG.md +474 -0
  7. package/README.md +219 -22
  8. package/apps/aws-stack/README.md +33 -0
  9. package/apps/aws-stack/package.json +1 -1
  10. package/apps/aws-stack/src/aws-stack.ts +74 -9
  11. package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
  12. package/apps/aws-stack/tests/stack.test.ts +2 -2
  13. package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
  14. package/apps/aws-stack/tests/vitest.setup.ts +18 -0
  15. package/apps/aws-stack/vitest.config.ts +3 -0
  16. package/apps/cf-tcp-container/package.json +1 -1
  17. package/apps/cf-tcp-container/src/config-loader.ts +6 -7
  18. package/apps/cf-tcp-container/src/container-server.ts +3 -0
  19. package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
  20. package/apps/cf-tcp-container/wrangler.toml +1 -1
  21. package/apps/cf-worker/package.json +2 -1
  22. package/apps/cf-worker/src/origin-allowlist.ts +99 -0
  23. package/apps/cf-worker/src/worker.ts +89 -9
  24. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +18 -0
  25. package/apps/cf-worker/tests/fixtures/web-dist/index.html +18 -0
  26. package/apps/cf-worker/tests/origin-allowlist.test.ts +101 -0
  27. package/apps/cf-worker/tests/smoke.test.ts +28 -2
  28. package/apps/cf-worker/tests/ws-origin.test.ts +89 -0
  29. package/apps/cf-worker/vitest.config.ts +1 -1
  30. package/apps/cf-worker/wrangler.test.toml +23 -0
  31. package/apps/cf-worker/wrangler.toml +59 -3
  32. package/apps/local-cli/package.json +1 -1
  33. package/apps/local-cli/src/config-loader.ts +6 -6
  34. package/apps/local-cli/src/main.ts +20 -0
  35. package/apps/local-cli/src/server.ts +9 -7
  36. package/apps/local-cli/tests/e2e.test.ts +112 -28
  37. package/apps/local-cli/tests/tcp.test.ts +71 -19
  38. package/apps/local-cli/tests/ws-subprotocol.test.ts +12 -7
  39. package/apps/web/landing/index.html +217 -0
  40. package/apps/web/package.json +28 -0
  41. package/apps/web/scripts/build.mjs +125 -0
  42. package/apps/web/src/build-env.ts +43 -0
  43. package/apps/web/src/config-schema.ts +138 -0
  44. package/apps/web/static/config.json +28 -0
  45. package/apps/web/static/config.prod.json +28 -0
  46. package/apps/web/static/config.staging.json +28 -0
  47. package/apps/web/tests/build-env.test.ts +63 -0
  48. package/apps/web/tests/build-smoke.test.ts +103 -0
  49. package/apps/web/tests/config-schema.test.ts +432 -0
  50. package/apps/web/tests/workspace.test.ts +12 -0
  51. package/apps/web/tsconfig.json +10 -0
  52. package/apps/web/upstream/.browserslistrc +3 -0
  53. package/apps/web/upstream/.editorconfig +9 -0
  54. package/apps/web/upstream/.eslintignore +3 -0
  55. package/apps/web/upstream/.eslintrc.js +90 -0
  56. package/apps/web/upstream/.github/FUNDING.yml +12 -0
  57. package/apps/web/upstream/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  58. package/apps/web/upstream/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  59. package/apps/web/upstream/.github/workflows/push.yml +26 -0
  60. package/apps/web/upstream/.github/workflows/release.yml +33 -0
  61. package/apps/web/upstream/.github/workflows/release_manual.yml +55 -0
  62. package/apps/web/upstream/.prettierrc.js +9 -0
  63. package/apps/web/upstream/.stylelintignore +5 -0
  64. package/apps/web/upstream/.stylelintrc.js +21 -0
  65. package/apps/web/upstream/LICENSE +201 -0
  66. package/apps/web/upstream/README.md +76 -0
  67. package/apps/web/upstream/babel.config.js +25 -0
  68. package/apps/web/upstream/crowdin.yml +3 -0
  69. package/apps/web/upstream/index.html +35 -0
  70. package/apps/web/upstream/jest.config.js +7 -0
  71. package/apps/web/upstream/jsdoc.json +22 -0
  72. package/apps/web/upstream/package.json +80 -0
  73. package/apps/web/upstream/scripts/pre-commit +43 -0
  74. package/apps/web/upstream/src/components/App.vue +516 -0
  75. package/apps/web/upstream/src/components/AppSettings.vue +587 -0
  76. package/apps/web/upstream/src/components/AutoComplete.vue +284 -0
  77. package/apps/web/upstream/src/components/Avatar.vue +105 -0
  78. package/apps/web/upstream/src/components/AwayStatusIndicator.vue +77 -0
  79. package/apps/web/upstream/src/components/BufferKey.vue +121 -0
  80. package/apps/web/upstream/src/components/BufferSettings.vue +97 -0
  81. package/apps/web/upstream/src/components/Captcha.vue +85 -0
  82. package/apps/web/upstream/src/components/ChannelBanlist.vue +96 -0
  83. package/apps/web/upstream/src/components/ChannelInfo.vue +159 -0
  84. package/apps/web/upstream/src/components/ChannelInvitelist.vue +276 -0
  85. package/apps/web/upstream/src/components/ChannelList.vue +345 -0
  86. package/apps/web/upstream/src/components/Container.vue +401 -0
  87. package/apps/web/upstream/src/components/ContainerHeader.vue +522 -0
  88. package/apps/web/upstream/src/components/ControlInput.vue +1016 -0
  89. package/apps/web/upstream/src/components/LoadingAnimation.vue +104 -0
  90. package/apps/web/upstream/src/components/MediaViewer.vue +199 -0
  91. package/apps/web/upstream/src/components/MessageInfo.vue +192 -0
  92. package/apps/web/upstream/src/components/MessageList.vue +1028 -0
  93. package/apps/web/upstream/src/components/MessageListAvatar.vue +20 -0
  94. package/apps/web/upstream/src/components/MessageListMessageCompact.vue +361 -0
  95. package/apps/web/upstream/src/components/MessageListMessageInline.vue +257 -0
  96. package/apps/web/upstream/src/components/MessageListMessageModern.vue +430 -0
  97. package/apps/web/upstream/src/components/NetworkSettings.vue +528 -0
  98. package/apps/web/upstream/src/components/Nicklist.vue +309 -0
  99. package/apps/web/upstream/src/components/NicklistUser.vue +181 -0
  100. package/apps/web/upstream/src/components/NotConnected.vue +271 -0
  101. package/apps/web/upstream/src/components/SelfUser.vue +283 -0
  102. package/apps/web/upstream/src/components/ServerSelector.vue +162 -0
  103. package/apps/web/upstream/src/components/ServerView.vue +126 -0
  104. package/apps/web/upstream/src/components/SettingsAdvanced.vue +232 -0
  105. package/apps/web/upstream/src/components/SettingsAliases.vue +121 -0
  106. package/apps/web/upstream/src/components/Sidebar.vue +429 -0
  107. package/apps/web/upstream/src/components/SidebarAboutBuffer.vue +288 -0
  108. package/apps/web/upstream/src/components/SidebarState.vue +145 -0
  109. package/apps/web/upstream/src/components/StartupError.vue +35 -0
  110. package/apps/web/upstream/src/components/StateBrowser.vue +420 -0
  111. package/apps/web/upstream/src/components/StateBrowserBuffer.vue +124 -0
  112. package/apps/web/upstream/src/components/StateBrowserNetwork.vue +823 -0
  113. package/apps/web/upstream/src/components/StateBrowserUsermenu.vue +162 -0
  114. package/apps/web/upstream/src/components/TypingStatusIndicator.vue +34 -0
  115. package/apps/web/upstream/src/components/TypingUsersList.vue +56 -0
  116. package/apps/web/upstream/src/components/UrlEmbed.vue +149 -0
  117. package/apps/web/upstream/src/components/UserBox.vue +728 -0
  118. package/apps/web/upstream/src/components/inputtools/Emoji.vue +67 -0
  119. package/apps/web/upstream/src/components/inputtools/TextStyle.vue +142 -0
  120. package/apps/web/upstream/src/components/startups/CommonLayout.vue +208 -0
  121. package/apps/web/upstream/src/components/startups/CustomServer.vue +495 -0
  122. package/apps/web/upstream/src/components/startups/KiwiBnc.vue +361 -0
  123. package/apps/web/upstream/src/components/startups/Personal.vue +317 -0
  124. package/apps/web/upstream/src/components/startups/Welcome.vue +544 -0
  125. package/apps/web/upstream/src/components/startups/ZncLogin.vue +229 -0
  126. package/apps/web/upstream/src/components/utils/InputConfirm.vue +68 -0
  127. package/apps/web/upstream/src/components/utils/InputPrompt.vue +118 -0
  128. package/apps/web/upstream/src/components/utils/InputText.vue +172 -0
  129. package/apps/web/upstream/src/components/utils/IrcInput.vue +579 -0
  130. package/apps/web/upstream/src/components/utils/PluginWrapper.vue +26 -0
  131. package/apps/web/upstream/src/components/utils/TabbedView.vue +149 -0
  132. package/apps/web/upstream/src/components/utils/TransitionExpand.vue +97 -0
  133. package/apps/web/upstream/src/helpers/Colours.js +128 -0
  134. package/apps/web/upstream/src/helpers/IrcdDiffs.js +26 -0
  135. package/apps/web/upstream/src/helpers/Md5.js +193 -0
  136. package/apps/web/upstream/src/helpers/Misc.js +401 -0
  137. package/apps/web/upstream/src/helpers/TextFormatting.js +237 -0
  138. package/apps/web/upstream/src/libs/AliasRewriter.js +157 -0
  139. package/apps/web/upstream/src/libs/AudioManager.js +60 -0
  140. package/apps/web/upstream/src/libs/BouncerMiddleware.js +247 -0
  141. package/apps/web/upstream/src/libs/BouncerProvider.js +551 -0
  142. package/apps/web/upstream/src/libs/ChathistoryMiddleware.js +153 -0
  143. package/apps/web/upstream/src/libs/ConfigLoader.js +94 -0
  144. package/apps/web/upstream/src/libs/EmojiProvider.js +46 -0
  145. package/apps/web/upstream/src/libs/GlobalApi.js +346 -0
  146. package/apps/web/upstream/src/libs/IPC.js +51 -0
  147. package/apps/web/upstream/src/libs/InputHandler.js +921 -0
  148. package/apps/web/upstream/src/libs/IrcClient.js +1506 -0
  149. package/apps/web/upstream/src/libs/Logger.js +71 -0
  150. package/apps/web/upstream/src/libs/Message.js +169 -0
  151. package/apps/web/upstream/src/libs/MessageFormatter.js +364 -0
  152. package/apps/web/upstream/src/libs/MessageParser.js +249 -0
  153. package/apps/web/upstream/src/libs/Notifications.js +78 -0
  154. package/apps/web/upstream/src/libs/ServerConnection.js +270 -0
  155. package/apps/web/upstream/src/libs/ServerSession.js +102 -0
  156. package/apps/web/upstream/src/libs/SoundBleep.js +20 -0
  157. package/apps/web/upstream/src/libs/StatePersistence.js +90 -0
  158. package/apps/web/upstream/src/libs/ThemeManager.js +128 -0
  159. package/apps/web/upstream/src/libs/TypingMiddleware.js +105 -0
  160. package/apps/web/upstream/src/libs/WindowTitle.js +63 -0
  161. package/apps/web/upstream/src/libs/batchedAdd.js +74 -0
  162. package/apps/web/upstream/src/libs/bufferTools.js +177 -0
  163. package/apps/web/upstream/src/libs/polyfill/Element.closest.js +18 -0
  164. package/apps/web/upstream/src/libs/renderers/Html.js +113 -0
  165. package/apps/web/upstream/src/libs/settingTools.js +31 -0
  166. package/apps/web/upstream/src/libs/state/BufferState.js +703 -0
  167. package/apps/web/upstream/src/libs/state/NetworkState.js +158 -0
  168. package/apps/web/upstream/src/libs/state/UserState.js +89 -0
  169. package/apps/web/upstream/src/libs/state/common.js +26 -0
  170. package/apps/web/upstream/src/libs/state.js +961 -0
  171. package/apps/web/upstream/src/libs/storage/Local.js +51 -0
  172. package/apps/web/upstream/src/main.js +566 -0
  173. package/apps/web/upstream/src/res/autocompleteCommands.js +31 -0
  174. package/apps/web/upstream/src/res/configTemplates.js +373 -0
  175. package/apps/web/upstream/src/res/globalStyle.css +277 -0
  176. package/apps/web/upstream/src/res/kiwiLoadingLogo.png +0 -0
  177. package/apps/web/upstream/src/res/locales/app.af-ZA.po +1145 -0
  178. package/apps/web/upstream/src/res/locales/app.ar-SA.po +1145 -0
  179. package/apps/web/upstream/src/res/locales/app.bg-BG.po +1145 -0
  180. package/apps/web/upstream/src/res/locales/app.bs-BA.po +1145 -0
  181. package/apps/web/upstream/src/res/locales/app.ca-ES.po +1145 -0
  182. package/apps/web/upstream/src/res/locales/app.cs-CZ.po +1145 -0
  183. package/apps/web/upstream/src/res/locales/app.da-DK.po +1145 -0
  184. package/apps/web/upstream/src/res/locales/app.de-DE.po +1145 -0
  185. package/apps/web/upstream/src/res/locales/app.dev.po +1246 -0
  186. package/apps/web/upstream/src/res/locales/app.el-GR.po +1145 -0
  187. package/apps/web/upstream/src/res/locales/app.en-US.po +1145 -0
  188. package/apps/web/upstream/src/res/locales/app.es-419.po +1145 -0
  189. package/apps/web/upstream/src/res/locales/app.es-AR.po +1145 -0
  190. package/apps/web/upstream/src/res/locales/app.es-EM.po +536 -0
  191. package/apps/web/upstream/src/res/locales/app.es-ES.po +1145 -0
  192. package/apps/web/upstream/src/res/locales/app.es-US.po +1145 -0
  193. package/apps/web/upstream/src/res/locales/app.eu-ES.po +1145 -0
  194. package/apps/web/upstream/src/res/locales/app.fi-FI.po +1145 -0
  195. package/apps/web/upstream/src/res/locales/app.fr-FR.po +1145 -0
  196. package/apps/web/upstream/src/res/locales/app.gl-ES.po +1145 -0
  197. package/apps/web/upstream/src/res/locales/app.he-IL.po +1145 -0
  198. package/apps/web/upstream/src/res/locales/app.hi-IN.po +1145 -0
  199. package/apps/web/upstream/src/res/locales/app.hu-HU.po +1145 -0
  200. package/apps/web/upstream/src/res/locales/app.id-ID.po +1145 -0
  201. package/apps/web/upstream/src/res/locales/app.it-IT.po +1145 -0
  202. package/apps/web/upstream/src/res/locales/app.ja-JP.po +1145 -0
  203. package/apps/web/upstream/src/res/locales/app.ko-KR.po +1145 -0
  204. package/apps/web/upstream/src/res/locales/app.nl-NL.po +1145 -0
  205. package/apps/web/upstream/src/res/locales/app.no-NO.po +1145 -0
  206. package/apps/web/upstream/src/res/locales/app.pl-PL.po +1145 -0
  207. package/apps/web/upstream/src/res/locales/app.pt-BR.po +1145 -0
  208. package/apps/web/upstream/src/res/locales/app.pt-PT.po +1145 -0
  209. package/apps/web/upstream/src/res/locales/app.ro-RO.po +1145 -0
  210. package/apps/web/upstream/src/res/locales/app.ru-RU.po +1145 -0
  211. package/apps/web/upstream/src/res/locales/app.sl-SI.po +1145 -0
  212. package/apps/web/upstream/src/res/locales/app.sq-AL.po +1145 -0
  213. package/apps/web/upstream/src/res/locales/app.sr-SP.po +1145 -0
  214. package/apps/web/upstream/src/res/locales/app.sv-SE.po +1145 -0
  215. package/apps/web/upstream/src/res/locales/app.tr-TR.po +1145 -0
  216. package/apps/web/upstream/src/res/locales/app.uk-UA.po +1145 -0
  217. package/apps/web/upstream/src/res/locales/app.vi-VN.po +1145 -0
  218. package/apps/web/upstream/src/res/locales/app.zh-CN.po +1145 -0
  219. package/apps/web/upstream/src/res/locales/app.zh-TW.po +1145 -0
  220. package/apps/web/upstream/src/res/localesList.json +27 -0
  221. package/apps/web/upstream/src/res/logo.png +0 -0
  222. package/apps/web/upstream/src/thirdparty/about.html +43 -0
  223. package/apps/web/upstream/src/thirdparty/index.js +5 -0
  224. package/apps/web/upstream/src/thirdparty/kiwiirccom.vue +185 -0
  225. package/apps/web/upstream/static/config.json +28 -0
  226. package/apps/web/upstream/static/emoticons/smile.png +0 -0
  227. package/apps/web/upstream/static/favicon.png +0 -0
  228. package/apps/web/upstream/static/highlight.mp3 +0 -0
  229. package/apps/web/upstream/static/highlight.ogg +0 -0
  230. package/apps/web/upstream/static/locales/.gitignore +2 -0
  231. package/apps/web/upstream/static/plugins/customise.html.example +10 -0
  232. package/apps/web/upstream/static/themes/coffee/theme.css +145 -0
  233. package/apps/web/upstream/static/themes/common/base.css +1064 -0
  234. package/apps/web/upstream/static/themes/dark/theme.css +196 -0
  235. package/apps/web/upstream/static/themes/default/theme.css +92 -0
  236. package/apps/web/upstream/static/themes/elite/theme.css +248 -0
  237. package/apps/web/upstream/static/themes/grayfox/theme.css +143 -0
  238. package/apps/web/upstream/static/themes/nightswatch/theme.css +367 -0
  239. package/apps/web/upstream/static/themes/osprey/theme.css +115 -0
  240. package/apps/web/upstream/static/themes/radioactive/theme.css +1211 -0
  241. package/apps/web/upstream/static/themes/sky/theme.css +107 -0
  242. package/apps/web/upstream/tests/unit/BatchAdd.spec.js +174 -0
  243. package/apps/web/upstream/tests/unit/MessageParser.spec.js +125 -0
  244. package/apps/web/upstream/tests/unit/Misc.spec.js +24 -0
  245. package/apps/web/upstream/tests/unit/NetworkState.spec.js +58 -0
  246. package/apps/web/upstream/tests/unit/StartupError.spec.js +20 -0
  247. package/apps/web/upstream/vue.config.js +150 -0
  248. package/apps/web/upstream/yarn.lock +9767 -0
  249. package/apps/web/vitest.config.ts +13 -0
  250. package/biome.json +2 -0
  251. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  252. package/docs/ADR-002-location-of-authority.md +82 -0
  253. package/docs/ADR-003-durable-object-sharding.md +110 -0
  254. package/docs/ADR-004-dynamodb-schema.md +96 -0
  255. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  256. package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
  257. package/docs/ADR-007-deterministic-ports.md +82 -0
  258. package/docs/ADR-008-monorepo-tooling.md +60 -0
  259. package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
  260. package/docs/ADR-Index.md +15 -0
  261. package/docs/AWS-Adapter-Architecture.md +496 -0
  262. package/docs/AWS-Deployment.md +1275 -0
  263. package/docs/AWS-TCP-Deployment.md +410 -0
  264. package/docs/Cloudflare-Deployment-Guide.md +719 -0
  265. package/docs/Cloudflare-TCP-Deployment.md +510 -0
  266. package/docs/Home.md +16 -0
  267. package/docs/Observability.md +87 -0
  268. package/docs/PlanExtensions.md +812 -0
  269. package/docs/PlanIRCv3Websocket.md +503 -0
  270. package/docs/PlanWebClient.md +451 -0
  271. package/docs/Release-Process.md +443 -0
  272. package/docs/WebClientGuide.md +535 -0
  273. package/package.json +2 -2
  274. package/packages/aws-adapter/package.json +1 -1
  275. package/packages/aws-adapter/src/handlers/default.ts +3 -0
  276. package/packages/aws-adapter/src/handlers/nlb-stream.ts +9 -0
  277. package/packages/aws-adapter/src/serialize.ts +29 -1
  278. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
  279. package/packages/aws-adapter/tests/account-store.test.ts +4 -7
  280. package/packages/aws-adapter/tests/aws-harness.ts +5 -2
  281. package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
  282. package/packages/aws-adapter/tests/aws-runtime.test.ts +79 -0
  283. package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
  284. package/packages/aws-adapter/tests/connect.test.ts +9 -9
  285. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
  286. package/packages/aws-adapter/tests/global-setup.ts +55 -30
  287. package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
  288. package/packages/aws-adapter/tests/handlers.test.ts +90 -4
  289. package/packages/aws-adapter/tests/message-store.test.ts +6 -5
  290. package/packages/aws-adapter/tests/nlb-stream.test.ts +60 -8
  291. package/packages/cf-adapter/package.json +2 -1
  292. package/packages/cf-adapter/src/connection-do.ts +3 -0
  293. package/packages/cf-adapter/src/serialize.ts +25 -4
  294. package/packages/cf-adapter/tests/cf-harness.ts +12 -1
  295. package/packages/cf-adapter/tests/cf-runtime.test.ts +269 -0
  296. package/packages/cf-adapter/tests/channel-do.test.ts +117 -0
  297. package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
  298. package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
  299. package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
  300. package/packages/cf-adapter/tests/registry-do.test.ts +104 -0
  301. package/packages/cf-adapter/tests/serialize.test.ts +308 -0
  302. package/packages/cf-adapter/vitest.config.ts +1 -1
  303. package/packages/in-memory-runtime/package.json +1 -1
  304. package/packages/irc-core/package.json +1 -1
  305. package/packages/irc-core/reports/mutation/mutation.html +342 -0
  306. package/packages/irc-core/src/caps/capabilities.ts +37 -3
  307. package/packages/irc-core/src/caps/index.ts +1 -0
  308. package/packages/irc-core/src/caps/sts.ts +84 -0
  309. package/packages/irc-core/src/commands/account-notify.ts +53 -0
  310. package/packages/irc-core/src/commands/away.ts +9 -3
  311. package/packages/irc-core/src/commands/cap.ts +23 -5
  312. package/packages/irc-core/src/commands/chathistory.ts +40 -11
  313. package/packages/irc-core/src/commands/index.ts +34 -0
  314. package/packages/irc-core/src/commands/isupport.ts +53 -0
  315. package/packages/irc-core/src/commands/mode.ts +8 -1
  316. package/packages/irc-core/src/commands/monitor.ts +327 -0
  317. package/packages/irc-core/src/commands/multiline.ts +256 -0
  318. package/packages/irc-core/src/commands/pre-away.ts +112 -0
  319. package/packages/irc-core/src/commands/privmsg.ts +19 -12
  320. package/packages/irc-core/src/commands/read-marker.ts +108 -0
  321. package/packages/irc-core/src/commands/registration.ts +8 -0
  322. package/packages/irc-core/src/commands/sasl.ts +24 -1
  323. package/packages/irc-core/src/commands/tagmsg.ts +71 -1
  324. package/packages/irc-core/src/commands/whois.ts +5 -0
  325. package/packages/irc-core/src/config.ts +65 -10
  326. package/packages/irc-core/src/effects.ts +14 -0
  327. package/packages/irc-core/src/ports.ts +389 -0
  328. package/packages/irc-core/src/protocol/index.ts +14 -0
  329. package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
  330. package/packages/irc-core/src/protocol/numerics.ts +15 -0
  331. package/packages/irc-core/src/protocol/outbound.ts +16 -1
  332. package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
  333. package/packages/irc-core/src/state/connection.ts +32 -1
  334. package/packages/irc-core/src/types.ts +112 -0
  335. package/packages/irc-core/stryker.commands.conf.json +1 -2
  336. package/packages/irc-core/tests/away-store.test.ts +73 -0
  337. package/packages/irc-core/tests/batch.test.ts +15 -0
  338. package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
  339. package/packages/irc-core/tests/caps/sts.test.ts +123 -0
  340. package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
  341. package/packages/irc-core/tests/commands/away.test.ts +85 -2
  342. package/packages/irc-core/tests/commands/cap.test.ts +165 -0
  343. package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
  344. package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
  345. package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
  346. package/packages/irc-core/tests/commands/mode.test.ts +72 -0
  347. package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
  348. package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
  349. package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
  350. package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
  351. package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
  352. package/packages/irc-core/tests/commands/registration.test.ts +64 -0
  353. package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
  354. package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
  355. package/packages/irc-core/tests/commands/whois.test.ts +34 -0
  356. package/packages/irc-core/tests/config.test.ts +103 -13
  357. package/packages/irc-core/tests/labeled-response.test.ts +181 -0
  358. package/packages/irc-core/tests/message-tags.test.ts +114 -0
  359. package/packages/irc-core/tests/monitor-store.test.ts +200 -0
  360. package/packages/irc-core/tests/numerics.test.ts +13 -0
  361. package/packages/irc-core/tests/parser.test.ts +109 -0
  362. package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
  363. package/packages/irc-core/tests/standard-replies.test.ts +412 -0
  364. package/packages/irc-core/tests/types.test.ts +27 -0
  365. package/packages/irc-server/package.json +1 -1
  366. package/packages/irc-server/src/actor.ts +450 -3
  367. package/packages/irc-server/src/dispatch.ts +108 -16
  368. package/packages/irc-server/tests/actor.test.ts +1432 -39
  369. package/packages/irc-server/tests/dispatch.test.ts +263 -2
  370. package/packages/irc-test-support/package.json +1 -1
  371. package/packages/irc-test-support/src/index.ts +6 -0
  372. package/packages/irc-test-support/src/scenarios.ts +24 -2
  373. package/packages/irc-test-support/src/test-config.ts +54 -0
  374. package/packages/irc-test-support/tests/test-config.test.ts +51 -0
  375. package/tools/ci-hardening/package.json +1 -1
  376. package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
  377. package/tools/load-test/package.json +33 -0
  378. package/tools/load-test/src/client.ts +351 -0
  379. package/tools/load-test/src/config.ts +313 -0
  380. package/tools/load-test/src/harness.ts +116 -0
  381. package/tools/load-test/src/main.ts +120 -0
  382. package/tools/load-test/src/metrics.ts +168 -0
  383. package/tools/load-test/src/report.ts +106 -0
  384. package/tools/load-test/tests/client.test.ts +212 -0
  385. package/tools/load-test/tests/config.test.ts +152 -0
  386. package/tools/load-test/tests/framing.test.ts +37 -0
  387. package/tools/load-test/tests/harness.test.ts +165 -0
  388. package/tools/load-test/tests/metrics.test.ts +174 -0
  389. package/tools/load-test/tests/report.test.ts +161 -0
  390. package/tools/load-test/tests/smoke.test.ts +67 -0
  391. package/tools/load-test/tsconfig.build.json +12 -0
  392. package/tools/load-test/tsconfig.test.json +10 -0
  393. package/tools/load-test/vitest.config.ts +29 -0
  394. package/tools/tcp-ws-forwarder/package.json +1 -1
@@ -0,0 +1,921 @@
1
+ 'kiwi public';
2
+
3
+ import _ from 'lodash';
4
+
5
+ import * as TextFormatting from '@/helpers/TextFormatting';
6
+ import * as Misc from '@/helpers/Misc';
7
+ import AliasRewriter from './AliasRewriter';
8
+
9
+ // Map of commandName=commandHandlerFn
10
+ const inputCommands = {};
11
+
12
+ export default class InputHandler {
13
+ constructor(state) {
14
+ this.state = state;
15
+ this.aliasRewriter = new AliasRewriter();
16
+
17
+ // Load the aliases from general settings
18
+ this.aliasRewriter.importFromString(state.setting('aliases'));
19
+
20
+ // Only watch the user setting changes in order to reload them
21
+ state.$watch('user_settings.aliases', (newVal) => {
22
+ this.aliasRewriter.importFromString(state.setting('aliases'));
23
+ });
24
+
25
+ this.listenForInput();
26
+ }
27
+
28
+ defaultContext() {
29
+ return {
30
+ network: this.state.getActiveNetwork(),
31
+ buffer: this.state.getActiveBuffer(),
32
+ };
33
+ }
34
+
35
+ validateContext(context) {
36
+ if (
37
+ typeof context !== 'object' ||
38
+ !Object.prototype.hasOwnProperty.call(context, 'buffer') ||
39
+ !Object.prototype.hasOwnProperty.call(context, 'network') ||
40
+ typeof context.buffer !== 'object' ||
41
+ typeof context.network !== 'object'
42
+ ) {
43
+ throw new TypeError('context must contain both network and buffer properties');
44
+ }
45
+ }
46
+
47
+ listenForInput() {
48
+ this.state.$on('input.raw', (input, context = this.defaultContext()) => {
49
+ let lines = input.split('\n');
50
+ lines.forEach((line) => this.processLine(line, context));
51
+ });
52
+ }
53
+
54
+ processLine(rawLine, context = this.defaultContext()) {
55
+ this.validateContext(context);
56
+ const { network, buffer } = context;
57
+ let line = rawLine;
58
+ let stylesStrippedLine = Misc.stripStyles(line);
59
+
60
+ // If no command specified, server buffers = send raw, channels/queries = send message
61
+ let escapedCommand = stylesStrippedLine.substr(0, 2) === '//';
62
+ if (stylesStrippedLine[0] !== '/' || escapedCommand) {
63
+ if (escapedCommand) {
64
+ line = line.substr(1);
65
+ }
66
+
67
+ if (buffer.isServer()) {
68
+ line = '/quote ' + line;
69
+ } else {
70
+ line = '/msg ' + buffer.name + ' ' + line;
71
+ }
72
+ } else if (stylesStrippedLine[0] === '/' && line[0] !== '/') {
73
+ // If attempting to send a /command but it has a colour code in front, use the
74
+ // style stripped version of the line
75
+ line = stylesStrippedLine;
76
+ }
77
+
78
+ let aliasVars = {
79
+ network: network.name,
80
+ server: network.connection.server,
81
+ channel: network.isChannelName(buffer.name) ? buffer.name : '',
82
+ query: network.isChannelName(buffer.name) ? '' : buffer.name,
83
+ destination: buffer.name,
84
+ nick: network.nick,
85
+ };
86
+ line = this.aliasRewriter.process(line, aliasVars);
87
+
88
+ // An alias rewrite may have cancelled out the input
89
+ if (!line) {
90
+ return;
91
+ }
92
+
93
+ // Remove the / from the start of the line
94
+ line = line.substr(1);
95
+
96
+ let spaceIdx = line.indexOf(' ');
97
+ if (spaceIdx === -1) spaceIdx = line.length;
98
+
99
+ let command = line.substr(0, spaceIdx);
100
+ let params = line.substr(spaceIdx + 1);
101
+
102
+ let eventObj = {
103
+ handled: false,
104
+ raw: rawLine,
105
+ command: command,
106
+ params: params,
107
+ };
108
+
109
+ // Plugins may tap into this event to handle a command themselves
110
+ this.state.$emit('input.command.' + command, eventObj, command, params);
111
+ if (eventObj.handled) {
112
+ return;
113
+ }
114
+
115
+ if (inputCommands[command.toLowerCase()]) {
116
+ inputCommands[command.toLowerCase()].call(this, eventObj, command, params);
117
+ }
118
+
119
+ if (!eventObj.handled) {
120
+ network.ircClient.raw(line);
121
+ }
122
+ }
123
+ }
124
+
125
+ /**
126
+ * The actual handler functions for commands. Called in context of the InputHandler instance
127
+ * inputCommand['the /command name'] = function(){};
128
+ */
129
+
130
+ // /lines allows aliases to send multiple commands, separated by |
131
+ inputCommands.lines = function inputCommandLines(event, command, line) {
132
+ event.handled = true;
133
+
134
+ line.split('|').forEach((subLine) => {
135
+ this.processLine(subLine.trim());
136
+ });
137
+ };
138
+
139
+ function handleMessage(type, event, command, line) {
140
+ event.handled = true;
141
+
142
+ let network = this.state.getActiveNetwork();
143
+
144
+ let spaceIdx = line.indexOf(' ');
145
+ if (spaceIdx === -1) spaceIdx = line.length;
146
+
147
+ let bufferName = line.substr(0, spaceIdx);
148
+ let message = line.substr(spaceIdx + 1);
149
+
150
+ // Only trim whitespace from the end if we have existing content. Some people enjoy sending
151
+ // pure whitespace messages which we don't want to interfere with
152
+ if (message.replace(/\s+/g, '') !== '') {
153
+ message = message.trimEnd();
154
+ } else {
155
+ // Windows uses \r\n for new lines as we split on \n trim \r from the end
156
+ message = message.replace(/[\r]+$/, '');
157
+ }
158
+
159
+ // Make sure we have some text to actually send
160
+ if (!message) {
161
+ return;
162
+ }
163
+
164
+ let localBuffer = bufferName;
165
+ let extractedTarget = network.ircClient.network.extractTargetGroup(bufferName);
166
+ if (extractedTarget) {
167
+ localBuffer = extractedTarget.target;
168
+ }
169
+
170
+ let buffer = localBuffer.length && this.state.getOrAddBufferByName(network.id, localBuffer);
171
+ if (buffer) {
172
+ let textFormatType = 'privmsg';
173
+ if (type === 'action') {
174
+ textFormatType = 'action';
175
+ } else if (type === 'notice') {
176
+ textFormatType = 'notice';
177
+ }
178
+
179
+ let messageBody = TextFormatting.formatText(textFormatType, {
180
+ nick: network.nick,
181
+ text: message,
182
+ });
183
+
184
+ let newMessage = {
185
+ time: Date.now(),
186
+ nick: network.nick,
187
+ message: messageBody,
188
+ type: type,
189
+ };
190
+
191
+ this.state.addMessage(buffer, newMessage);
192
+ }
193
+
194
+ let fnNames = {
195
+ privmsg: 'say',
196
+ action: 'action',
197
+ notice: 'notice',
198
+ };
199
+ let fnName = fnNames[type] || 'say';
200
+ network.ircClient[fnName](bufferName, message);
201
+ }
202
+
203
+ inputCommands.msg = function inputCommandMsg(event, command, line) {
204
+ handleMessage.call(this, 'privmsg', event, command, line);
205
+ };
206
+ inputCommands.action = function inputCommandMsg(event, command, line) {
207
+ handleMessage.call(this, 'action', event, command, line);
208
+ };
209
+ inputCommands.notice = function inputCommandMsg(event, command, line) {
210
+ handleMessage.call(this, 'notice', event, command, line);
211
+ };
212
+ inputCommands.dice = function inputCommandDice(event, command, line) {
213
+ // /dice 100
214
+
215
+ event.handled = true;
216
+ let buffer = this.state.getActiveBuffer();
217
+ let network = this.state.getActiveNetwork();
218
+
219
+ let sides = line.replace(/\D/g, '');
220
+ sides = parseInt(sides || '0', 10);
221
+ if (sides <= 0) {
222
+ sides = 6;
223
+ }
224
+ let rndNumber = Math.floor(Math.random() * sides) + 1;
225
+
226
+ let msg = TextFormatting.t('dice_roll', {
227
+ sides: TextFormatting.formatNumber(sides),
228
+ number: TextFormatting.formatNumber(rndNumber),
229
+ });
230
+ network.ircClient.action(buffer.name, msg);
231
+ this.state.addMessage(buffer, {
232
+ nick: network.nick,
233
+ message: msg,
234
+ type: 'action',
235
+ });
236
+ };
237
+
238
+ inputCommands.ctcp = function inputCommandCtcp(event, command, line) {
239
+ event.handled = true;
240
+
241
+ let params = line.split(' ');
242
+ let target = params.shift();
243
+ let ctcpType = params.shift();
244
+
245
+ if (!ctcpType || !target) {
246
+ return;
247
+ }
248
+
249
+ let network = this.state.getActiveNetwork();
250
+ network.ircClient.ctcpRequest(...[target, ctcpType].concat(params));
251
+ };
252
+
253
+ inputCommands.join = function inputCommandJoin(event, command, line) {
254
+ event.handled = true;
255
+
256
+ let network = this.state.getActiveNetwork();
257
+ let bufferObjs = Misc.extractBuffers(line);
258
+
259
+ // handle join without any buffers specified
260
+ if (bufferObjs.length === 0) {
261
+ let buffer = this.state.getActiveBuffer();
262
+
263
+ // join the active channel if its not joined
264
+ if (buffer.isChannel() && !buffer.joined) {
265
+ network.ircClient.join(buffer.name, buffer.key);
266
+ return;
267
+ }
268
+
269
+ // report an error if the user tries to join without specifying the channel
270
+ this.state.addMessage(buffer, {
271
+ nick: '*',
272
+ message: TextFormatting.t('error_no_channel_join'),
273
+ type: 'error',
274
+ });
275
+ return;
276
+ }
277
+
278
+ // Only switch to the first channel we join if multiple are being joined
279
+ let hasSwitchedActiveBuffer = false;
280
+ bufferObjs.forEach((bufferObj, idx) => {
281
+ // /join 0 parts all channels and is only ever used to troll IRC newbies.
282
+ // Just disable it entirely.
283
+ if (bufferObj.name === '0') {
284
+ return;
285
+ }
286
+
287
+ // Prepend a # channel prefix if not specified already
288
+ let chanName = network.isChannelName(bufferObj.name) ?
289
+ bufferObj.name :
290
+ '#' + bufferObj.name;
291
+
292
+ let newBuffer = this.state.addBuffer(network.id, chanName);
293
+
294
+ if (newBuffer && !hasSwitchedActiveBuffer) {
295
+ this.state.setActiveBuffer(network.id, newBuffer.name);
296
+ hasSwitchedActiveBuffer = true;
297
+ }
298
+
299
+ if (bufferObj.key) {
300
+ newBuffer.key = bufferObj.key;
301
+ }
302
+
303
+ network.ircClient.join(chanName, bufferObj.key);
304
+ });
305
+ };
306
+
307
+ inputCommands.part = function inputCommandPart(event, command, line) {
308
+ event.handled = true;
309
+
310
+ let network = this.state.getActiveNetwork();
311
+ let bufferNames = [];
312
+ let message = '';
313
+
314
+ if (line === '') {
315
+ // /part
316
+ bufferNames = [this.state.getActiveBuffer().name];
317
+ } else {
318
+ let lineParts = line.split(' ');
319
+ if (network.isChannelName(lineParts[0])) {
320
+ // /part #channel,#possible_channel possible part message
321
+ bufferNames = _.compact(lineParts[0].split(','));
322
+ message = lineParts.slice(1).join(' ');
323
+ } else {
324
+ // /part possible part message
325
+ bufferNames = [this.state.getActiveBuffer().name];
326
+ message = line;
327
+ }
328
+ }
329
+
330
+ bufferNames.forEach((bufferName) => {
331
+ network.ircClient.part(bufferName, message);
332
+ });
333
+ };
334
+
335
+ inputCommands.quit = function inputCommandQuit(event, command, line) {
336
+ event.handled = true;
337
+
338
+ let network = this.state.getActiveNetwork();
339
+ network.ircClient.quit(line);
340
+ };
341
+
342
+ inputCommands.topic = function inputCommandTopic(event, command, line) {
343
+ event.handled = true;
344
+
345
+ let network = this.state.getActiveNetwork();
346
+ let bufferName = '';
347
+ let newTopic = '';
348
+
349
+ if (line === '') {
350
+ // /topic
351
+ return;
352
+ }
353
+
354
+ let lineParts = line.split(' ');
355
+ if (network.isChannelName(lineParts[0])) {
356
+ // /topic #channel a topic
357
+ bufferName = lineParts[0];
358
+ newTopic = lineParts.slice(1).join(' ');
359
+ } else {
360
+ // /topic a topic
361
+ bufferName = this.state.getActiveBuffer().name;
362
+ newTopic = line;
363
+ }
364
+
365
+ network.ircClient.setTopic(bufferName, newTopic);
366
+ };
367
+
368
+ inputCommands.kick = function inputCommandKick(event, command, line) {
369
+ event.handled = true;
370
+
371
+ let network = this.state.getActiveNetwork();
372
+ let toKick = '';
373
+ let bufferName = '';
374
+ let kickReason = '';
375
+
376
+ if (line === '') {
377
+ // No params given
378
+ return;
379
+ }
380
+
381
+ let lineParts = line.split(' ');
382
+
383
+ if (network.isChannelName(lineParts[0])) {
384
+ bufferName = lineParts.shift();
385
+ }
386
+
387
+ toKick = lineParts.shift();
388
+ kickReason = lineParts.join(' ');
389
+
390
+ if (!bufferName) {
391
+ bufferName = this.state.getActiveBuffer().name;
392
+ }
393
+ if (!toKick) {
394
+ return;
395
+ }
396
+
397
+ network.ircClient.raw('KICK', bufferName, toKick, kickReason);
398
+ };
399
+
400
+ inputCommands.ignore = function inputCommandIgnore(event, command, line) {
401
+ event.handled = true;
402
+
403
+ let network = this.state.getActiveNetwork();
404
+ let toIgnore = line.split(' ').shift();
405
+
406
+ if (!toIgnore) {
407
+ return;
408
+ }
409
+
410
+ let user = this.state.getUser(network.id, toIgnore);
411
+ if (user) {
412
+ user.ignore = true;
413
+ let buffer = this.state.getActiveBuffer();
414
+ this.state.addMessage(buffer, {
415
+ nick: '*',
416
+ message: 'Ignoring ' + user.nick,
417
+ type: 'message',
418
+ });
419
+ }
420
+ };
421
+
422
+ inputCommands.unignore = function inputCommandUnignore(event, command, line) {
423
+ event.handled = true;
424
+
425
+ let network = this.state.getActiveNetwork();
426
+ let toUnignore = line.split(' ').shift();
427
+
428
+ if (!toUnignore) {
429
+ return;
430
+ }
431
+
432
+ let user = this.state.getUser(network.id, toUnignore);
433
+ if (user) {
434
+ user.ignore = false;
435
+ let buffer = this.state.getActiveBuffer();
436
+ this.state.addMessage(buffer, {
437
+ nick: '*',
438
+ message: 'No longer ignoring ' + user.nick,
439
+ type: 'message',
440
+ });
441
+ }
442
+ };
443
+
444
+ inputCommands.close = function inputCommandClose(event, command, line) {
445
+ event.handled = true;
446
+
447
+ let network = this.state.getActiveNetwork();
448
+ let bufferNames = _.compact(line.split(/[, ]/));
449
+ if (bufferNames.length === 0) {
450
+ bufferNames = [this.state.getActiveBuffer().name];
451
+ }
452
+
453
+ bufferNames.forEach((bufferName) => {
454
+ let buffer = network.bufferByName(bufferName);
455
+ if (!buffer) {
456
+ return;
457
+ }
458
+
459
+ this.state.removeBuffer(buffer);
460
+ });
461
+ };
462
+
463
+ inputCommands.query = function inputCommandQuery(event, command, line) {
464
+ event.handled = true;
465
+
466
+ let pos = line.indexOf(' ');
467
+ if (pos === -1) {
468
+ pos = line.length;
469
+ }
470
+
471
+ let nick = line.substr(0, pos);
472
+ let message = line.substr(pos + 1);
473
+
474
+ let network = this.state.getActiveNetwork();
475
+ let buffer = this.state.getOrAddBufferByName(network.id, nick);
476
+
477
+ this.state.setActiveBuffer(network.id, buffer.name);
478
+
479
+ if (message) {
480
+ this.state.$emit('input.raw', '/msg ' + buffer.name + ' ' + message);
481
+ }
482
+ };
483
+
484
+ inputCommands.invite = function inputCommandInvite(event, command, line) {
485
+ event.handled = true;
486
+
487
+ let network = this.state.getActiveNetwork();
488
+ let buffer = this.state.getActiveBuffer();
489
+
490
+ let lineParts = line.split(' ');
491
+ let nick = lineParts.shift();
492
+ let channel = lineParts.shift();
493
+
494
+ if (!channel) {
495
+ channel = buffer.name;
496
+ }
497
+
498
+ if (!network.isChannelName(channel)) {
499
+ return;
500
+ }
501
+
502
+ network.ircClient.raw('INVITE', nick, channel);
503
+ this.state.addMessage(buffer, {
504
+ nick: '*',
505
+ message: `Invited ${nick} to ${channel}`,
506
+ type: 'message',
507
+ });
508
+ };
509
+
510
+ inputCommands.nick = function inputCommandNick(event, command, line) {
511
+ event.handled = true;
512
+
513
+ let spaceIdx = line.indexOf(' ');
514
+ if (spaceIdx === -1) spaceIdx = line.length;
515
+
516
+ let newNick = line.substr(0, spaceIdx);
517
+ let network = this.state.getActiveNetwork();
518
+ network.ircClient.changeNick(newNick);
519
+ };
520
+
521
+ inputCommands.away = function inputCommandAway(event, command, line) {
522
+ event.handled = true;
523
+
524
+ let network = this.state.getActiveNetwork();
525
+ network.ircClient.raw('AWAY', line || 'Currently away');
526
+ };
527
+
528
+ inputCommands.back = function inputCommandAway(event, command, line) {
529
+ event.handled = true;
530
+
531
+ let network = this.state.getActiveNetwork();
532
+ network.ircClient.raw('AWAY');
533
+ };
534
+
535
+ inputCommands.quote = function inputCommandQuote(event, command, line) {
536
+ event.handled = true;
537
+
538
+ let network = this.state.getActiveNetwork();
539
+
540
+ // Sending a manual CAP command triggers raw CAPs to be shown in the server tab
541
+ if (line.split(' ')[0].toLowerCase() === 'cap') {
542
+ network.setting('show_raw_caps', true);
543
+ }
544
+
545
+ let buffer = this.state.getActiveBuffer();
546
+ if (buffer.isServer()) {
547
+ this.state.addMessage(buffer, {
548
+ time: Date.now(),
549
+ nick: '',
550
+ message: line,
551
+ });
552
+ }
553
+
554
+ network.ircClient.raw(line);
555
+ };
556
+
557
+ inputCommands.whois = function inputCommandWhois(event, command, line) {
558
+ event.handled = true;
559
+
560
+ let parts = line.split(' ');
561
+ let network = this.state.getActiveNetwork();
562
+ let buffer = this.state.getActiveBuffer();
563
+
564
+ network.ircClient.whois(parts[0], parts[0], (whoisData) => {
565
+ if (whoisData.error) {
566
+ let messageBody = TextFormatting.formatText('whois_error', {
567
+ nick: whoisData.nick,
568
+ text: whoisData.error,
569
+ });
570
+ this.state.addMessage(buffer, {
571
+ time: Date.now(),
572
+ nick: '',
573
+ message: messageBody,
574
+ type: 'error',
575
+ });
576
+ return;
577
+ }
578
+
579
+ let out = [];
580
+ let display = (message) => {
581
+ if (!message) {
582
+ return;
583
+ }
584
+
585
+ out.push(message);
586
+ };
587
+ let formats = {
588
+ mask: 'is {{nick}}!{{user}}@{{host}} * ({{real_name}})',
589
+ from: 'is connecting from {{actual_hostname}} {{actual_ip}}',
590
+ channels: 'is on {{channels}}',
591
+ server: 'is using {{server}} ({{server_info}})',
592
+ operator: '{{operator}}',
593
+ modes: '{{modes}}',
594
+ account: 'is logged in as {{account}}',
595
+ registered_nick: '{{registered_nick}}',
596
+ secure: 'is using a secure connection',
597
+ idle: 'has been idle for {{idle}}',
598
+ country: '{{country}}',
599
+ certfp: '{{certfp}}',
600
+ logon: 'connected on {{logon}}',
601
+
602
+ // The following entries will be ignored from whoisData as display() ignores
603
+ // empty lines.
604
+ nick: '',
605
+ user: '',
606
+ ident: '',
607
+ hostname: '',
608
+ real_name: '',
609
+ actual_ip: '',
610
+ server_info: '',
611
+ actual_hostname: '',
612
+ country_code: '',
613
+ };
614
+
615
+ // Display a select few entries first to keep a consistent order, and then
616
+ // show any extra information at the end
617
+ if (whoisData.nick && whoisData.hostname) {
618
+ display(formats.mask
619
+ .replace('{{nick}}', whoisData.nick)
620
+ .replace('{{user}}', whoisData.ident)
621
+ .replace('{{host}}', whoisData.hostname)
622
+ .replace('{{real_name}}', whoisData.real_name));
623
+ }
624
+ if (whoisData.actual_hostname && whoisData.actual_ip) {
625
+ display(formats.from
626
+ .replace('{{actual_hostname}}', whoisData.actual_hostname)
627
+ .replace('{{actual_ip}}', whoisData.actual_ip));
628
+ }
629
+ if (whoisData.country) {
630
+ display(formats.country.replace('{{country}}', whoisData.country));
631
+ }
632
+ if (whoisData.channels) {
633
+ display(formats.channels.replace('{{channels}}', whoisData.channels));
634
+ }
635
+ if (whoisData.server) {
636
+ display(formats.server
637
+ .replace('{{server}}', whoisData.server)
638
+ .replace('{{server_info}}', whoisData.server_info));
639
+ }
640
+ if (whoisData.operator) {
641
+ display(formats.operator.replace('{{operator}}', whoisData.operator));
642
+ }
643
+ if (whoisData.modes) {
644
+ display(formats.modes.replace('{{modes}}', whoisData.modes));
645
+ }
646
+ if (whoisData.account) {
647
+ display(formats.account.replace('{{account}}', whoisData.account));
648
+ }
649
+ if (whoisData.registered_nick) {
650
+ display(formats.registered_nick.replace('{{registered_nick}}', whoisData.registered_nick));
651
+ }
652
+ if (whoisData.secure) {
653
+ display(formats.secure);
654
+ }
655
+ if (whoisData.certfp) {
656
+ display(formats.certfp.replace('{{certfp}}', whoisData.certfp));
657
+ }
658
+ if (whoisData.idle) {
659
+ let idleSeconds = Math.floor(parseInt(whoisData.idle, 10));
660
+ display(formats.idle.replace('{{idle}}', TextFormatting.formatDuration(idleSeconds)));
661
+ }
662
+ if (whoisData.logon) {
663
+ let logonTime = parseInt(whoisData.logon, 10);
664
+ if (!Number.isNaN(logonTime)) {
665
+ let logonDate = new Date(logonTime * 1000);
666
+ display(formats.logon.replace('{{logon}}', logonDate));
667
+ }
668
+ }
669
+
670
+ _.each(whoisData, (val, key) => {
671
+ // Only include lines we haven't already used
672
+ if (typeof formats[key] === 'undefined') {
673
+ // Some keys such as `special` are arrays of values
674
+ if (_.isArray(val)) {
675
+ val.forEach((v) => display(`${key}: ${v}`));
676
+ } else {
677
+ display(`${key}: ${val}`);
678
+ }
679
+ }
680
+ });
681
+
682
+ out.forEach((l) => {
683
+ this.state.addMessage(buffer, {
684
+ nick: parts[0],
685
+ message: l,
686
+ type: 'whois',
687
+ });
688
+ });
689
+ });
690
+ };
691
+
692
+ inputCommands.whowas = function inputCommandWhowas(event, command, line) {
693
+ event.handled = true;
694
+
695
+ let parts = line.split(' ');
696
+ let network = this.state.getActiveNetwork();
697
+ let buffer = this.state.getActiveBuffer();
698
+
699
+ network.ircClient.whowas(parts[0], parts[0], (whowasData) => {
700
+ if (whowasData.error) {
701
+ let messageBody = TextFormatting.formatText('whowas_error', {
702
+ nick: whowasData.nick,
703
+ text: whowasData.error,
704
+ });
705
+ this.state.addMessage(buffer, {
706
+ time: Date.now(),
707
+ nick: '',
708
+ message: messageBody,
709
+ type: 'whowas',
710
+ });
711
+ return;
712
+ }
713
+
714
+ ['whowas_ident', 'whowas_server'].forEach((prop) => {
715
+ let messageBody = TextFormatting.formatText(prop, {
716
+ nick: whowasData.nick,
717
+ ident: whowasData.ident,
718
+ host: whowasData.hostname,
719
+ name: whowasData.real_name,
720
+ server: whowasData.server,
721
+ info: whowasData.server_info,
722
+ });
723
+
724
+ this.state.addMessage(buffer, {
725
+ time: Date.now(),
726
+ nick: whowasData.nick,
727
+ message: messageBody,
728
+ type: 'whowas',
729
+ });
730
+ });
731
+ });
732
+ };
733
+
734
+ inputCommands.mode = function inputCommandMode(event, command, line) {
735
+ event.handled = true;
736
+
737
+ // /mode [target] [+-modes]
738
+
739
+ let network = this.state.getActiveNetwork();
740
+ let buffer = this.state.getActiveBuffer();
741
+ let target = buffer.isChannel() ?
742
+ buffer.name :
743
+ network.nick;
744
+
745
+ let parts = _.compact(line.split(' '));
746
+
747
+ if (line && line[0] !== '+' && line[0] !== '-') {
748
+ target = parts.shift();
749
+ }
750
+
751
+ if (parts[0]) {
752
+ // parts[0] = the mode(s)
753
+ // parts[1] = optional mode arguments
754
+
755
+ // If we're asking for a ban list, show the response in the active channel
756
+ if (parts[0] === '+b' && !parts[1]) {
757
+ buffer.flags.requested_banlist = true;
758
+ // An IRCd may fuck up and simply not reply to a MODE command. Give a few seconds
759
+ // for it to reply and if not, ignore our request was sent
760
+ setTimeout(() => {
761
+ buffer.flags.requested_banlist = false;
762
+ }, 4000);
763
+ }
764
+
765
+ network.ircClient.mode(target, parts[0], parts.splice(1));
766
+ } else {
767
+ // No modes specified will request the modes for the target
768
+ network.ircClient.mode(target);
769
+
770
+ if (target === buffer.name) {
771
+ // If we have requested modes for the active channel then flag it to show
772
+ // the response in the buffer itself. Wait a few seconds before removing
773
+ // the flag as there is no way to determine that everything has been received.
774
+ buffer.flags.requested_modes = true;
775
+ setTimeout(() => {
776
+ buffer.flags.requested_modes = false;
777
+ }, 4000);
778
+ }
779
+ }
780
+ };
781
+
782
+ inputCommands.names = function inputCommandNames(event, command, line) {
783
+ event.handled = true;
784
+
785
+ // /names [#channel]
786
+
787
+ let network = this.state.getActiveNetwork();
788
+ let args = line;
789
+
790
+ if (!args) {
791
+ args = this.state.getActiveBuffer().name;
792
+ }
793
+
794
+ network.ircClient.raw('NAMES ' + args);
795
+ };
796
+
797
+ inputCommands.inject = function inputCommandInject(event, command, line) {
798
+ event.handled = true;
799
+
800
+ let network = this.state.getActiveNetwork();
801
+ let connection = network.ircClient.connection;
802
+ connection.addReadBuffer(line);
803
+ };
804
+
805
+ inputCommands.clear = function inputCommandClear(event, command, line) {
806
+ event.handled = true;
807
+
808
+ let buffer = this.state.getActiveBuffer();
809
+ buffer.clearMessages();
810
+
811
+ this.state.addMessage(buffer, {
812
+ nick: '*',
813
+ message: 'Scrollback cleared',
814
+ });
815
+ };
816
+
817
+ inputCommands.echo = function inputCommandEcho(event, command, line) {
818
+ event.handled = true;
819
+
820
+ let buffer = this.state.getActiveBuffer();
821
+
822
+ this.state.addMessage(buffer, {
823
+ nick: '*',
824
+ message: line,
825
+ });
826
+ };
827
+
828
+ inputCommands.set = function inputCommandEcho(event, command, line) {
829
+ event.handled = true;
830
+
831
+ let buffer = this.state.getActiveBuffer();
832
+
833
+ let setting = '';
834
+ let spacePos = line.indexOf(' ');
835
+
836
+ if (spacePos > -1) {
837
+ // Anything after the space becomes the new setting value
838
+ // false = boolean false
839
+ // true = boolean true
840
+ // off = boolean false
841
+ // on = boolean true
842
+ // "false" = string false
843
+ // "true" = string true
844
+ setting = line.substr(0, spacePos);
845
+ let value = line.substr(spacePos + 1).trim();
846
+ switch (value.toLowerCase().trim()) {
847
+ case 'true':
848
+ case 'on':
849
+ value = true;
850
+ break;
851
+ case 'false':
852
+ case 'off':
853
+ value = false;
854
+ break;
855
+ default:
856
+ }
857
+
858
+ // Unquote any quoted values
859
+ // ie. "true" should jsut be the string true
860
+ if (value[0] === '"' && value[value.length - 1] === '"') {
861
+ value = value.substr(1, value.length - 2);
862
+ }
863
+
864
+ this.state.setting(setting, value);
865
+ } else {
866
+ setting = line;
867
+ }
868
+
869
+ this.state.addMessage(buffer, {
870
+ nick: '*',
871
+ message: `${setting} = ${this.state.setting(setting)}`,
872
+ });
873
+ };
874
+
875
+ inputCommands.list = function inputCommandList(event, command, line) {
876
+ event.handled = true;
877
+
878
+ let network = this.state.getActiveNetwork();
879
+ if (!network.channel_list.length && network.channel_list_state !== 'updating') {
880
+ network.channel_list_state = 'updating';
881
+ network.ircClient.raw('LIST ' + line);
882
+ }
883
+
884
+ network.showServerBuffer('channels');
885
+ };
886
+
887
+ inputCommands.server = function inputCommandServer(event, command, line) {
888
+ event.handled = true;
889
+
890
+ let parts = line.split(' ');
891
+ let serverAddr = parts[0];
892
+ let serverPort = parts[1] || 6667;
893
+ let serverTls = false;
894
+ let serverPassword = parts[2];
895
+ let nick = parts[3] || 'ircuser';
896
+
897
+ if (serverPort[0] === '+') {
898
+ serverTls = true;
899
+ serverPort = parseInt(serverPort.substr(1), 10);
900
+ } else {
901
+ serverTls = false;
902
+ serverPort = parseInt(serverPort, 10);
903
+ }
904
+
905
+ this.state.addNetwork(serverAddr, nick, {
906
+ server: serverAddr,
907
+ port: serverPort,
908
+ tls: serverTls,
909
+ password: serverPassword,
910
+ });
911
+ };
912
+
913
+ inputCommands.beep = function inputCommandBeep(event, command, line) {
914
+ event.handled = true;
915
+ this.state.$emit('audio.bleep');
916
+ };
917
+
918
+ inputCommands.notify = function inputCommandNotify(event, command, line) {
919
+ event.handled = true;
920
+ this.state.$emit('notification.show', line);
921
+ };