diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index cb9920bf..1959586e 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -33,7 +33,7 @@ jobs: - name: Setup Hugo uses: peaceiris/actions-hugo@v2 with: - hugo-version: '0.80.0' + hugo-version: '0.132.2' extended: true - name: Generate documentation translations diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea51c9ff..1e3e485f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,7 +22,7 @@ pages: image: registry.gitlab.com/pages/hugo/hugo_extended:latest variables: GIT_SUBMODULE_STRATEGY: recursive - GIT_SUBMODULE_PATHS: support/documentation/themes/hugo-theme-learn + GIT_SUBMODULE_PATHS: support/documentation/themes/hugo-theme-relearn script: # gitlab need the generated documentation to be in the /public dir. - hugo -s support/documentation/ --minify -d ../../public/ --baseURL='https://livingston.frama.io/peertube-plugin-livechat/' diff --git a/.gitmodules b/.gitmodules index 5f7f7e4e..91decf4c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,6 +2,6 @@ # # SPDX-License-Identifier: AGPL-3.0-only -[submodule "documentation/themes/hugo-theme-learn"] - path = support/documentation/themes/hugo-theme-learn - url = https://github.com/matcornic/hugo-theme-learn.git +[submodule "support/documentation/themes/hugo-theme-relearn"] + path = support/documentation/themes/hugo-theme-relearn + url = https://github.com/McShelby/hugo-theme-relearn.git diff --git a/.reuse/dep5 b/.reuse/dep5 index 68795aa2..0f4c7451 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -32,3 +32,7 @@ License: AGPL-3.0-only Files: .github/PULL_REQUEST_TEMPLATE.md Copyright: 2024 John Livingston License: AGPL-3.0-only + +Files: prosody-modules/mod_firewall/* +Copyright: Prosody Community Modules +License: MIT diff --git a/CHANGELOG.md b/CHANGELOG.md index 352a3d9f..5e08072f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## 11.0.1 + +### Minor changes and fixes + +* Fix "send message" button that was sending the message twice. + +## 11.0.0 + +### Importante Notes + +With the new [mod_firewall](https://livingston.frama.io/peertube-plugin-livechat/documentation/admin/mod_firewall/) feature, Peertube admins can write firewall rules for the Prosody server. These rules could be used to run arbitrary code on the server. If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory. Check the documentation for more information. This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. + +The concord theme was removed from ConverseJS. If you had it set in the plugin settings, it will fallback to the Peertube theme. + +### New features + +* Updating ConverseJS, to use upstream (v11 WIP). This comes with many improvements and new features. +* #146: copy message button for moderators. +* #137: option to hide moderator name who made actions (kick, ban, message moderation, ...). +* #144: [moderator notes](https://livingston.frama.io/peertube-plugin-livechat/documentation/user/streamers/moderation_notes/). +* #145: action for moderators to find all messages from a given participant. +* #97: option to use and configure [mod_firewall](https://livingston.frama.io/peertube-plugin-livechat/documentation/admin/mod_firewall/) at the server level. + +### Minor changes and fixes + +* #118: improved accessibility. +* Avatar set for anonymous users: new 'none' choice (that will fallback to Converse new colorized avatars). +* New translation: Albanian. +* Translation updates: Crotian, Japanese, traditional Chinese, Arabic, Galician. +* Updated mod_muc_moderation to upstream. +* Fix new task ordering. +* Fix: clicking on the current user nickname in message history was failing to open the profile modal. +* Fix: increase chat height on small screens, try to better detect the device viewport size and orientation. +* Converse theme: removed concord, added cyberpunk. +* Fixed Converse theme settings localization. +* Fix: improved minimum chat width. + ## 10.3.3 ### Minor changes and fixes diff --git a/assets/styles/admin/firewall/_firewall.scss b/assets/styles/admin/firewall/_firewall.scss new file mode 100644 index 00000000..1871a09b --- /dev/null +++ b/assets/styles/admin/firewall/_firewall.scss @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2024 John Livingston + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* stylelint-disable custom-property-pattern */ + +@use "sass:color"; +@use "../../variables"; + +.peertube-plugin-livechat-admin-firewall { + h1 { + padding-top: 40px; + + /* See Peertube sub-menu-h1 mixin */ + font-size: 1.3rem; + border-bottom: 2px solid var(--greyBackgroundColor); + padding-bottom: 15px; + } + + textarea[name^="_content_"] { + min-height: 10rem; + } + + input[type="submit"], + input[type="reset"], + button[type="submit"], + button[type="reset"] { + // Peertube rounded-line-height-1-5 mixins + line-height: variables.$button-calc-line-height; + + // Peertube peertube-button mixin + padding: 4px 13px; + border: 0; + font-weight: variables.$font-semibold; + border-radius: 3px !important; + text-align: center; + cursor: pointer; + font-size: variables.$button-font-size; + } + + input[type="submit"], + button[type="submit"] { + // Peertube orange-button mixin + &, + &:active, + &:focus { + color: #fff; + background-color: var(--mainColor); + } + + &:hover { + color: #fff; + background-color: var(--mainHoverColor); + } + + &[disabled], + &.disabled { + cursor: default; + color: #fff; + background-color: var(--inputBorderColor); + } + } + + input[type="reset"], + button[type="reset"] { + // Peertube grey-button mixin + background-color: var(--greyBackgroundColor); + color: var(--greyForegroundColor); + + &:hover, + &:active, + &:focus, + &[disabled], + &.disabled { + color: var(--greyForegroundColor); + background-color: var(--greySecondaryBackgroundColor); + } + + &[disabled], + &.disabled { + cursor: default; + } + } + + .peertube-livechat-admin-firewall-col-name { + width: 25%; + } + + .peertube-livechat-admin-firewall-col-content { + width: 65%; + } +} diff --git a/assets/styles/elements/_spinner.scss b/assets/styles/elements/_spinner.scss index 672bf01c..9fa9e39d 100644 --- a/assets/styles/elements/_spinner.scss +++ b/assets/styles/elements/_spinner.scss @@ -15,9 +15,9 @@ livechat-spinner, height: 48px; margin: 20px; /* stylelint-disable-next-line custom-property-pattern */ - border: 5px solid var(--greyBackgroundColor); + border: 5px solid var(--greyBackgroundColor) !important; // !important is required for it to work in ConverseJS /* stylelint-disable-next-line custom-property-pattern */ - border-bottom-color: var(--mainColor); + border-bottom-color: var(--mainColor) !important; // !important is required for it to work in ConverseJS border-radius: 50%; display: inline-block; box-sizing: border-box; diff --git a/assets/styles/style.scss b/assets/styles/style.scss index 0fd2c1bb..1dbc8f8a 100644 --- a/assets/styles/style.scss +++ b/assets/styles/style.scss @@ -9,4 +9,5 @@ @use "elements/index"; @use "video"; @use "configuration/configuration"; +@use "admin/firewall/firewall"; @use "list-rooms/list-rooms.scss"; diff --git a/assets/styles/video/_container.scss b/assets/styles/video/_container.scss index e9c83592..e3a155b9 100644 --- a/assets/styles/video/_container.scss +++ b/assets/styles/video/_container.scss @@ -18,17 +18,31 @@ /* Note: livechat-viewer-mode-content (the form where anonymous users can choose nickname or log in with external account), can be something like ~180px height (at time of writing). - We must ensure that the 200px limit for converse-muc and converse-root is + We must ensure that the px height limit for converse-muc and converse-root is always higher than livechat-viewer-mode-content max size. + Note: We also must ensure that when the user has choosen its nickname, and there is an + ongoing poll, the user can see the chat when the poll is folded. */ #peertube-plugin-livechat-container converse-root { display: block; border: 1px solid black; - min-height: max(30vh, 200px); // Always at least 200px, and ideally at least 30% of viewport. + min-height: max(30vh, 300px); // Always at least 200px, and ideally at least 30% of viewport. height: 100%; + min-width: min(400px, 25vw); converse-muc { - min-height: max(59vh, 400px); + min-height: max(30vh, 300px); + } + + @media screen and (orientation: portrait) and (max-width: 767px) { + /* On small screen, and when portrait mode, we are giving the chat more vertical space. + It should go under the video. + */ + min-height: max(50vh, 300px); + + converse-muc { + min-height: max(50vh, 300px); + } } } diff --git a/client/@types/global.d.ts b/client/@types/global.d.ts index ecc2d9be..13bbbbfc 100644 --- a/client/@types/global.d.ts +++ b/client/@types/global.d.ts @@ -12,6 +12,7 @@ declare const MUSTACHE_CONFIGURATION_CHANNEL: string // Constants that begins with "LOC_" are loaded by build-client.js, reading the english locale file. // See the online documentation: https://livingston.frama.io/peertube-plugin-livechat/contributing/translate/ declare const LOC_ONLINE_HELP: string +declare const LOC_CHAT: string declare const LOC_OPEN_CHAT: string declare const LOC_OPEN_CHAT_NEW_WINDOW: string declare const LOC_CLOSE_CHAT: string @@ -133,3 +134,13 @@ declare const LOC_POLL_VOTE_OK: string declare const LOC_MODERATION_DELAY: string declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_MODERATION_DELAY_DESC: string +declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_ANONYMIZE_MODERATION_LABEL: string +declare const LOC_LIVECHAT_CONFIGURATION_CHANNEL_ANONYMIZE_MODERATION_DESC: string + +declare const LOC_PROSODY_FIREWALL_CONFIGURATION: string +declare const LOC_PROSODY_FIREWALL_CONFIGURATION_HELP: string +declare const LOC_PROSODY_FIREWALL_DISABLED_WARNING: string +declare const LOC_PROSODY_FIREWALL_FILE_ENABLED: string +declare const LOC_PROSODY_FIREWALL_NAME: string +declare const LOC_PROSODY_FIREWALL_NAME_DESC: string +declare const LOC_PROSODY_FIREWALL_CONTENT: string diff --git a/client/admin-plugin-client-plugin.ts b/client/admin-plugin-client-plugin.ts index 4233e525..7b0f872b 100644 --- a/client/admin-plugin-client-plugin.ts +++ b/client/admin-plugin-client-plugin.ts @@ -270,6 +270,8 @@ function register (clientOptions: RegisterClientOptions): void { return !(options.formValues['chat-all-lives'] === true && options.formValues['chat-per-live-video'] === true) case 'auto-ban-anonymous-ip': return options.formValues['chat-no-anonymous'] !== false + case 'prosody-firewall-configure-button': + return options.formValues['prosody-firewall-enabled'] !== true } if (name?.startsWith('external-auth-')) { diff --git a/client/common-client-plugin.ts b/client/common-client-plugin.ts index e1c23f2b..af7e1d46 100644 --- a/client/common-client-plugin.ts +++ b/client/common-client-plugin.ts @@ -8,6 +8,7 @@ import { registerConfiguration } from './common/configuration/register' import { registerVideoWatch } from './common/videowatch/register' import { registerRoom } from './common/room/register' import { initPtContext } from './common/lib/contexts/peertube' +import { registerAdminFirewall } from './common/admin/firewall/register' import './common/lib/elements' // Import shared elements. async function register (clientOptions: RegisterClientOptions): Promise { @@ -69,7 +70,8 @@ async function register (clientOptions: RegisterClientOptions): Promise { await Promise.all([ registerVideoWatch(), registerRoom(clientOptions), - registerConfiguration(clientOptions) + registerConfiguration(clientOptions), + registerAdminFirewall(clientOptions) ]) } diff --git a/client/common/admin/firewall/elements/admin-firewall.ts b/client/common/admin/firewall/elements/admin-firewall.ts new file mode 100644 index 00000000..aba28c90 --- /dev/null +++ b/client/common/admin/firewall/elements/admin-firewall.ts @@ -0,0 +1,131 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { AdminFirewallConfiguration } from 'shared/lib/types' +import { AdminFirewallService } from '../services/admin-firewall' +import { LivechatElement } from '../../../lib/elements/livechat' +import { ValidationError, ValidationErrorType } from '../../../lib/models/validation' +import { tplAdminFirewall } from '../templates/admin-firewall' +import { TemplateResult, html, nothing } from 'lit' +import { customElement, state } from 'lit/decorators.js' +import { Task } from '@lit/task' + +@customElement('livechat-admin-firewall') +export class AdminFirewallElement extends LivechatElement { + private _adminFirewallService?: AdminFirewallService + + @state() + public firewallConfiguration?: AdminFirewallConfiguration + + @state() + public validationError?: ValidationError + + @state() + public actionDisabled: boolean = false + + private _asyncTaskRender: Task + + constructor () { + super() + this._asyncTaskRender = this._initTask() + } + + protected _initTask (): Task { + return new Task(this, { + task: async () => { + this._adminFirewallService = new AdminFirewallService(this.ptOptions) + this.firewallConfiguration = await this._adminFirewallService.fetchConfiguration() + this.actionDisabled = false // in case of reset + }, + args: () => [] + }) + } + + /** + * Resets the form by reloading data from backend. + */ + public async reset (event?: Event): Promise { + event?.preventDefault() + this.actionDisabled = true + this._asyncTaskRender = this._initTask() + this.requestUpdate() + } + + /** + * Resets the validation errors. + * @param ev the vent + */ + public resetValidation (_ev?: Event): void { + if (this.validationError) { + this.validationError = undefined + this.requestUpdate('_validationError') + } + } + + /** + * Saves the configuration. + * @param event event + */ + public readonly saveConfig = async (event?: Event): Promise => { + event?.preventDefault() + if (!this.firewallConfiguration || !this._adminFirewallService) { + return + } + this.actionDisabled = true + this._adminFirewallService.saveConfiguration(this.firewallConfiguration) + .then((result: AdminFirewallConfiguration) => { + this.validationError = undefined + this.ptTranslate(LOC_SUCCESSFULLY_SAVED).then((msg) => { + this.ptNotifier.info(msg) + }, () => {}) + this.firewallConfiguration = result + this.requestUpdate('firewallConfiguration') + this.requestUpdate('_validationError') + }) + .catch(async (error: Error) => { + this.validationError = undefined + if (error instanceof ValidationError) { + this.validationError = error + } + this.logger.warn(`A validation error occurred in saving configuration. ${error.name}: ${error.message}`) + this.ptNotifier.error( + error.message + ? error.message + : await this.ptTranslate(LOC_ERROR) + ) + this.requestUpdate('_validationError') + }) + .finally(() => { + this.actionDisabled = false + }) + } + + public readonly getInputValidationClass = (propertyName: string): { [key: string]: boolean } => { + const validationErrorTypes: ValidationErrorType[] | undefined = + this.validationError?.properties[`${propertyName}`] + return validationErrorTypes ? (validationErrorTypes.length ? { 'is-invalid': true } : { 'is-valid': true }) : {} + } + + public readonly renderFeedback = (feedbackId: string, + propertyName: string): TemplateResult | typeof nothing => { + const errorMessages: TemplateResult[] = [] + const validationErrorTypes: ValidationErrorType[] | undefined = + this.validationError?.properties[`${propertyName}`] ?? undefined + + // FIXME: this code is duplicated in dymamic table form + if (validationErrorTypes && validationErrorTypes.length !== 0) { + return html`${errorMessages}` + } else { + return nothing + } + } + + protected override render = (): unknown => { + return this._asyncTaskRender.render({ + pending: () => html``, + error: () => html``, + complete: () => tplAdminFirewall(this) + }) + } +} diff --git a/client/common/admin/firewall/elements/index.ts b/client/common/admin/firewall/elements/index.ts new file mode 100644 index 00000000..71940db2 --- /dev/null +++ b/client/common/admin/firewall/elements/index.ts @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import './admin-firewall' diff --git a/client/common/admin/firewall/register.ts b/client/common/admin/firewall/register.ts new file mode 100644 index 00000000..a9e63d45 --- /dev/null +++ b/client/common/admin/firewall/register.ts @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { RegisterClientOptions } from '@peertube/peertube-types/client' +import { html, render } from 'lit' +import './elements' // Import all needed elements. + +/** + * Registers stuff related to mod_firewall configuration. + * @param clientOptions Peertube client options + */ +async function registerAdminFirewall (clientOptions: RegisterClientOptions): Promise { + const { registerClientRoute } = clientOptions + + registerClientRoute({ + route: 'livechat/admin/firewall', + onMount: async ({ rootEl }) => { + render(html``, rootEl) + } + }) +} + +export { + registerAdminFirewall +} diff --git a/client/common/admin/firewall/services/admin-firewall.ts b/client/common/admin/firewall/services/admin-firewall.ts new file mode 100644 index 00000000..e4cc908a --- /dev/null +++ b/client/common/admin/firewall/services/admin-firewall.ts @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { RegisterClientOptions } from '@peertube/peertube-types/client' +import type { AdminFirewallConfiguration } from 'shared/lib/types' +import { + maxFirewallFileSize, maxFirewallNameLength, maxFirewallFiles, firewallNameRegexp +} from 'shared/lib/admin-firewall' +import { ValidationError, ValidationErrorType } from '../../../lib/models/validation' +import { getBaseRoute } from '../../../../utils/uri' + +export class AdminFirewallService { + public _registerClientOptions: RegisterClientOptions + + private readonly _headers: any = {} + + constructor (registerClientOptions: RegisterClientOptions) { + this._registerClientOptions = registerClientOptions + + this._headers = this._registerClientOptions.peertubeHelpers.getAuthHeader() ?? {} + this._headers['content-type'] = 'application/json;charset=UTF-8' + } + + async validateConfiguration (adminFirewallConfiguration: AdminFirewallConfiguration): Promise { + const propertiesError: ValidationError['properties'] = {} + + if (adminFirewallConfiguration.files.length > maxFirewallFiles) { + const validationError = new ValidationError( + 'AdminFirewallConfigurationValidationError', + await this._registerClientOptions.peertubeHelpers.translate(LOC_TOO_MANY_ENTRIES), + propertiesError + ) + throw validationError + } + + const seen = new Map() + for (const [i, e] of adminFirewallConfiguration.files.entries()) { + propertiesError[`files.${i}.name`] = [] + if (e.name === '') { + propertiesError[`files.${i}.name`].push(ValidationErrorType.Missing) + } else if (e.name.length > maxFirewallNameLength) { + propertiesError[`files.${i}.name`].push(ValidationErrorType.TooLong) + } else if (!firewallNameRegexp.test(e.name)) { + propertiesError[`files.${i}.name`].push(ValidationErrorType.WrongFormat) + } else if (seen.has(e.name)) { + propertiesError[`files.${i}.name`].push(ValidationErrorType.Duplicate) + } else { + seen.set(e.name, true) + } + + propertiesError[`files.${i}.content`] = [] + if (e.content.length > maxFirewallFileSize) { + propertiesError[`files.${i}.content`].push(ValidationErrorType.TooLong) + } + } + + if (Object.values(propertiesError).find(e => e.length > 0)) { + const validationError = new ValidationError( + 'AdminFirewallConfigurationValidationError', + await this._registerClientOptions.peertubeHelpers.translate(LOC_VALIDATION_ERROR), + propertiesError + ) + throw validationError + } + + return true + } + + async saveConfiguration ( + adminFirewallConfiguration: AdminFirewallConfiguration + ): Promise { + if (!await this.validateConfiguration(adminFirewallConfiguration)) { + throw new Error('Invalid form data') + } + + const response = await fetch( + getBaseRoute(this._registerClientOptions) + '/api/admin/firewall/', + { + method: 'POST', + headers: this._headers, + body: JSON.stringify(adminFirewallConfiguration) + } + ) + + if (!response.ok) { + throw new Error('Failed to save configuration.') + } + + return response.json() + } + + async fetchConfiguration (): Promise { + const response = await fetch( + getBaseRoute(this._registerClientOptions) + '/api/admin/firewall/', + { + method: 'GET', + headers: this._headers + } + ) + + if (!response.ok) { + throw new Error('Can\'t get firewall configuration.') + } + + return response.json() + } +} diff --git a/client/common/admin/firewall/templates/admin-firewall.ts b/client/common/admin/firewall/templates/admin-firewall.ts new file mode 100644 index 00000000..ffa430c9 --- /dev/null +++ b/client/common/admin/firewall/templates/admin-firewall.ts @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { AdminFirewallElement } from '../elements/admin-firewall' +import type { TemplateResult } from 'lit' +import type { DynamicFormHeader, DynamicFormSchema } from '../../../lib/elements/dynamic-table-form' +import { maxFirewallFiles, maxFirewallNameLength, maxFirewallFileSize } from 'shared/lib/admin-firewall' +import { ptTr } from '../../../lib/directives/translation' +import { html } from 'lit' + +export function tplAdminFirewall (el: AdminFirewallElement): TemplateResult { + const tableHeaderList: DynamicFormHeader = { + enabled: { + colName: ptTr(LOC_PROSODY_FIREWALL_FILE_ENABLED) + }, + name: { + colName: ptTr(LOC_PROSODY_FIREWALL_NAME), + description: ptTr(LOC_PROSODY_FIREWALL_NAME_DESC), + headerClassList: ['peertube-livechat-admin-firewall-col-name'] + }, + content: { + colName: ptTr(LOC_PROSODY_FIREWALL_CONTENT), + headerClassList: ['peertube-livechat-admin-firewall-col-content'] + } + } + const tableSchema: DynamicFormSchema = { + enabled: { + inputType: 'checkbox', + default: true + }, + name: { + inputType: 'text', + default: '', + maxlength: maxFirewallNameLength + }, + content: { + inputType: 'textarea', + default: '', + maxlength: maxFirewallFileSize + } + } + + return html` + + + ${ptTr(LOC_PROSODY_FIREWALL_CONFIGURATION)} + + + ${ptTr(LOC_PROSODY_FIREWALL_CONFIGURATION_HELP, true)} + + + + ${ + el.firewallConfiguration?.enabled + ? '' + : html`${ptTr(LOC_PROSODY_FIREWALL_DISABLED_WARNING, true)}` + } + + + { + el.resetValidation(e) + if (el.firewallConfiguration) { + el.firewallConfiguration.files = e.detail + el.requestUpdate('firewallConfiguration') + } + } + } + > + + + + ${ptTr(LOC_CANCEL)} + + + ${ptTr(LOC_SAVE)} + + + + ` +} diff --git a/client/common/configuration/elements/channel-home.ts b/client/common/configuration/elements/channel-home.ts index 6d70459a..0a861ccc 100644 --- a/client/common/configuration/elements/channel-home.ts +++ b/client/common/configuration/elements/channel-home.ts @@ -50,7 +50,7 @@ export class ChannelHomeElement extends LivechatElement { ${this._channels?.map((channel) => html` - + ${channel.avatar ? html`` : html`` diff --git a/client/common/configuration/elements/templates/channel-configuration.ts b/client/common/configuration/elements/templates/channel-configuration.ts index 69878f95..ff950eec 100644 --- a/client/common/configuration/elements/templates/channel-configuration.ts +++ b/client/common/configuration/elements/templates/channel-configuration.ts @@ -135,6 +135,7 @@ export function tplChannelConfiguration (el: ChannelConfigurationElement): Templ { if (event?.target && el.channelConfiguration) { @@ -254,6 +255,32 @@ export function tplChannelConfiguration (el: ChannelConfigurationElement): Templ ${el.renderFeedback('peertube-livechat-moderation-delay-feedback', 'moderation.delay')} + + + + + { + if (event?.target && el.channelConfiguration) { + el.channelConfiguration.configuration.moderation.anonymize = + (event.target as HTMLInputElement).checked + } + el.requestUpdate('channelConfiguration') + } + } + value="1" + ?checked=${el.channelConfiguration?.configuration.moderation.anonymize} + /> + ${ptTr(LOC_LIVECHAT_CONFIGURATION_CHANNEL_ANONYMIZE_MODERATION_LABEL)} + + + @@ -15,6 +16,7 @@ export const AddSVG: string = // This content comes from the file assets/images/x-square.svg, from the Feather icons set https://feathericons.com/ export const RemoveSVG: string = ` diff --git a/client/common/lib/elements/dynamic-table-form.ts b/client/common/lib/elements/dynamic-table-form.ts index bc0099e5..93436e03 100644 --- a/client/common/lib/elements/dynamic-table-form.ts +++ b/client/common/lib/elements/dynamic-table-form.ts @@ -47,11 +47,11 @@ interface CellDataSchema { minlength?: number maxlength?: number size?: number - label?: TemplateResult | string options?: { [key: string]: string } datalist?: DynamicTableAcceptedTypes[] separator?: string inputType?: DynamicTableAcceptedInputTypes + inputTitle?: string default?: DynamicTableAcceptedTypes colClassList?: string[] // CSS classes to add to the element. } @@ -64,7 +64,7 @@ interface DynamicTableRowData { interface DynamicFormHeaderCellData { colName: TemplateResult | DirectiveResult - description: TemplateResult | DirectiveResult + description?: TemplateResult | DirectiveResult headerClassList?: string[] } @@ -236,7 +236,7 @@ export class DynamicTableFormElement extends LivechatElement { classList.push(...headerCellData.headerClassList) } return html` - ${headerCellData.description} + ${headerCellData.description ?? ''} ` } @@ -295,6 +295,7 @@ export class DynamicTableFormElement extends LivechatElement { const inputId = `peertube-livechat-${this.formName.replace(/_/g, '-')}-${propertyName.toString().replace(/_/g, '-')}-${rowId}` + const inputTitle: DirectiveResult | undefined = propertySchema.inputTitle ?? this.header[propertyName]?.colName const feedback = this._renderFeedback(inputId, propertyName, originalIndex) switch (propertySchema.default?.constructor) { @@ -320,6 +321,7 @@ export class DynamicTableFormElement extends LivechatElement { formElement = html`${this._renderInput(rowId, inputId, inputName, + inputTitle, propertyName, propertySchema, propertyValue as string, @@ -332,6 +334,7 @@ export class DynamicTableFormElement extends LivechatElement { formElement = html`${this._renderTextarea(rowId, inputId, inputName, + inputTitle, propertyName, propertySchema, propertyValue as string, @@ -344,6 +347,7 @@ export class DynamicTableFormElement extends LivechatElement { formElement = html`${this._renderSelect(rowId, inputId, inputName, + inputTitle, propertyName, propertySchema, propertyValue as string, @@ -356,6 +360,7 @@ export class DynamicTableFormElement extends LivechatElement { formElement = html`${this._renderImageFileInput(rowId, inputId, inputName, + inputTitle, propertyName, propertySchema, propertyValue?.toString(), @@ -376,6 +381,7 @@ export class DynamicTableFormElement extends LivechatElement { formElement = html`${this._renderInput(rowId, inputId, inputName, + inputTitle, propertyName, propertySchema, (propertyValue as Date).toISOString(), @@ -394,6 +400,7 @@ export class DynamicTableFormElement extends LivechatElement { formElement = html`${this._renderInput(rowId, inputId, inputName, + inputTitle, propertyName, propertySchema, propertyValue as string, @@ -411,6 +418,7 @@ export class DynamicTableFormElement extends LivechatElement { formElement = html`${this._renderCheckbox(rowId, inputId, inputName, + inputTitle, propertyName, propertySchema, propertyValue as boolean, @@ -446,6 +454,7 @@ export class DynamicTableFormElement extends LivechatElement { formElement = html`${this._renderInput(rowId, inputId, inputName, + inputTitle, propertyName, propertySchema, (propertyValue)?.join(propertySchema.separator ?? ',') ?? @@ -461,6 +470,7 @@ export class DynamicTableFormElement extends LivechatElement { formElement = html`${this._renderTextarea(rowId, inputId, inputName, + inputTitle, propertyName, propertySchema, (propertyValue)?.join(propertySchema.separator ?? ',') ?? @@ -476,6 +486,7 @@ export class DynamicTableFormElement extends LivechatElement { formElement = html`${this._renderTagsInput(rowId, inputId, inputName, + inputTitle, propertyName, propertySchema, propertyValue, @@ -501,6 +512,7 @@ export class DynamicTableFormElement extends LivechatElement { _renderInput = (rowId: number, inputId: string, inputName: string, + inputTitle: string | DirectiveResult | undefined, propertyName: string, propertySchema: CellDataSchema, propertyValue: string, @@ -515,6 +527,7 @@ export class DynamicTableFormElement extends LivechatElement { ) )} id=${inputId} + title=${ifDefined(inputTitle)} aria-describedby="${inputId}-feedback" list=${ifDefined(propertySchema.datalist ? inputId + '-datalist' : undefined)} min=${ifDefined(propertySchema.min)} @@ -534,6 +547,7 @@ export class DynamicTableFormElement extends LivechatElement { _renderTagsInput = (rowId: number, inputId: string, inputName: string, + inputTitle: string | DirectiveResult | undefined, propertyName: string, propertySchema: CellDataSchema, propertyValue: Array, @@ -547,7 +561,7 @@ export class DynamicTableFormElement extends LivechatElement { ) )} id=${inputId} - .inputPlaceholder=${propertySchema.label as any} + .inputTitle=${inputTitle as any} aria-describedby="${inputId}-feedback" .min=${propertySchema.min} .max=${propertySchema.max} @@ -563,6 +577,7 @@ export class DynamicTableFormElement extends LivechatElement { _renderTextarea = (rowId: number, inputId: string, inputName: string, + inputTitle: string | DirectiveResult | undefined, propertyName: string, propertySchema: CellDataSchema, propertyValue: string, @@ -576,6 +591,7 @@ export class DynamicTableFormElement extends LivechatElement { ) )} id=${inputId} + title=${ifDefined(inputTitle)} aria-describedby="${inputId}-feedback" min=${ifDefined(propertySchema.min)} max=${ifDefined(propertySchema.max)} @@ -588,6 +604,7 @@ export class DynamicTableFormElement extends LivechatElement { _renderCheckbox = (rowId: number, inputId: string, inputName: string, + inputTitle: string | DirectiveResult | undefined, propertyName: string, propertySchema: CellDataSchema, propertyValue: boolean, @@ -602,6 +619,7 @@ export class DynamicTableFormElement extends LivechatElement { ) )} id=${inputId} + title=${ifDefined(inputTitle)} aria-describedby="${inputId}-feedback" @change=${(event: Event) => this._updatePropertyFromValue(event, propertyName, propertySchema, rowId)} value="1" @@ -611,6 +629,7 @@ export class DynamicTableFormElement extends LivechatElement { _renderSelect = (rowId: number, inputId: string, inputName: string, + inputTitle: string | DirectiveResult | undefined, propertyName: string, propertySchema: CellDataSchema, propertyValue: string, @@ -623,11 +642,12 @@ export class DynamicTableFormElement extends LivechatElement { ) )} id=${inputId} + title=${ifDefined(inputTitle)} aria-describedby="${inputId}-feedback" aria-label=${inputName} @change=${(event: Event) => this._updatePropertyFromValue(event, propertyName, propertySchema, rowId)} > - ${propertySchema.label ?? 'Choose your option'} + ${inputTitle ?? ''} ${Object.entries(propertySchema.options ?? {}) ?.map(([value, name]) => html`${name}` @@ -638,6 +658,7 @@ export class DynamicTableFormElement extends LivechatElement { _renderImageFileInput = (rowId: number, inputId: string, inputName: string, + inputTitle: string | DirectiveResult | undefined, propertyName: string, propertySchema: CellDataSchema, propertyValue: string, @@ -647,6 +668,7 @@ export class DynamicTableFormElement extends LivechatElement { .name=${inputName} class=${classMap(this._getInputValidationClass(propertyName, originalIndex))} id=${inputId} + .inputTitle=${inputTitle as any} aria-describedby="${inputId}-feedback" @change=${(event: Event) => this._updatePropertyFromValue(event, propertyName, propertySchema, rowId)} .value=${propertyValue} diff --git a/client/common/lib/elements/image-file-input.ts b/client/common/lib/elements/image-file-input.ts index 2180cd0e..6c6a528d 100644 --- a/client/common/lib/elements/image-file-input.ts +++ b/client/common/lib/elements/image-file-input.ts @@ -1,11 +1,11 @@ // SPDX-FileCopyrightText: 2024 John Livingston // // SPDX-License-Identifier: AGPL-3.0-only - import { LivechatElement } from './livechat' import { html } from 'lit' +import type { DirectiveResult } from 'lit/directive' import { customElement, property } from 'lit/decorators.js' - +import { ifDefined } from 'lit/directives/if-defined.js' /** * Special element to upload image files. * If no current value, displays an input type="file" field. @@ -29,13 +29,16 @@ export class ImageFileInputElement extends LivechatElement { @property({ attribute: false }) public maxSize?: number + @property({ attribute: false }) + public inputTitle?: string | DirectiveResult + @property({ attribute: false }) public accept: string[] = ['image/jpg', 'image/png', 'image/gif'] protected override render = (): unknown => { return html` ${this.value - ? html` { + ? html` { ev.preventDefault() const upload: HTMLInputElement | null | undefined = this.parentElement?.querySelector('input[type="file"]') upload?.click() @@ -44,6 +47,7 @@ export class ImageFileInputElement extends LivechatElement { } this._handleInputEvent(e)} @change=${(e: Event) => e.stopPropagation()} .value=${this._inputValue ?? ''} - placeholder=${ifDefined(this.inputPlaceholder)} /> + title=${ifDefined(this.inputTitle)} /> ${(this.datalist) ? html` ${(this.datalist ?? []).map((value) => html``)} diff --git a/client/common/videowatch/button.ts b/client/common/videowatch/button.ts index 045d0275..22acf797 100644 --- a/client/common/videowatch/button.ts +++ b/client/common/videowatch/button.ts @@ -42,6 +42,23 @@ function displayButton (dbo: displayButtonOptions): void { if ('href' in dbo) { button.href = dbo.href } + + if (!button.href || button.href === '#') { + // No href => it is not a link. + button.role = 'button' + button.tabIndex = 0 + + // We must also ensure that the enter key is triggering the onclick + if (button.onclick) { + button.onkeydown = ev => { + if (ev.key === 'Enter') { + ev.preventDefault() + button.click() + } + } + } + } + if (('targetBlank' in dbo) && dbo.targetBlank) { button.target = '_blank' } @@ -52,6 +69,10 @@ function displayButton (dbo: displayButtonOptions): void { tmp.innerHTML = svg.trim() const svgDom = tmp.firstChild if (svgDom) { + if ('ariaHidden' in (svgDom as HTMLElement)) { + // Icon must be hidden for screen readers. + (svgDom as HTMLElement).ariaHidden = 'true' + } button.prepend(svgDom) } } catch (err) { diff --git a/client/common/videowatch/chat.ts b/client/common/videowatch/chat.ts index 1dba3b41..6c7d8f61 100644 --- a/client/common/videowatch/chat.ts +++ b/client/common/videowatch/chat.ts @@ -16,8 +16,6 @@ import { localizedHelpUrl } from '../../utils/help' import { getBaseRoute } from '../../utils/uri' import { displayConverseJS } from '../../utils/conversejs' -let savedMyPluginFlexGrow: string | undefined - /** * Initialize the chat for the current video * @param video the video @@ -25,7 +23,6 @@ let savedMyPluginFlexGrow: string | undefined async function initChat (video: Video): Promise { const ptContext = getPtContext() const logger = ptContext.logger - savedMyPluginFlexGrow = undefined if (!video) { logger.error('No video provided') @@ -46,6 +43,8 @@ async function initChat (video: Video): Promise { container.setAttribute('id', 'peertube-plugin-livechat-container') container.setAttribute('peertube-plugin-livechat-state', 'initializing') container.setAttribute('peertube-plugin-livechat-current-url', window.location.href) + container.role = 'region' + container.ariaLabel = await ptContext.ptOptions.peertubeHelpers.translate(LOC_CHAT) placeholder.append(container) try { @@ -353,19 +352,6 @@ function _hackStyles (on: boolean): void { buttons.classList.remove('peertube-plugin-livechat-buttons-open') } }) - const myPluginPlaceholder: HTMLElement | null = document.querySelector('my-plugin-placeholder') - if (on) { - // Saving current style attributes and maximazing space for the chat - if (myPluginPlaceholder) { - savedMyPluginFlexGrow = myPluginPlaceholder.style.flexGrow // Should be "", but can be anything else. - myPluginPlaceholder.style.flexGrow = '1' - } - } else { - // restoring values... - if (savedMyPluginFlexGrow !== undefined && myPluginPlaceholder) { - myPluginPlaceholder.style.flexGrow = savedMyPluginFlexGrow - } - } } catch (err) { getPtContext().logger.error(`Failed hacking styles: '${err as string}'`) } diff --git a/client/utils/conversejs.ts b/client/utils/conversejs.ts index 89936971..758e14ee 100644 --- a/client/utils/conversejs.ts +++ b/client/utils/conversejs.ts @@ -167,7 +167,7 @@ async function displayConverseJS ( const converseJSParams: InitConverseJSParams = await (response).json() if (!pollListenerInitiliazed) { - // First time we got here, initiliaze this event: + // First time we got here, initialize this event: const i18nVoteOk = await clientOptions.peertubeHelpers.translate(LOC_POLL_VOTE_OK) pollListenerInitiliazed = true document.addEventListener('livechat-poll-vote', () => { diff --git a/conversejs/build-conversejs.sh b/conversejs/build-conversejs.sh index 239c2918..c1078d16 100644 --- a/conversejs/build-conversejs.sh +++ b/conversejs/build-conversejs.sh @@ -15,32 +15,22 @@ set -x # Set CONVERSE_VERSION and CONVERSE_REPO to select which repo and tag/commit/branch use. # Defaults values: -CONVERSE_VERSION="v10.1.6" +CONVERSE_VERSION="v11.0.0" CONVERSE_REPO="https://github.com/conversejs/converse.js.git" # You can eventually set CONVERSE_COMMIT to a specific commit ID, if you want to apply some patches. -CONVERSE_COMMIT="" +# 2024-09-02: using Converse upstream (v11 WIP). +CONVERSE_COMMIT="9952046d580bc2930e29833f4c9987a3d4c95bc2" -# 2014-01-16: we are using a custom version, to wait for some PR to be apply upstream. -# This version includes following changes: -# - #converse.js/3300: Adding the maxWait option for `debouncedPruneHistory` -# - #converse.js/3302: debounce MUC sidebar rendering -# - Fix: refresh the MUC sidebar when participants collection is sorted -# - Fix: MUC occupant list does not sort itself on nicknames or roles changes -# - Fix inconsistency between browsers on textarea outlines -# - Fix: room information not correctly refreshed when modifications are made by other users -# This version already includes following changes that will not be merged in ConverseJS upstream: -# - Don't load vCards for all room occupants when the right menu is closed -# - Changing the default avatar, for something very light (to mitigate blinking effect when vCards are loaded) -# - Custom settings livechat_load_all_vcards for the readonly mode -# - Adding "users" icon in the menu toggle button -# - Removing unecessary plugins: headless/pubsub, minimize, notifications, profile, omemo, push, roomlist, dragresize. -# - Destroy room: remove the challenge, and the new JID -# - New config option [colorize_username](https://conversejs.org/docs/html/configuration.html#colorize_username) -# - New loadEmojis hook, to customize emojis at runtime. -# - Fix custom emojis path when assets_path is not the default path. -CONVERSE_VERSION="livechat-10.1.0" -# CONVERSE_COMMIT="4402fcc3fc60f6c9334f86528c33a0b463371d12" +# It is possible to use another repository, if we want some customization that are not upstream (yet): +# CONVERSE_VERSION="livechat" +# # CONVERSE_COMMIT="4402fcc3fc60f6c9334f86528c33a0b463371d12" +# CONVERSE_REPO="https://github.com/JohnXLivingston/converse.js" +# CONVERSE_COMMIT="xxxx" + +# 2024-09-03: include badges short label and quick fix for sendMessage button CONVERSE_REPO="https://github.com/JohnXLivingston/converse.js" +CONVERSE_VERSION="livechat-11.0.1" +CONVERSE_COMMIT="" rootdir="$(pwd)" src_dir="$rootdir/conversejs" diff --git a/conversejs/builtin.ts b/conversejs/builtin.ts index 3165f871..9cb1cc5a 100644 --- a/conversejs/builtin.ts +++ b/conversejs/builtin.ts @@ -34,6 +34,7 @@ declare global { env: { html: Function sizzle: Function + dayjs: Function } } initConversePlugins: typeof initConversePlugins @@ -218,20 +219,24 @@ async function initConverse ( // * mode === chat-only + !transparent + !readonly + is using a livechat token // Technically it would work in 'chat-only' mode, but i don't want to add too many things to test // (and i now there is some CSS bugs in the task list). - let enableTask = false + // Same for the moderator notes app. + let enableApps = false if (chatIncludeMode === 'peertube-video' || chatIncludeMode === 'peertube-fullpage') { - enableTask = true + enableApps = true } else if ( chatIncludeMode === 'chat-only' && usedLivechatToken && !initConverseParams.transparent && !initConverseParams.forceReadonly ) { - enableTask = true + enableApps = true } - if (enableTask) { + if (enableApps) { params.livechat_task_app_enabled = true params.livechat_task_app_restore = chatIncludeMode === 'peertube-fullpage' || chatIncludeMode === 'chat-only' + params.livechat_note_app_enabled = true + params.livechat_note_app_restore = chatIncludeMode === 'peertube-fullpage' || chatIncludeMode === 'chat-only' + params.livechat_mam_search_app_enabled = true } try { diff --git a/conversejs/custom/index.js b/conversejs/custom/index.js index 51d352c5..fdc969e9 100644 --- a/conversejs/custom/index.js +++ b/conversejs/custom/index.js @@ -8,14 +8,13 @@ * @description This files will override the original ConverseJS index.js file. */ -import '@converse/headless' +import 'shared/styles/index.scss' + import './i18n/index.js' import 'shared/registry.js' import { CustomElement } from 'shared/components/element' import { VIEW_PLUGINS } from './shared/constants.js' -import { _converse, converse } from '@converse/headless/core' - -import 'shared/styles/index.scss' +import { _converse, converse } from '@converse/headless' /* START: Removable plugins * ------------------------ @@ -45,11 +44,16 @@ import './plugins/singleton/index.js' import './plugins/fullscreen/index.js' import '../custom/plugins/size/index.js' +import '../custom/plugins/mam-search/index.js' +import '../custom/plugins/notes/index.js' import '../custom/plugins/tasks/index.js' import '../custom/plugins/terms/index.js' import '../custom/plugins/poll/index.js' /* END: Removable components */ +// Running some specific livechat patches: +import '../custom/livechat-patch-vcard.js' + import { CORE_PLUGINS } from './headless/shared/constants.js' import { ROOM_FEATURES } from './headless/plugins/muc/constants.js' // We must add our custom plugins to CORE_PLUGINS (so it is white listed): @@ -57,11 +61,13 @@ CORE_PLUGINS.push('livechat-converse-size') CORE_PLUGINS.push('livechat-converse-tasks') CORE_PLUGINS.push('livechat-converse-terms') CORE_PLUGINS.push('livechat-converse-poll') +CORE_PLUGINS.push('livechat-converse-notes') +CORE_PLUGINS.push('livechat-converse-mam-search') // We must also add our custom ROOM_FEATURES, so that they correctly resets // (see headless/plugins/muc, getDiscoInfoFeatures, which loops on this const) ROOM_FEATURES.push('x_peertubelivechat_mute_anonymous') -_converse.CustomElement = CustomElement +_converse.exports.CustomElement = CustomElement const initialize = converse.initialize diff --git a/conversejs/custom/livechat-external-login-content.js b/conversejs/custom/livechat-external-login-content.js index 10f4b099..5bca5b5c 100644 --- a/conversejs/custom/livechat-external-login-content.js +++ b/conversejs/custom/livechat-external-login-content.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { api } from '@converse/headless/core.js' +import { api } from '@converse/headless/index.js' import { CustomElement } from 'shared/components/element.js' import { tplExternalLoginModal } from 'templates/livechat-external-login-modal.js' import { __ } from 'i18n' diff --git a/conversejs/custom/livechat-patch-vcard.js b/conversejs/custom/livechat-patch-vcard.js new file mode 100644 index 00000000..e108f71d --- /dev/null +++ b/conversejs/custom/livechat-patch-vcard.js @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +// Here we are patching the vCard plugin, to add some specific optimizations. + +import { _converse, api } from '@converse/headless/index.js' +import { + onOccupantAvatarChanged, + setVCardOnModel, + setVCardOnOccupant +} from '@converse/headless/plugins/vcard/utils.js' + +const pluginDefinition = _converse.pluggable.plugins['converse-vcard'] +const originalInitialize = pluginDefinition.initialize + +pluginDefinition.initialize = function initialize () { + const previousListeners = _converse._events.chatRoomInitialized ?? [] + originalInitialize.apply(this) + + _converse.api.settings.extend({ + livechat_load_all_vcards: false + }) + + // Now we must detect the new chatRoomInitialized listener, and remove it: + const listenersToRemove = [] + for (const def of _converse._events.chatRoomInitialized ?? []) { + if (def.callback && !previousListeners.includes(def.callback)) { + listenersToRemove.push(def.callback) + } + } + for (const callback of listenersToRemove) { + console.debug('Livechat patching vcard: we must remove this listener', callback) + api.listen.not('chatRoomInitialized', callback) + } + + // Adding the new listener: + api.listen.on('chatRoomInitialized', (m) => { + console.debug('Patched version of the vcard chatRoomInitialized event.') + setVCardOnModel(m) + + // loadAll: when in readonly mode (ie: OBS integration), always load all avatars. + const loadAll = api.settings.get('livechat_load_all_vcards') === true + let hiddenOccupants = m.get('hidden_occupants') + if (hiddenOccupants !== true || loadAll) { + m.occupants.forEach(setVCardOnOccupant) + } + m.listenTo(m.occupants, 'add', (occupant) => { + if (hiddenOccupants !== true || loadAll) { + setVCardOnOccupant(occupant) + } + }) + m.on('change:hidden_occupants', () => { + hiddenOccupants = m.get('hidden_occupants') + if (hiddenOccupants !== true || loadAll) { + m.occupants.forEach(setVCardOnOccupant) + } + }) + m.listenTo(m.occupants, 'change:image_hash', o => onOccupantAvatarChanged(o)) + }) +} diff --git a/conversejs/custom/plugins/mam-search/api.js b/conversejs/custom/plugins/mam-search/api.js new file mode 100644 index 00000000..77fe71fe --- /dev/null +++ b/conversejs/custom/plugins/mam-search/api.js @@ -0,0 +1,112 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { api, converse } from '../../../src/headless/index.js' +import { XMLNS_MAM_SEARCH } from './constants.js' + +const env = converse.env +const { + $iq, + Strophe, + sizzle, + log, + TimeoutError, + __, + u +} = env +const NS = Strophe.NS + +async function query (options) { + if (!api.connection.connected()) { + throw new Error('Can\'t call `api.livechat_mam_search.query` before having established an XMPP session') + } + + if (!options?.room) { + throw new Error('api.livechat_mam_search.query: Missing room parameter.') + } + + const attrs = { + type: 'set', + to: options.room + } + + const jid = attrs.to + const supported = await api.disco.supports(XMLNS_MAM_SEARCH, jid) + if (!supported) { + log.warn(`Did not search MAM archive for ${jid} because it doesn't support ${XMLNS_MAM_SEARCH}`) + return { messages: [] } + } + + const queryid = u.getUniqueId() + const stanza = $iq(attrs).c('query', { xmlns: XMLNS_MAM_SEARCH, queryid: queryid }) + + stanza.c('x', { xmlns: NS.XFORM, type: 'submit' }) + .c('field', { var: 'FORM_TYPE', type: 'hidden' }) + .c('value').t(XMLNS_MAM_SEARCH).up().up() + + if (options.from) { + stanza.c('field', { var: 'from' }).c('value') + .t(options.from).up().up() + } + if (options.occupant_id) { + stanza.c('field', { var: 'occupant_id' }).c('value') + .t(options.occupant_id).up().up() + } + stanza.up() + + // TODO: handle RSM (pagination.) + + const connection = api.connection.get() + + const messages = [] + const messageHandler = connection.addHandler((stanza) => { + const result = sizzle(`message > result[xmlns="${NS.MAM}"]`, stanza).pop() + if (result === undefined || result.getAttribute('queryid') !== queryid) { + return true + } + const from = stanza.getAttribute('from') + if (from !== attrs.to) { + log.warn(`Ignoring alleged groupchat MAM message from ${from}`) + return true + } + messages.push(stanza) + return true + }, NS.MAM) + + let error + const timeout = api.settings.get('message_archiving_timeout') + const iqResult = await api.sendIQ(stanza, timeout, false) + + if (iqResult === null) { + const errMsg = __('Timeout while trying to fetch archived messages.') + log.error(errMsg) + error = new TimeoutError(errMsg) + return { messages, error } + } else if (u.isErrorStanza(iqResult)) { + const errMsg = __('An error occurred while querying for archived messages.') + log.error(errMsg) + log.error(iqResult) + error = new Error(errMsg) + return { messages, error } + } + connection.deleteHandler(messageHandler) + + return { messages } +} + +async function showMessagesFrom (occupant) { + const appElement = document.querySelector('livechat-converse-muc-mam-search-app') + if (!appElement) { + throw new Error('Cant find Search App Element') + } + appElement.searchFrom(occupant) + await appElement.showApp() + await appElement.updateComplete // waiting for the app to be open + return appElement +} + +export default { + query, + showMessagesFrom +} diff --git a/conversejs/custom/plugins/mam-search/components/muc-mam-search-app-view.js b/conversejs/custom/plugins/mam-search/components/muc-mam-search-app-view.js new file mode 100644 index 00000000..71150f30 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/components/muc-mam-search-app-view.js @@ -0,0 +1,52 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { api } from '@converse/headless' +import { parseMUCMessage } from '@converse/headless/plugins/muc/parsers.js' +import { MUCApp } from '../../../shared/components/muc-app/index.js' +import { tplMamSearchApp } from '../templates/muc-mam-search-app.js' + +/** + * Custom Element to display the Mam Search Application. + */ +export default class MUCMamSearchApp extends MUCApp { + restoreSettingName = undefined + sessionStorageRestoreKey = undefined + + static get properties () { + return { + model: { type: Object, attribute: true }, // the muc model + occupant: { type: Object, attribute: true }, // the occupant to search (can be undefined if no current search) + results: { type: Object, attribute: true } // a Collection with the results. + } + } + + render () { + return tplMamSearchApp(this, this.model, this.occupant) + } + + searchFrom (occupant) { + this.results = undefined + this.occupant = occupant + const p = api.livechat_mam_search.query({ + room: this.model.get('jid'), + // FIXME: shouldn't we escape the nick? cant see any code that escapes it in Converse. + from: occupant.get('from') || this.model.get('jid') + '/' + (occupant.get('nick') ?? ''), + occupant_id: occupant.get('occupant_id') + }) + + // don't wait the result to show something! (there will be a spinner) + p.then(async (results) => { + this.occupant = occupant // in case user did simultaneous requests + + const messages = await Promise.all(results.messages.map(s => parseMUCMessage(s, this.model))) + // Note: we are not using MUCMessage objects, because we don't want the objects + // used here to interract with objects in the chat rooms. + // We could have a lot of unwanted sideeffects. + this.results = messages.reverse() + }) + } +} + +api.elements.define('livechat-converse-muc-mam-search-app', MUCMamSearchApp) diff --git a/conversejs/custom/plugins/mam-search/components/muc-mam-search-message-view.js b/conversejs/custom/plugins/mam-search/components/muc-mam-search-message-view.js new file mode 100644 index 00000000..60023641 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/components/muc-mam-search-message-view.js @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { CustomElement } from 'shared/components/element.js' +import { tplMucMamSearchMessage } from '../templates/muc-mam-search-message.js' +import { api } from '@converse/headless' + +import '../styles/muc-mam-search-message.scss' + +export default class MUCMamSearchMessageView extends CustomElement { + static get properties () { + return { + message: { type: Object, attribute: true }, // /!\ this is not a model + mucModel: { type: Object, attribute: true }, + searchOccupantModel: { type: Object, attribute: true } + } + } + + async initialize () { + this.listenTo(this.mucModel, 'change', () => this.requestUpdate()) + this.listenTo(this.searchOccupantModel, 'change', () => this.requestUpdate()) + } + + render () { + return tplMucMamSearchMessage(this, this.mucModel, this.searchOccupantModel, this.message) + } + + getMessageOccupant () { + const occupants = this.mucModel?.occupants + if (!occupants?.findOccupant) { return undefined } + + const nick = this.message.nick + const jid = this.message.from + const occupantId = this.message.occupant_id + + if (!nick && !jid && !occupantId) { + return undefined + } + + if (occupantId) { + const o = occupants.findOccupant({ occupant_id: occupantId }) + if (o) { + return o + } + } + + if (jid) { + const o = occupants.findOccupant({ + jid, + nick + }) + if (o) { + return o + } + } + + // If we don't find it, maybe it is a user that has spoken a long time ago (or never spoked). + // In such case, we must create a dummy occupant: + const o = occupants.create({ + nick, + occupant_id: occupantId, + jid + }) + return o + } + + getDateTime () { + if (!this.message.time) { + return undefined + } + try { + const d = new Date(this.message.time) + return d.toLocaleDateString() + ' - ' + d.toLocaleTimeString() + } catch (err) { + console.log(err) + return undefined + } + } +} + +api.elements.define('livechat-converse-muc-mam-search-message', MUCMamSearchMessageView) diff --git a/conversejs/custom/plugins/mam-search/components/muc-mam-search-occupant-view.js b/conversejs/custom/plugins/mam-search/components/muc-mam-search-occupant-view.js new file mode 100644 index 00000000..85251ff5 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/components/muc-mam-search-occupant-view.js @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { CustomElement } from 'shared/components/element.js' +import { tplMucMamSearchOccupant } from '../templates/muc-mam-search-occupant' +import { api } from '@converse/headless' + +import '../styles/muc-mam-search-occupant.scss' + +export default class MUCMamSearchOccupantView extends CustomElement { + static get properties () { + return { + model: { type: Object, attribute: true }, + message: { type: Object, attribute: true } // optional message. + } + } + + async initialize () { + this.listenTo(this.model, 'change', () => this.requestUpdate()) + } + + render () { + return tplMucMamSearchOccupant(this, this.model, this.message) + } +} + +api.elements.define('livechat-converse-muc-mam-search-occupant', MUCMamSearchOccupantView) diff --git a/conversejs/custom/plugins/mam-search/constants.js b/conversejs/custom/plugins/mam-search/constants.js new file mode 100644 index 00000000..c92dd4a2 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/constants.js @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +export const XMLNS_MAM_SEARCH = 'urn:xmpp:mam:2#x-search' diff --git a/conversejs/custom/plugins/mam-search/index.js b/conversejs/custom/plugins/mam-search/index.js new file mode 100644 index 00000000..1af95426 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/index.js @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { api, converse } from '../../../src/headless/index.js' +import { getMessageActionButtons, getOccupantActionButtons } from './utils.js' +import mamSearchApi from './api.js' + +import './components/muc-mam-search-app-view.js' +import './components/muc-mam-search-occupant-view.js' +import './components/muc-mam-search-message-view.js' + +converse.plugins.add('livechat-converse-mam-search', { + dependencies: ['converse-muc', 'converse-muc-views'], + async initialize () { + const _converse = this._converse + + Object.assign(api, { + livechat_mam_search: mamSearchApi + }) + + _converse.api.settings.extend({ + livechat_mam_search_app_enabled: false + }) + + // Adding buttons on messages: + _converse.api.listen.on('getMessageActionButtons', getMessageActionButtons) + // Adding buttons on occupants: + _converse.api.listen.on('getOccupantActionButtons', getOccupantActionButtons) + + // FIXME: should we listen to any event (feature/affiliation change?, mam_enabled?) to refresh messageActionButtons? + } +}) diff --git a/conversejs/custom/plugins/mam-search/styles/muc-mam-search-message.scss b/conversejs/custom/plugins/mam-search/styles/muc-mam-search-message.scss new file mode 100644 index 00000000..2001bad6 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/styles/muc-mam-search-message.scss @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2024 John Livingston + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +.conversejs { + livechat-converse-muc-mam-search-message { + border: 1px solid var(--chatroom-head-bg-color); + border-radius: 4px; + display: block; + margin: 0.25em 0; + padding: 0.25em; + width: 100%; + + converse-rich-text { + color: var(--message-text-color); + font-size: var(--message-font-size); + padding: 0; + white-space: pre-wrap; + word-wrap: break-word; + word-break: break-word; + } + + .livechat-message-date { + font-size: 0.75em; + list-style: none; + text-align: right; + } + } +} diff --git a/conversejs/custom/plugins/mam-search/styles/muc-mam-search-occupant.scss b/conversejs/custom/plugins/mam-search/styles/muc-mam-search-occupant.scss new file mode 100644 index 00000000..2678b926 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/styles/muc-mam-search-occupant.scss @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 John Livingston + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +.conversejs { + livechat-converse-muc-mam-search-occupant { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + padding: 0.25em; + + & > a { + display: flex; + flex-flow: row nowrap; + align-items: center; + + span { + font-weight: bold; + margin-left: 0.5em; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + } + } + + & > ul { + font-weight: lighter; + font-size: 0.75em; + list-style: none; + text-align: right; + } + } +} diff --git a/conversejs/custom/plugins/mam-search/templates/muc-mam-search-app.js b/conversejs/custom/plugins/mam-search/templates/muc-mam-search-app.js new file mode 100644 index 00000000..8e3a9e10 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/templates/muc-mam-search-app.js @@ -0,0 +1,60 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { converseLocalizedHelpUrl } from '../../../shared/lib/help' +import { tplMUCApp } from '../../../shared/components/muc-app/templates/muc-app.js' +import { html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { __ } from 'i18n' + +function tplContent (el, mucModel, occupantModel) { + return html` + ${ + occupantModel + ? html` + + ` + : '' + } + + ${ + el.results + ? repeat(el.results, (message) => message.id, message => { + return html`` + }) + : html`` + } + ` +} + +export function tplMamSearchApp (el, mucModel, occupantModel) { + if (!mucModel) { + // should not happen + return html`` + } + + if (!el.show) { + return html`` + } + + // eslint-disable-next-line no-undef + const i18nSearch = __(LOC_message_search) + // eslint-disable-next-line no-undef + const i18nHelp = __(LOC_online_help) + const helpUrl = converseLocalizedHelpUrl({ + page: 'documentation/user/streamers/moderation' + }) + + return tplMUCApp( + el, + i18nSearch, + helpUrl, + i18nHelp, + tplContent(el, mucModel, occupantModel) + ) +} diff --git a/conversejs/custom/plugins/mam-search/templates/muc-mam-search-message.js b/conversejs/custom/plugins/mam-search/templates/muc-mam-search-message.js new file mode 100644 index 00000000..5c9596c1 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/templates/muc-mam-search-message.js @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { html } from 'lit' + +/** + * Renders the message as a search result. + * @param el The message element + * @param mucModel The MUC model + * @param searchOccupantModel The model of the occupant for which we are searching + * @param message The message (warning: this is not a model) + * @returns TemplateResult (or equivalent) + */ +export function tplMucMamSearchMessage (el, mucModel, searchOccupantModel, message) { + const occupant = el.getMessageOccupant() + return html` + ${ + occupant + ? html` + ` + : '' + } + + + ${el.getDateTime()}` +} diff --git a/conversejs/custom/plugins/mam-search/templates/muc-mam-search-occupant.js b/conversejs/custom/plugins/mam-search/templates/muc-mam-search-occupant.js new file mode 100644 index 00000000..51c5ef35 --- /dev/null +++ b/conversejs/custom/plugins/mam-search/templates/muc-mam-search-occupant.js @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { html } from 'lit' +import { api } from '@converse/headless' +import { getAuthorStyle } from '../../../../src/utils/color.js' +import { __ } from 'i18n' + +export function tplMucMamSearchOccupant (el, occupant, message) { + const authorStyle = getAuthorStyle(occupant) + const jid = occupant.get('jid') + const occupantId = occupant.get('occupant_id') + + return html` + { + api.modal.show('converse-muc-occupant-modal', { model: occupant }, ev) + }}> + + + ${occupant.getDisplayName()} + + + ${ + // user changed nick: display the original nick + message && message.nick !== undefined && message.nick !== occupant.get('nick') + // eslint-disable-next-line no-undef + ? html`${message.nick}` + : '' + } + ${jid ? html`${jid}` : ''} + ${occupantId ? html`${occupantId}` : ''} + ` +} diff --git a/conversejs/custom/plugins/mam-search/utils.js b/conversejs/custom/plugins/mam-search/utils.js new file mode 100644 index 00000000..59139add --- /dev/null +++ b/conversejs/custom/plugins/mam-search/utils.js @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { api } from '../../../src/headless/index.js' +import { XMLNS_MAM_SEARCH } from './constants.js' +import { __ } from 'i18n' + +function getMessageActionButtons (messageActionsEl, buttons) { + const messageModel = messageActionsEl.model + if (!api.settings.get('livechat_mam_search_app_enabled')) { + return buttons + } + + if (messageModel.get('type') !== 'groupchat') { + // only on groupchat message. + return buttons + } + + if (!messageModel.occupant) { + return buttons + } + + const muc = messageModel.collection?.chatbox + if (!muc) { + return buttons + } + + if (!muc.features?.get?.(XMLNS_MAM_SEARCH)) { + return buttons + } + + const myself = muc.getOwnOccupant() + if (!myself || !['admin', 'owner'].includes(myself.get('affiliation'))) { + return buttons + } + + // eslint-disable-next-line no-undef + const i18nSearch = __(LOC_search_occupant_message) + + buttons.push({ + i18n_text: i18nSearch, + handler: async (ev) => { + ev.preventDefault() + api.livechat_mam_search.showMessagesFrom(messageModel.occupant) + }, + button_class: '', + icon_class: 'fa fa-magnifying-glass', + name: 'muc-mam-search' + }) + + return buttons +} + +function getOccupantActionButtons (occupant, buttons) { + if (!api.settings.get('livechat_mam_search_app_enabled')) { + return buttons + } + + const muc = occupant.collection?.chatroom + if (!muc) { + return buttons + } + + if (!muc.features?.get?.(XMLNS_MAM_SEARCH)) { + return buttons + } + + const myself = muc.getOwnOccupant() + if (!myself || !['admin', 'owner'].includes(myself.get('affiliation'))) { + return buttons + } + + // eslint-disable-next-line no-undef + const i18nSearch = __(LOC_search_occupant_message) + + buttons.push({ + i18n_text: i18nSearch, + handler: async (ev) => { + ev.preventDefault() + api.livechat_mam_search.showMessagesFrom(occupant) + }, + button_class: '', + icon_class: 'fa fa-magnifying-glass', + name: 'muc-mam-search' + }) + + return buttons +} + +export { + getMessageActionButtons, + getOccupantActionButtons +} diff --git a/conversejs/custom/plugins/notes/api.js b/conversejs/custom/plugins/notes/api.js new file mode 100644 index 00000000..14d92260 --- /dev/null +++ b/conversejs/custom/plugins/notes/api.js @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +async function openNotes () { + const appElement = document.querySelector('livechat-converse-muc-note-app') + if (!appElement) { + throw new Error('Cant find Note App Element') + } + await appElement.showApp() + await appElement.updateComplete // waiting for the app to be open + + const notesElement = appElement.querySelector('livechat-converse-muc-notes') + if (!notesElement) { + throw new Error('Cant find Notes Element') + } + await notesElement.updateComplete + return notesElement +} + +async function openCreateNoteForm (occupant) { + const notesElement = await openNotes() + await notesElement.openCreateNoteForm(undefined, occupant) +} + +async function searchNotesAbout (occupant) { + const notesElement = await openNotes() + await notesElement.filterNotes({ occupant }) +} + +export default { + openNotes, + openCreateNoteForm, + searchNotesAbout +} diff --git a/conversejs/custom/plugins/notes/components/muc-note-app-view.js b/conversejs/custom/plugins/notes/components/muc-note-app-view.js new file mode 100644 index 00000000..4747fab0 --- /dev/null +++ b/conversejs/custom/plugins/notes/components/muc-note-app-view.js @@ -0,0 +1,21 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { api } from '@converse/headless' +import { MUCApp } from '../../../shared/components/muc-app/index.js' +import { tplMUCNoteApp } from '../templates/muc-note-app.js' + +/** + * Custom Element to display the Notes Application. + */ +export default class MUCNoteApp extends MUCApp { + restoreSettingName = 'livechat_note_app_restore' + sessionStorageRestoreKey = 'livechat-converse-note-app-show' + + render () { + return tplMUCNoteApp(this, this.model) + } +} + +api.elements.define('livechat-converse-muc-note-app', MUCNoteApp) diff --git a/conversejs/custom/plugins/notes/components/muc-note-occupant-view.js b/conversejs/custom/plugins/notes/components/muc-note-occupant-view.js new file mode 100644 index 00000000..f7121f5f --- /dev/null +++ b/conversejs/custom/plugins/notes/components/muc-note-occupant-view.js @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { CustomElement } from 'shared/components/element.js' +import { tplMucNoteOccupant } from '../templates/muc-note-occupant' +import { api } from '@converse/headless' + +import '../styles/muc-note-occupant.scss' + +export default class MUCNoteOccupantView extends CustomElement { + static get properties () { + return { + model: { type: Object, attribute: true }, + note: { type: Object, attribute: true }, // optional associated note + full_display: { type: Boolean, attribute: true } + } + } + + async initialize () { + this.listenTo(this.model, 'change', () => this.requestUpdate()) + } + + render () { + return tplMucNoteOccupant(this, this.model, this.note) + } +} + +api.elements.define('livechat-converse-muc-note-occupant', MUCNoteOccupantView) diff --git a/conversejs/custom/plugins/notes/components/muc-note-view.js b/conversejs/custom/plugins/notes/components/muc-note-view.js new file mode 100644 index 00000000..260b31e3 --- /dev/null +++ b/conversejs/custom/plugins/notes/components/muc-note-view.js @@ -0,0 +1,110 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { CustomElement } from 'shared/components/element.js' +import { api } from '@converse/headless' +import { tplMucNote } from '../templates/muc-note' +import { __ } from 'i18n' + +import '../styles/muc-note.scss' + +export default class MUCNoteView extends CustomElement { + static get properties () { + return { + model: { type: Object, attribute: true }, + edit: { type: Boolean, attribute: false }, + is_ocupant_filter: { type: Boolean, attribute: true } + } + } + + async initialize () { + this.edit = false + if (!this.model) { + return + } + + this.listenTo(this.model, 'change', () => this.requestUpdate()) + } + + render () { + return tplMucNote(this, this.model) + } + + shouldUpdate (changedProperties) { + if (!super.shouldUpdate(...arguments)) { return false } + // When a note is currently edited, and another users change the order, + // it could refresh losing the current form. + // To avoid this, we cancel update here. + // Note: of course, if 'edit' is part of the edited properties, we must update anyway + // (it means we just leaved the form) + if (this.edit && !changedProperties.has('edit')) { + console.info('Canceling an update on note, because it is currently edited', this) + return false + } + return true + } + + async saveNote (ev) { + ev?.preventDefault?.() + + const description = ev.target.description.value + + if ((description ?? '') === '') { return } + + try { + this.querySelectorAll('input[type=submit]').forEach(el => { + el.setAttribute('disabled', true) + el.classList.add('disabled') + }) + + const note = this.model + note.set('description', description) + await note.saveItem() + + this.edit = false + this.requestUpdate() // In case we cancel another update in shouldUpdate + } catch (err) { + console.error(err) + } finally { + this.querySelectorAll('input[type=submit]').forEach(el => { + el.removeAttribute('disabled') + el.classList.remove('disabled') + }) + } + } + + async deleteNote (ev) { + ev?.preventDefault?.() + + // eslint-disable-next-line no-undef + const i18nConfirmDelete = __(LOC_moderator_note_delete_confirm) + + const result = await api.confirm(i18nConfirmDelete) + if (!result) { return } + + try { + await this.model.deleteItem() + } catch (err) { + api.alert( + 'error', __('Error'), [__('Error')] + ) + } + } + + async toggleEdit () { + this.edit = !this.edit + if (this.edit) { + await this.updateComplete + const textarea = this.querySelector('textarea[name="description"]') + if (textarea) { + textarea.focus() + // Placing cursor at the end: + textarea.selectionStart = textarea.value.length + textarea.selectionEnd = textarea.selectionStart + } + } + } +} + +api.elements.define('livechat-converse-muc-note', MUCNoteView) diff --git a/conversejs/custom/plugins/notes/components/muc-notes-view.js b/conversejs/custom/plugins/notes/components/muc-notes-view.js new file mode 100644 index 00000000..c393f1bc --- /dev/null +++ b/conversejs/custom/plugins/notes/components/muc-notes-view.js @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { api } from '@converse/headless' +import tplMucNotes from '../templates/muc-notes' +import { __ } from 'i18n' +import { DraggablesCustomElement } from '../../../shared/components/draggables/index.js' + +import '../styles/muc-notes.scss' + +export default class MUCNotesView extends DraggablesCustomElement { + static get properties () { + return { + model: { type: Object, attribute: true }, + create_note_error_message: { type: String, attribute: false }, + create_note_opened: { type: Boolean, attribute: false }, + create_note_about_occupant: { type: Object, attribute: false }, + occupant_filter: { type: Object, attribute: false } + } + } + + async initialize () { + this.create_note_error_message = '' + + if (!this.model) { + return + } + + this.draggableTagName = 'livechat-converse-muc-note' + this.droppableTagNames = ['livechat-converse-muc-note'] + this.droppableAlwaysBottomTagNames = [] + + // Adding or removing a new note: we must update. + this.listenTo(this.model, 'add', () => this.requestUpdate()) + this.listenTo(this.model, 'remove', () => this.requestUpdate()) + this.listenTo(this.model, 'sort', () => this.requestUpdate()) + + await super.initialize() + } + + render () { + return tplMucNotes(this, this.model) + } + + async openCreateNoteForm (ev, occupant) { + ev?.preventDefault?.() + this.create_note_opened = true + this.create_note_about_occupant = occupant ?? undefined + if (this.create_note_about_occupant === undefined && this.occupant_filter) { + // if we have a current filter, we can use it for the new note. + this.create_note_about_occupant = this.occupant_filter + } + await this.updateComplete + const textarea = this.querySelector('.notes-create-note textarea[name="description"]') + if (textarea) { + textarea.focus() + } + } + + closeCreateNoteForm (ev) { + ev?.preventDefault?.() + this.create_note_opened = false + this.create_note_about_occupant = undefined + } + + filterNotes (filters) { + this.occupant_filter = filters?.occupant || undefined + } + + async submitCreateNote (ev) { + ev.preventDefault() + + const description = ev.target.description.value + if (this.create_note_error_message) { + this.create_note_error_message = '' + } + + if ((description ?? '') === '') { return } + + try { + this.querySelectorAll('input[type=submit]').forEach(el => { + el.setAttribute('disabled', true) + el.classList.add('disabled') + }) + + await this.model.createNote({ + description: description, + about_jid: ev.target.about_jid?.value || undefined, + about_nick: ev.target.about_nick?.value || undefined, + about_occupant_id: ev.target.about_occupant_id?.value || undefined + }) + + this.closeCreateNoteForm() + } catch (err) { + console.error(err) + // eslint-disable-next-line no-undef + this.create_note_error_message = __(LOC_moderator_notes_create_error) + } finally { + this.querySelectorAll('input[type=submit]').forEach(el => { + el.removeAttribute('disabled') + el.classList.remove('disabled') + }) + } + } + + _dropDone (draggedEl, droppedOnEl, onTopHalf) { + super._dropDone(...arguments) + console.log('[livechat note drag&drop] Note dropped...') + + const note = draggedEl.model + if (!note) { + throw new Error('No model for the draggedEl') + } + const targetNote = droppedOnEl.model + if (!targetNote) { + throw new Error('No model for the droppedOnEl') + } + if (note === targetNote) { + console.log('[livechat note drag&drop] Note dropped on itself, nothing to do') + return + } + + let newOrder = targetNote.get('order') ?? 0 + if (onTopHalf) { newOrder = Math.max(0, newOrder + 1) } // reverse order! + + // Warning: the order of the collection is reversed! + // _saveOrders needs it in ascending order! + this._saveOrders(Array.from(this.model).reverse(), note, newOrder) + } +} + +api.elements.define('livechat-converse-muc-notes', MUCNotesView) diff --git a/conversejs/custom/plugins/notes/constants.js b/conversejs/custom/plugins/notes/constants.js new file mode 100644 index 00000000..baefcc00 --- /dev/null +++ b/conversejs/custom/plugins/notes/constants.js @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +export const XMLNS_NOTE = 'urn:peertube-plugin-livechat:note' diff --git a/conversejs/custom/plugins/notes/index.js b/conversejs/custom/plugins/notes/index.js new file mode 100644 index 00000000..d20c805f --- /dev/null +++ b/conversejs/custom/plugins/notes/index.js @@ -0,0 +1,69 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { _converse, converse } from '../../../src/headless/index.js' +import { XMLNS_NOTE } from './constants.js' +import { ChatRoomNote } from './note.js' +import { ChatRoomNotes } from './notes.js' +import { + initOrDestroyChatRoomNotes, getHeadingButtons, getMessageActionButtons, getOccupantActionButtons +} from './utils.js' +import notesApi from './api.js' + +import './components/muc-note-app-view.js' +import './components/muc-notes-view.js' +import './components/muc-note-view.js' +import './components/muc-note-occupant-view.js' + +converse.plugins.add('livechat-converse-notes', { + dependencies: ['converse-muc', 'converse-disco', 'converse-pubsub'], + + initialize () { + Object.assign( + _converse.exports, + { + ChatRoomNotes, + ChatRoomNote + } + ) + + _converse.api.settings.extend({ + livechat_note_app_enabled: false, + livechat_note_app_restore: false // should we open the app by default if it was previously oppened? + }) + + Object.assign(_converse.api, { + livechat_notes: notesApi + }) + + _converse.api.listen.on('chatRoomInitialized', muc => { + muc.session.on('change:connection_status', _session => { + // When joining a room, initializing the Notes object (if user has access), + // When disconnected from a room, destroying the Notes object: + initOrDestroyChatRoomNotes(muc) + }) + + // When the current user affiliation changes, we must also delete or initialize the TaskLists object: + muc.occupants.on('change:affiliation', occupant => { + if (occupant.get('jid') !== _converse.bare_jid) { // only for myself + return + } + initOrDestroyChatRoomNotes(muc) + }) + + // To be sure that everything works in any case, we also must listen for addition in muc.features. + muc.features.on('change:' + XMLNS_NOTE, () => { + initOrDestroyChatRoomNotes(muc) + }) + }) + + // adding the "Notes" button in the MUC heading buttons: + _converse.api.listen.on('getHeadingButtons', getHeadingButtons) + + // Adding buttons on messages: + _converse.api.listen.on('getMessageActionButtons', getMessageActionButtons) + // Adding buttons on occupants: + _converse.api.listen.on('getOccupantActionButtons', getOccupantActionButtons) + } +}) diff --git a/conversejs/custom/plugins/notes/note-pubsub-manager.js b/conversejs/custom/plugins/notes/note-pubsub-manager.js new file mode 100644 index 00000000..3b0dbece --- /dev/null +++ b/conversejs/custom/plugins/notes/note-pubsub-manager.js @@ -0,0 +1,51 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { PubSubManager } from '../../shared/lib/pubsub-manager.js' + +export class NotePubSubManager extends PubSubManager { + _additionalModelToData (item, data) { + super._additionalModelToData(item, data) + + data.about_jid = item.get('about_jid') + data.about_occupant_id = item.get('about_occupant_id') + data.about_nick = item.get('about_nick') + } + + _additionalDataToItemNode (data, item) { + super._additionalDataToItemNode(data, item) + + const aboutAttributes = {} + if (data.about_jid !== undefined) { + aboutAttributes.jid = data.about_jid + } + if (data.about_nick !== undefined) { + aboutAttributes.nick = data.about_nick + } + const occupantId = data.about_occupant_id + + if (occupantId !== undefined || Object.values(aboutAttributes).length) { + item.c('note-about', aboutAttributes) + if (occupantId) { + item.c('occupant-id', { xmlns: 'urn:xmpp:occupant-id:0', id: occupantId }).up() + } + item.up() + } + } + + _additionalParseItemNode (itemNode, type, data) { + super._additionalParseItemNode(itemNode, type, data) + + const about = itemNode.querySelector('& > note-about') + if (!about) { return } + + data.about_jid = about.getAttribute('jid') + data.about_nick = about.getAttribute('nick') + + const occupantIdEl = about.querySelector('& > occupant-id') + if (occupantIdEl) { + data.about_occupant_id = occupantIdEl.getAttribute('id') + } + } +} diff --git a/conversejs/custom/plugins/notes/note.js b/conversejs/custom/plugins/notes/note.js new file mode 100644 index 00000000..482ecca1 --- /dev/null +++ b/conversejs/custom/plugins/notes/note.js @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { Model } from '@converse/skeletor/src/model.js' + +/** + * A chat room note. + * @class + * @namespace _converse.exports.ChatRoomNote + * @memberof _converse + */ +class ChatRoomNote extends Model { + idAttribute = 'id' + _aboutOccupantCache = null + _aboutOccupantCacheFor = null + + async saveItem () { + console.log('Saving note ' + this.get('id') + '...') + await this.collection.chatroom.noteManager.saveItem(this) + console.log('Note ' + this.get('id') + ' saved.') + } + + async deleteItem () { + return this.collection.chatroom.noteManager.deleteItems([this]) + } + + getAboutOccupant () { + const occupants = this.collection.chatroom?.occupants + if (!occupants?.findOccupant) { return undefined } + + const nick = this.get('about_nick') + const jid = this.get('about_jid') + const occupantId = this.get('about_occupant_id') + + if (!nick && !jid && !occupantId) { + this._aboutOccupantCache = null + this._aboutOccupantCacheFor = null + return undefined + } + + // Keeping some cache, to avoid intensive search on each rendering. + const cacheKey = `${occupantId ?? ''} ${jid ?? ''} ${nick ?? ''}` + if (this._aboutOccupantCacheFor === cacheKey && this._aboutOccupantCache) { + return this._aboutOccupantCache + } + + this._aboutOccupantCacheFor = cacheKey + + if (occupantId) { + const o = occupants.findOccupant({ occupant_id: occupantId }) + if (o) { + this._aboutOccupantCache = o + return o + } + } + + if (jid) { + const o = occupants.findOccupant({ + jid, + nick + }) + if (o) { + this._aboutOccupantCache = o + return o + } + } + + // If we don't find it, maybe it is a user that has spoken a long time ago (or never spoked). + // In such case, we must create a dummy occupant: + this._aboutOccupantCache = occupants.create({ + nick, + occupant_id: occupantId, + jid + }) + return this._aboutOccupantCache + } +} + +export { + ChatRoomNote +} diff --git a/conversejs/custom/plugins/notes/notes.js b/conversejs/custom/plugins/notes/notes.js new file mode 100644 index 00000000..f957fd36 --- /dev/null +++ b/conversejs/custom/plugins/notes/notes.js @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { Collection } from '@converse/skeletor/src/collection.js' +import { ChatRoomNote } from './note' +import { initStorage } from '@converse/headless/utils/storage.js' + +/** + * A list of {@link _converse.exports.ChatRoomNote} instances, representing notes associated to a MUC. + * @class + * @namespace _converse.exports.ChatRoomNotes + * @memberOf _converse + */ +class ChatRoomNotes extends Collection { + model = ChatRoomNote + + initialize (models, options) { + this.model = ChatRoomNote // don't know why, must do it again here + super.initialize(arguments) + this.chatroom = options.chatroom + + const id = `converse-livechat-notes-${this.chatroom.get('jid')}` + initStorage(this, id, 'session') + + this.on('change:order', () => this.sort()) + } + + comparator (n1, n2) { + // must reverse order + const o1 = n1.get('order') ?? 0 + const o2 = n2.get('order') ?? 0 + return o1 < o2 ? 1 : o1 > o2 ? -1 : 0 + } + + async createNote (data) { + data = Object.assign({}, data) + + if (!data.order) { + data.order = 1 + Math.max( + 0, + ...(this.map(n => n.get('order') ?? 0).filter(o => !isNaN(o))) + ) + } + + console.log('Creating note...') + await this.chatroom.noteManager.createItem(this, data) + console.log('Note created.') + } +} + +export { + ChatRoomNotes +} diff --git a/conversejs/custom/plugins/notes/styles/muc-note-occupant.scss b/conversejs/custom/plugins/notes/styles/muc-note-occupant.scss new file mode 100644 index 00000000..185caa3d --- /dev/null +++ b/conversejs/custom/plugins/notes/styles/muc-note-occupant.scss @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2024 John Livingston + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +.conversejs { + livechat-converse-muc-note-occupant { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + padding: 0.25em; + + & > a { + display: flex; + flex-flow: row nowrap; + align-items: center; + + span { + font-weight: bold; + margin-left: 0.5em; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + } + } + + & > ul { + font-weight: lighter; + font-size: 0.75em; + list-style: none; + text-align: right; + } + } +} diff --git a/conversejs/custom/plugins/notes/styles/muc-note.scss b/conversejs/custom/plugins/notes/styles/muc-note.scss new file mode 100644 index 00000000..d0dc986a --- /dev/null +++ b/conversejs/custom/plugins/notes/styles/muc-note.scss @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2024 John Livingston + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +.conversejs { + livechat-converse-muc-note { + padding: 0; + width: 100%; + + .note-line { + border: 1px solid var(--chatroom-head-bg-color); + border-radius: 4px; + display: flex; + flex-flow: row nowrap; + justify-content: space-around; + margin: 0.25em 0; + padding: 0.25em; + column-gap: 0.25em; + width: 100%; + + .note-content { + flex-grow: 2; + } + + .note-description { + white-space: pre-wrap; + } + + .note-action { + background: unset; + border: 0; + padding-left: 0.25em; + padding-right: 0.25em; + } + + form { + width: 100%; + } + } + } +} diff --git a/conversejs/custom/plugins/notes/styles/muc-notes.scss b/conversejs/custom/plugins/notes/styles/muc-notes.scss new file mode 100644 index 00000000..2dedc47b --- /dev/null +++ b/conversejs/custom/plugins/notes/styles/muc-notes.scss @@ -0,0 +1,38 @@ +/* + * SPDX-FileCopyrightText: 2024 John Livingston + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +.conversejs { + .notes-actions { + display: flex; + flex-flow: row nowrap; + justify-content: right; + width: 100%; + } + + .notes-action { + background: unset; + border: 0; + padding-left: 0.25em; + padding-right: 0.25em; + } + + .notes-filters { + border: 1px solid var(--chatroom-head-bg-color); + border-radius: 4px; + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; + margin: 0.25em 0; + padding: 0.25em; + column-gap: 0.25em; + width: 100%; + + livechat-converse-muc-note-occupant { + flex-grow: 2; + } + } +} diff --git a/conversejs/custom/plugins/notes/templates/muc-note-app.js b/conversejs/custom/plugins/notes/templates/muc-note-app.js new file mode 100644 index 00000000..e63f6e24 --- /dev/null +++ b/conversejs/custom/plugins/notes/templates/muc-note-app.js @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { converseLocalizedHelpUrl } from '../../../shared/lib/help' +import { tplMUCApp } from '../../../shared/components/muc-app/templates/muc-app.js' +import { html } from 'lit' +import { __ } from 'i18n' + +export function tplMUCNoteApp (el, mucModel) { + if (!mucModel) { + // should not happen + return html`` + } + if (!mucModel.notes) { + // too soon, not initialized yet (this will happen) + return html`` + } + + if (!el.show) { + return html`` + } + + // eslint-disable-next-line no-undef + const i18nNotes = __(LOC_moderator_notes) + // eslint-disable-next-line no-undef + const i18nHelp = __(LOC_online_help) + const helpUrl = converseLocalizedHelpUrl({ + page: 'documentation/user/streamers/moderation_notes' + }) + + return tplMUCApp( + el, + i18nNotes, + helpUrl, + i18nHelp, + html`` + ) +} diff --git a/conversejs/custom/plugins/notes/templates/muc-note-occupant.js b/conversejs/custom/plugins/notes/templates/muc-note-occupant.js new file mode 100644 index 00000000..72c50ff3 --- /dev/null +++ b/conversejs/custom/plugins/notes/templates/muc-note-occupant.js @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { html } from 'lit' +import { api } from '@converse/headless' +import { getAuthorStyle } from '../../../../src/utils/color.js' +import { __ } from 'i18n' + +export function tplMucNoteOccupant (el, occupant, note) { + const authorStyle = getAuthorStyle(occupant) + const jid = occupant.get('jid') + const occupantId = occupant.get('occupant_id') + + return html` + { + api.modal.show('converse-muc-occupant-modal', { model: occupant }, ev) + }}> + + + ${occupant.getDisplayName()} + + ${ + el.full_display + ? html` + ${ + // user changed nick: display the original nick + note && note.get('about_nick') && note.get('about_nick') !== occupant.get('nick') + // eslint-disable-next-line no-undef + ? html`${note.get('about_nick')}` + : '' + } + ${jid ? html`${jid}` : ''} + ${occupantId ? html`${occupantId}` : ''} + ` + : '' + } + ` +} diff --git a/conversejs/custom/plugins/notes/templates/muc-note.js b/conversejs/custom/plugins/notes/templates/muc-note.js new file mode 100644 index 00000000..16815e20 --- /dev/null +++ b/conversejs/custom/plugins/notes/templates/muc-note.js @@ -0,0 +1,130 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { api } from '@converse/headless' +import { html } from 'lit' +import { __ } from 'i18n' + +export function tplMucNote (el, note) { + // eslint-disable-next-line no-undef + const i18nDelete = __(LOC_moderator_note_delete) + // eslint-disable-next-line no-undef + const i18nSearch = __(LOC_moderator_note_search_for_participant) + + const aboutOccupant = note.getAboutOccupant() + + return !el.edit + ? html` + + + ${ + aboutOccupant + ? html` + ` + : '' + } + ${note.get('description') ?? ''} + + ${ + aboutOccupant && el.is_ocupant_filter + ? '' + : html` + { + ev.preventDefault() + api.livechat_notes.searchNotesAbout(aboutOccupant) + }}> + + ` + } + + + + + + + ` + : html` + + + ${ + aboutOccupant + ? html` + + ` + : '' + } + ${_tplNoteForm(note)} + + + + + + ` +} + +function _tplNoteForm (note) { + // eslint-disable-next-line no-undef + const i18nNoteDesc = __(LOC_moderator_note_description) + + return html` + ${note ? note.get('description') : ''} + ` +} + +function _tplNoteOccupantFormFields (occupant) { + if (!occupant) { return '' } + return html` + + + + ` +} + +export function tplMucCreateNoteForm (notesEl, occupant) { + const i18nOk = __('Ok') + const i18nCancel = __('Cancel') + + return html` + + ${ + occupant + ? html` + ${_tplNoteOccupantFormFields(occupant)} + + ` + : '' + } + ${_tplNoteForm(undefined)} + + + + ${!notesEl.create_note_error_message + ? '' + : html`${notesEl.create_note_error_message}` + } + + ` +} diff --git a/conversejs/custom/plugins/notes/templates/muc-notes.js b/conversejs/custom/plugins/notes/templates/muc-notes.js new file mode 100644 index 00000000..e488dea2 --- /dev/null +++ b/conversejs/custom/plugins/notes/templates/muc-notes.js @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { html } from 'lit' +import { repeat } from 'lit/directives/repeat.js' +import { __ } from 'i18n' +import { tplMucCreateNoteForm } from './muc-note' + +function tplFilters (el) { + const filterOccupant = el.occupant_filter + if (!filterOccupant) { return '' } + + // eslint-disable-next-line no-undef + const i18nSearch = __(LOC_moderator_note_filters) + + return html` + + + ${ + filterOccupant + ? html`` + : '' + } + { + ev?.preventDefault() + el.filterNotes({}) + }} title="${__('Close')}"> + + + + + ` +} + +function isFiltered (el, note) { + const filterOccupant = el.occupant_filter + if (!filterOccupant) { return false } + + const noteOccupant = note.getAboutOccupant() + // there is an occupant filter, so if current note has no associated occupant, we can pass. + if (!noteOccupant) { return true } + + if (noteOccupant === filterOccupant) { + // Yes! + return false + } + + // We will also test for nickname, so that we can found anonymous users + // (they can have multiple associated occupants) + if (filterOccupant.get('nick') && filterOccupant.get('nick') === noteOccupant.get('nick')) { + return false + } + + return true +} + +export default function tplMucNotes (el, notes) { + if (!notes) { // if user loses rights + return html`` // FIXME: add a message like "you dont have access"? + } + + return html` + ${ + el.create_note_opened ? tplMucCreateNoteForm(el, el.create_note_about_occupant) : tplCreateButton(el) + } + ${tplFilters(el)} + ${ + repeat(notes, (note) => note.get('id'), (note) => { + return isFiltered(el, note) + ? '' + : html`` + }) + }` +} + +function tplCreateButton (el) { + // eslint-disable-next-line no-undef + const i18nCreateNote = __(LOC_moderator_note_create) + return html` + + + + + ` +} diff --git a/conversejs/custom/plugins/notes/utils.js b/conversejs/custom/plugins/notes/utils.js new file mode 100644 index 00000000..3b5fa8a4 --- /dev/null +++ b/conversejs/custom/plugins/notes/utils.js @@ -0,0 +1,195 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { XMLNS_NOTE } from './constants.js' +import { NotePubSubManager } from './note-pubsub-manager.js' +import { converse, _converse, api } from '../../../src/headless/index.js' +import { __ } from 'i18n' + +export function getHeadingButtons (view, buttons) { + const muc = view.model + if (muc.get('type') !== _converse.constants.CHATROOMS_TYPE) { + // only on MUC. + return buttons + } + + if (!muc.notes) { // this is defined only if user has access (see initOrDestroyChatRoomNotes) + return buttons + } + + // Adding a "Open moderator noteds" button. + buttons.unshift({ + // eslint-disable-next-line no-undef + i18n_text: __(LOC_moderator_notes), + handler: async (ev) => { + ev.preventDefault() + // opening or closing the muc notes: + const NoteAppEl = ev.target.closest('converse-root').querySelector('livechat-converse-muc-note-app') + NoteAppEl.toggleApp() + }, + a_class: '', + icon_class: 'fa-note-sticky', + name: 'muc-notes' + }) + + return buttons +} + +export function getMessageActionButtons (messageActionsEl, buttons) { + const messageModel = messageActionsEl.model + if (messageModel.get('type') !== 'groupchat') { + // only on groupchat message. + return buttons + } + + if (!messageModel.occupant) { + return buttons + } + + const muc = messageModel.collection?.chatbox + if (!muc?.notes) { + return buttons + } + + // eslint-disable-next-line no-undef + const i18nCreate = __(LOC_moderator_note_create_for_participant) + // eslint-disable-next-line no-undef + const i18nSearch = __(LOC_moderator_note_search_for_participant) + + buttons.push({ + i18n_text: i18nCreate, + handler: async (ev) => { + ev.preventDefault() + await api.livechat_notes.openCreateNoteForm(messageModel.occupant) + }, + button_class: '', + icon_class: 'fa fa-note-sticky', + name: 'muc-note-create-for-occupant' + }) + + buttons.push({ + i18n_text: i18nSearch, + handler: async (ev) => { + ev.preventDefault() + await api.livechat_notes.searchNotesAbout(messageModel.occupant) + }, + button_class: '', + icon_class: 'fa fa-magnifying-glass', + name: 'muc-note-search-for-occupant' + }) + + return buttons +} + +export function getOccupantActionButtons (occupant, buttons) { + const muc = occupant.collection?.chatroom + if (!muc?.notes) { + // We dont have access. + return buttons + } + + // eslint-disable-next-line no-undef + const i18nCreate = __(LOC_moderator_note_create_for_participant) + // eslint-disable-next-line no-undef + const i18nSearch = __(LOC_moderator_note_search_for_participant) + + buttons.push({ + i18n_text: i18nCreate, + handler: async (ev) => { + ev.preventDefault() + await api.livechat_notes.openCreateNoteForm(occupant) + }, + button_class: '', + icon_class: 'fa fa-note-sticky', + name: 'muc-note-create-for-occupant' + }) + + buttons.push({ + i18n_text: i18nSearch, + handler: async (ev) => { + ev.preventDefault() + await api.livechat_notes.searchNotesAbout(occupant) + }, + button_class: '', + icon_class: 'fa fa-magnifying-glass', + name: 'muc-note-search-for-occupant' + }) + + return buttons +} + +function _initChatRoomNotes (mucModel) { + if (mucModel.noteManager) { + // already initiliazed + return + } + + mucModel.notes = new _converse.exports.ChatRoomNotes(undefined, { chatroom: mucModel }) + + mucModel.noteManager = new NotePubSubManager( + mucModel.get('jid'), + 'livechat-notes', // the node name + { + note: { + itemTag: 'note', + xmlns: XMLNS_NOTE, + collection: mucModel.notes, + fields: { + description: String + }, + attributes: { + order: Number + } + } + } + ) + mucModel.noteManager.start().catch(err => console.log(err)) + + // We must requestUpdate for all message actions, to add the "create note" button. + // FIXME: this should not be done here (but it is simplier for now) + document.querySelectorAll('converse-message-actions').forEach(el => el.requestUpdate()) +} + +function _destroyChatRoomNotes (mucModel) { + if (!mucModel.noteManager) { return } + + mucModel.noteManager.stop().catch(err => console.log(err)) + mucModel.noteManager = undefined + + mucModel.notes = undefined + + // We must requestUpdate for all message actions, to remove the "create note" button. + // FIXME: this should not be done here (but it is simplier for now) + document.querySelectorAll('converse-message-actions').forEach(el => el.requestUpdate()) +} + +export function initOrDestroyChatRoomNotes (mucModel) { + if (mucModel.get('type') !== _converse.constants.CHATROOMS_TYPE) { + // only on MUC. + return _destroyChatRoomNotes(mucModel) + } + + if (!api.settings.get('livechat_note_app_enabled')) { + // Feature disabled, no need to handle notes. + return _destroyChatRoomNotes(mucModel) + } + + if (mucModel.session.get('connection_status') !== converse.ROOMSTATUS.ENTERED) { + return _destroyChatRoomNotes(mucModel) + } + + // We must check disco features + // (if the chat is remote, the server could use a livechat version that does not support this feature) + if (!mucModel.features?.get?.(XMLNS_NOTE)) { + return _destroyChatRoomNotes(mucModel) + } + + const myself = mucModel.getOwnOccupant() + if (!myself || !['admin', 'owner'].includes(myself.get('affiliation'))) { + // User must be admin or owner + return _destroyChatRoomNotes(mucModel) + } + + return _initChatRoomNotes(mucModel) +} diff --git a/conversejs/custom/plugins/poll/components/poll-form-view.js b/conversejs/custom/plugins/poll/components/poll-form-view.js index a0fba934..9182b847 100644 --- a/conversejs/custom/plugins/poll/components/poll-form-view.js +++ b/conversejs/custom/plugins/poll/components/poll-form-view.js @@ -4,7 +4,7 @@ import { XMLNS_POLL } from '../constants.js' import { tplPollForm } from '../templates/poll-form.js' import { CustomElement } from 'shared/components/element.js' -import { converse, api } from '@converse/headless/core' +import { converse, api, parsers } from '@converse/headless' import { webForm2xForm } from '@converse/headless/utils/form' import { __ } from 'i18n' import '../styles/poll-form.scss' @@ -18,7 +18,6 @@ export default class MUCPollFormView extends CustomElement { return { model: { type: Object, attribute: true }, modal: { type: Object, attribute: true }, - form_fields: { type: Object, attribute: false }, alert_message: { type: Object, attribute: false }, title: { type: String, attribute: false }, instructions: { type: String, attribute: false } @@ -27,6 +26,8 @@ export default class MUCPollFormView extends CustomElement { _fieldTranslationMap = new Map() + xform = undefined + async initialize () { this.alert_message = undefined if (!this.model) { @@ -36,20 +37,18 @@ export default class MUCPollFormView extends CustomElement { try { this._initFieldTranslations() const stanza = await this._fetchPollForm() - const query = stanza.querySelector('query') - const xform = sizzle(`x[xmlns="${Strophe.NS.XFORM}"]`, query)[0] + const xform = parsers.parseXForm(stanza) if (!xform) { throw Error('Missing xform in stanza') } + xform.fields?.map(f => this._translateField(f)) + this.xform = xform + // eslint-disable-next-line no-undef this.title = __(LOC_poll_title) // xform.querySelector('title')?.textContent ?? '' // eslint-disable-next-line no-undef this.instructions = __(LOC_poll_instructions) // xform.querySelector('instructions')?.textContent ?? '' - this.form_fields = Array.from(xform.querySelectorAll('field')).map(field => { - this._translateField(field) - return u.xForm2TemplateResult(field, stanza) - }) } catch (err) { console.error(err) this.alert_message = __('Error') @@ -86,10 +85,10 @@ export default class MUCPollFormView extends CustomElement { } _translateField (field) { - const v = field.getAttribute('var') + const v = field.var const label = this._fieldTranslationMap.get(v) if (label) { - field.setAttribute('label', label) + field.label = label } } @@ -114,7 +113,7 @@ export default class MUCPollFormView extends CustomElement { await api.sendIQ(iq) if (this.modal) { - this.modal.onHide() + this.modal.close() } } catch (err) { if (u.isErrorStanza(err)) { diff --git a/conversejs/custom/plugins/poll/components/poll-view.js b/conversejs/custom/plugins/poll/components/poll-view.js index 9973aa99..a51c5d02 100644 --- a/conversejs/custom/plugins/poll/components/poll-view.js +++ b/conversejs/custom/plugins/poll/components/poll-view.js @@ -4,7 +4,7 @@ import { tplPoll } from '../templates/poll.js' import { CustomElement } from 'shared/components/element.js' -import { converse, _converse, api } from '@converse/headless/core' +import { converse, _converse, api } from '@converse/headless' import '../styles/poll.scss' export default class MUCPollView extends CustomElement { diff --git a/conversejs/custom/plugins/poll/index.js b/conversejs/custom/plugins/poll/index.js index 6e08b5c8..0aca42f7 100644 --- a/conversejs/custom/plugins/poll/index.js +++ b/conversejs/custom/plugins/poll/index.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { _converse, converse } from '../../../src/headless/core.js' +import { _converse, converse } from '../../../src/headless/index.js' import { getHeadingButtons } from './utils.js' import { POLL_MESSAGE_TAG, POLL_QUESTION_TAG, POLL_CHOICE_TAG } from './constants.js' import { __ } from 'i18n' diff --git a/conversejs/custom/plugins/poll/modals/poll-form.js b/conversejs/custom/plugins/poll/modals/poll-form.js index f56bb9c3..d9746289 100644 --- a/conversejs/custom/plugins/poll/modals/poll-form.js +++ b/conversejs/custom/plugins/poll/modals/poll-form.js @@ -4,7 +4,7 @@ import { __ } from 'i18n' import BaseModal from 'plugins/modal/modal.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import { modal_close_button as ModalCloseButton } from 'plugins/modal/templates/buttons.js' import { html } from 'lit' @@ -13,8 +13,8 @@ class PollFormModal extends BaseModal { super.initialize() } - onHide () { - super.onHide() + close () { + super.close() api.modal.remove('livechat-converse-poll-form-modal') } diff --git a/conversejs/custom/plugins/poll/templates/poll-form.js b/conversejs/custom/plugins/poll/templates/poll-form.js index 4e831171..1009b254 100644 --- a/conversejs/custom/plugins/poll/templates/poll-form.js +++ b/conversejs/custom/plugins/poll/templates/poll-form.js @@ -5,6 +5,10 @@ import { converseLocalizedHelpUrl } from '../../../shared/lib/help' import { html } from 'lit' import { __ } from 'i18n' +import { converse } from '@converse/headless' + +const u = converse.env.utils + export function tplPollForm (el) { const i18nOk = __('Ok') // eslint-disable-next-line no-undef @@ -13,10 +17,18 @@ export function tplPollForm (el) { page: 'documentation/user/streamers/polls' }) + let formFieldTemplates + if (el.xform) { + const fields = el.xform.fields + formFieldTemplates = fields.map(field => { + return u.xFormField2TemplateResult(field) + }) + } + return html` ${el.alert_message ? html`${el.alert_message}` : ''} ${ - el.form_fields + formFieldTemplates ? html` el.formSubmit(ev)}> @@ -30,9 +42,9 @@ export function tplPollForm (el) { ${el.instructions} - ${el.form_fields} + ${formFieldTemplates} - + ` diff --git a/conversejs/custom/plugins/poll/templates/poll.js b/conversejs/custom/plugins/poll/templates/poll.js index f1b61144..152a0f32 100644 --- a/conversejs/custom/plugins/poll/templates/poll.js +++ b/conversejs/custom/plugins/poll/templates/poll.js @@ -63,7 +63,7 @@ function _tplChoice (el, currentPoll, choice, canVote) { @@ -83,21 +83,21 @@ export function tplPoll (el, currentPoll, canVote) { return html` ${currentPoll.over - ? html` + ? html` ` : '' } ${el.collapsed ? html` - + ` : html` - + { + return currentSize + }, + width_is: (sizes) => { + if (!Array.isArray(sizes)) { + sizes = [sizes] + } + if (!currentSize) { return false } + return sizes.includes(currentSize.width) + }, + height_is: (sizes) => { + if (!Array.isArray(sizes)) { + sizes = [sizes] + } + if (!currentSize) { return false } + return sizes.includes(currentSize.height) + } + } + }) _converse.api.listen.on('connected', start) _converse.api.listen.on('reconnected', start) _converse.api.listen.on('disconnected', stop) @@ -42,6 +65,7 @@ function start () { } function stop () { + currentSize = undefined rootResizeObserver.disconnect() const root = document.querySelector('converse-root') if (root) { @@ -60,8 +84,9 @@ function handle (el) { el.setAttribute('livechat-converse-root-width', width) el.setAttribute('livechat-converse-root-height', height) - api.trigger('livechatSizeChanged', { + currentSize = { height: height, width: width - }) + } + api.trigger('livechatSizeChanged', Object.assign({}, currentSize)) // cloning... } diff --git a/conversejs/custom/plugins/tasks/components/muc-task-app-view.js b/conversejs/custom/plugins/tasks/components/muc-task-app-view.js index 155b75a2..d1b8378e 100644 --- a/conversejs/custom/plugins/tasks/components/muc-task-app-view.js +++ b/conversejs/custom/plugins/tasks/components/muc-task-app-view.js @@ -2,36 +2,20 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { api } from '@converse/headless/core' -import { CustomElement } from 'shared/components/element.js' +import { api } from '@converse/headless' +import { MUCApp } from '../../../shared/components/muc-app/index.js' import { tplMUCTaskApp } from '../templates/muc-task-app.js' -import '../styles/muc-task-app.scss' - /** * Custom Element to display the Task Application. */ -export default class MUCTaskApp extends CustomElement { - static get properties () { - return { - model: { type: Object, attribute: true }, // mucModel - show: { type: Boolean, attribute: false } - } - } - - async initialize () { - this.show = api.settings.get('livechat_task_app_restore') && - (window.sessionStorage?.getItem?.('livechat-converse-task-app-show') === '1') - } +export default class MUCTaskApp extends MUCApp { + restoreSettingName = 'livechat_task_app_restore' + sessionStorageRestoreKey = 'livechat-converse-task-app-show' render () { return tplMUCTaskApp(this, this.model) } - - toggleApp () { - this.show = !this.show - window.sessionStorage?.setItem?.('livechat-converse-task-app-show', this.show ? '1' : '') - } } api.elements.define('livechat-converse-muc-task-app', MUCTaskApp) diff --git a/conversejs/custom/plugins/tasks/components/muc-task-list-view.js b/conversejs/custom/plugins/tasks/components/muc-task-list-view.js index 3f97abfd..8688468c 100644 --- a/conversejs/custom/plugins/tasks/components/muc-task-list-view.js +++ b/conversejs/custom/plugins/tasks/components/muc-task-list-view.js @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { CustomElement } from 'shared/components/element.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import tplMucTaskList from '../templates/muc-task-list' import { __ } from 'i18n' diff --git a/conversejs/custom/plugins/tasks/components/muc-task-lists-view.js b/conversejs/custom/plugins/tasks/components/muc-task-lists-view.js index c4429827..a04faa2e 100644 --- a/conversejs/custom/plugins/tasks/components/muc-task-lists-view.js +++ b/conversejs/custom/plugins/tasks/components/muc-task-lists-view.js @@ -2,17 +2,14 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { CustomElement } from 'shared/components/element.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import tplMucTaskLists from '../templates/muc-task-lists' import { __ } from 'i18n' +import { DraggablesCustomElement } from '../../../shared/components/draggables/index.js' import '../styles/muc-task-lists.scss' -import '../styles/muc-task-drag.scss' - -export default class MUCTaskListsView extends CustomElement { - currentDraggedTask = null +export default class MUCTaskListsView extends DraggablesCustomElement { static get properties () { return { model: { type: Object, attribute: true }, @@ -27,42 +24,22 @@ export default class MUCTaskListsView extends CustomElement { return } + this.draggableTagName = 'livechat-converse-muc-task' + this.droppableTagNames = ['livechat-converse-muc-task', 'livechat-converse-muc-task-list'] + this.droppableAlwaysBottomTagNames = ['livechat-converse-muc-task-list'] + // Adding or removing a new task list: we must update. this.listenTo(this.model, 'add', () => this.requestUpdate()) this.listenTo(this.model, 'remove', () => this.requestUpdate()) this.listenTo(this.model, 'sort', () => this.requestUpdate()) - this._handleDragStartBinded = this._handleDragStart.bind(this) - this._handleDragOverBinded = this._handleDragOver.bind(this) - this._handleDragLeaveBinded = this._handleDragLeave.bind(this) - this._handleDragEndBinded = this._handleDragEnd.bind(this) - this._handleDropBinded = this._handleDrop.bind(this) + return super.initialize() } render () { return tplMucTaskLists(this, this.model) } - connectedCallback () { - super.connectedCallback() - this.currentDraggedTask = null - this.addEventListener('dragstart', this._handleDragStartBinded) - this.addEventListener('dragover', this._handleDragOverBinded) - this.addEventListener('dragleave', this._handleDragLeaveBinded) - this.addEventListener('dragend', this._handleDragEndBinded) - this.addEventListener('drop', this._handleDropBinded) - } - - disconnectedCallback () { - super.disconnectedCallback() - this.currentDraggedTask = null - this.removeEventListener('dragstart', this._handleDragStartBinded) - this.removeEventListener('dragover', this._handleDragOverBinded) - this.removeEventListener('dragleave', this._handleDragLeaveBinded) - this.removeEventListener('dragend', this._handleDragEndBinded) - this.removeEventListener('drop', this._handleDropBinded) - } - async submitCreateTaskList (ev) { ev.preventDefault() @@ -96,15 +73,7 @@ export default class MUCTaskListsView extends CustomElement { } } - _getParentTaskEl (target) { - return target.closest?.('livechat-converse-muc-task') - } - - _getParentTaskOrTaskListEl (target) { - return target.closest?.('livechat-converse-muc-task, livechat-converse-muc-task-list') - } - - _isATaskEl (target) { + isATaskEl (target) { return target.nodeName?.toLowerCase() === 'livechat-converse-muc-task' } @@ -112,71 +81,18 @@ export default class MUCTaskListsView extends CustomElement { return target.nodeName?.toLowerCase() === 'livechat-converse-muc-task-list' } - _isOnTopHalf (ev, taskEl) { - const y = ev.clientY - const bounding = taskEl.getBoundingClientRect() - return (y <= bounding.y + (bounding.height / 2)) - } - - _resetDropOver () { - document.querySelectorAll('.livechat-drag-bottom-half, .livechat-drag-top-half').forEach( - el => el.classList.remove('livechat-drag-bottom-half', 'livechat-drag-top-half') - ) - } - - _handleDragStart (ev) { - // The draggable=true is on a livechat-converse-muc-task child - const possibleTaskEl = ev.target.parentElement - if (!this._isATaskEl(possibleTaskEl)) { return } - console.log('[livechat task drag&drop] Starting to drag a task...') - this.currentDraggedTask = possibleTaskEl - this._resetDropOver() - } - - _handleDragOver (ev) { - if (!this.currentDraggedTask) { return } - const taskOrTaskListEl = this._getParentTaskOrTaskListEl(ev.target) - if (!taskOrTaskListEl) { return } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event says we should preventDefault - ev.preventDefault() - - // Are we on the top or bottom part of the taskEl? - // Note: for task list, we always add the task in the task list, so no need to test here. - const topHalf = this._isATaskEl(taskOrTaskListEl) ? this._isOnTopHalf(ev, taskOrTaskListEl) : false - taskOrTaskListEl.classList.add(topHalf ? 'livechat-drag-top-half' : 'livechat-drag-bottom-half') - taskOrTaskListEl.classList.remove(topHalf ? 'livechat-drag-bottom-half' : 'livechat-drag-top-half') - } - - _handleDragLeave (ev) { - if (!this.currentDraggedTask) { return } - const taskOrTaskListEl = this._getParentTaskOrTaskListEl(ev.target) - if (!taskOrTaskListEl) { return } - taskOrTaskListEl.classList.remove('livechat-drag-bottom-half', 'livechat-drag-top-half') - } - - _handleDragEnd (_ev) { - this.currentDraggedTask = null - this._resetDropOver() - } - - _handleDrop (_ev) { - if (!this.currentDraggedTask) { return } - - const droppedOnEl = document.querySelector('.livechat-drag-bottom-half, .livechat-drag-top-half') - const droppedOntaskOrTaskListEl = this._getParentTaskOrTaskListEl(droppedOnEl) - if (!droppedOntaskOrTaskListEl) { return } - + _dropDone (draggedEl, droppedOnEl, onTopHalf) { + super._dropDone(...arguments) console.log('[livechat task drag&drop] Task dropped...') - const task = this.currentDraggedTask.model + const task = draggedEl.model let newOrder, targetTasklist - if (this.isATaskListEl(droppedOntaskOrTaskListEl)) { + if (this.isATaskListEl(droppedOnEl)) { // We dropped on a task list, we must add as first entry. newOrder = 0 - targetTasklist = droppedOntaskOrTaskListEl.model + targetTasklist = droppedOnEl.model if (task.get('list') !== targetTasklist.get('id')) { console.log('[livechat task drag&drop] Changing task list...') task.set('list', targetTasklist.get('id')) @@ -185,9 +101,9 @@ export default class MUCTaskListsView extends CustomElement { console.log('[livechat task drag&drop] Task dropped on tasklist, but already first item, nothing to do') return } - } else if (this._isATaskEl(droppedOntaskOrTaskListEl)) { + } else if (this.isATaskEl(droppedOnEl)) { // We dropped on a task, we must get its order (+1 if !onTopHalf) - const droppedOnTask = droppedOntaskOrTaskListEl.model + const droppedOnTask = droppedOnEl.model if (task === droppedOnTask) { // But of course, if dropped on itself there is nothing to do. console.log('[livechat task drag&drop] Task dropped on itself, nothing to do') @@ -199,9 +115,8 @@ export default class MUCTaskListsView extends CustomElement { task.set('list', droppedOnTask.get('list')) } - const topHalf = droppedOnEl.classList.contains('livechat-drag-top-half') newOrder = droppedOnTask.get('order') ?? 0 - if (!topHalf) { newOrder = Math.max(0, newOrder + 1) } + if (!onTopHalf) { newOrder = Math.max(0, newOrder + 1) } if (typeof newOrder !== 'number' || isNaN(newOrder)) { console.error( @@ -217,45 +132,7 @@ export default class MUCTaskListsView extends CustomElement { return } - if (typeof newOrder !== 'number' || isNaN(newOrder)) { - console.error('[livechat task drag&drop] Computed new order is not a number, aborting.') - return - } - console.log('[livechat task drag&drop] Task new order will be ' + newOrder) - - console.log('[livechat task drag&drop] Reordering tasks...') - let currentOrder = newOrder + 1 - for (const t of targetTasklist.getTasks()) { - if (t === task) { - console.log('[livechat task drag&drop] Skipping the currently moved task') - continue - } - - let order = t.get('order') ?? 0 - if (typeof order !== 'number' || isNaN(order)) { - console.error('[livechat task drag&drop] Found a task with an invalid order, fixing it.') - order = currentOrder // this will cause the code bellow to increment task order - } - if (order < newOrder) { continue } - - currentOrder++ - if (order > currentOrder) { - console.log( - `Task "${t.get('name')}" as already on order greater than ${currentOrder.toString()}, stoping.` - ) - break - } - - console.log(`Changing order of task "${t.get('name')}" to ${currentOrder}`) - t.set('order', currentOrder) - t.saveItem() // TODO: handle errors? - } - - console.log('[livechat task drag&drop] Setting new order on the moved task') - task.set('order', newOrder) - task.saveItem() // TODO: handle errors? - - this._resetDropOver() + this._saveOrders(targetTasklist.getTasks(), task, newOrder) } } diff --git a/conversejs/custom/plugins/tasks/components/muc-task-view.js b/conversejs/custom/plugins/tasks/components/muc-task-view.js index 1d02af85..db7bfdda 100644 --- a/conversejs/custom/plugins/tasks/components/muc-task-view.js +++ b/conversejs/custom/plugins/tasks/components/muc-task-view.js @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { CustomElement } from 'shared/components/element.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import { tplMucTask } from '../templates/muc-task' import { __ } from 'i18n' diff --git a/conversejs/custom/plugins/tasks/index.js b/conversejs/custom/plugins/tasks/index.js index 50b6afd7..8c7b53fb 100644 --- a/conversejs/custom/plugins/tasks/index.js +++ b/conversejs/custom/plugins/tasks/index.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { _converse, converse } from '../../../src/headless/core.js' +import { _converse, converse } from '../../../src/headless/index.js' import { ChatRoomTaskLists } from './task-lists.js' import { ChatRoomTaskList } from './task-list.js' import { ChatRoomTasks } from './tasks.js' @@ -18,9 +18,14 @@ converse.plugins.add('livechat-converse-tasks', { dependencies: ['converse-muc', 'converse-disco', 'converse-pubsub'], initialize () { - _converse.ChatRoomTaskLists = ChatRoomTaskLists - _converse.ChatRoomTaskList = ChatRoomTaskList - _converse.ChatRoomTasks = ChatRoomTasks + Object.assign( + _converse.exports, + { + ChatRoomTaskLists, + ChatRoomTaskList, + ChatRoomTasks + } + ) _converse.api.settings.extend({ livechat_task_app_enabled: false, diff --git a/conversejs/custom/plugins/tasks/modals/pick-task-list.js b/conversejs/custom/plugins/tasks/modals/pick-task-list.js index 7258bd33..1c5e1701 100644 --- a/conversejs/custom/plugins/tasks/modals/pick-task-list.js +++ b/conversejs/custom/plugins/tasks/modals/pick-task-list.js @@ -4,7 +4,7 @@ import BaseModal from 'plugins/modal/modal.js' import tplPickTaskList from './templates/pick-task-list.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import { __ } from 'i18n' export default class PickTaskListModal extends BaseModal { diff --git a/conversejs/custom/plugins/tasks/modals/templates/pick-task-list.js b/conversejs/custom/plugins/tasks/modals/templates/pick-task-list.js index 998fc93a..d872ae16 100644 --- a/conversejs/custom/plugins/tasks/modals/templates/pick-task-list.js +++ b/conversejs/custom/plugins/tasks/modals/templates/pick-task-list.js @@ -19,22 +19,22 @@ export default function (el) { return html` el.onPick(ev)}> - - - ${ - repeat(muc.tasklists, (tasklist) => tasklist.get('id'), (tasklist) => { - return html`${tasklist.get('name')}` - }) - } - - - ${i18nMessage} - - + + + ${ + repeat(muc.tasklists, (tasklist) => tasklist.get('id'), (tasklist) => { + return html`${tasklist.get('name')}` + }) + } + + + ${i18nMessage} + + - - ${__('OK')} - - + + ${__('OK')} + + ` } diff --git a/conversejs/custom/plugins/tasks/styles/muc-task-drag.scss b/conversejs/custom/plugins/tasks/styles/muc-task-drag.scss deleted file mode 100644 index 60904a3d..00000000 --- a/conversejs/custom/plugins/tasks/styles/muc-task-drag.scss +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 John Livingston - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -.conversejs { - livechat-converse-muc-task { - &.livechat-drag-bottom-half .task-line { - border-bottom: 4px solid blue; - } - - &.livechat-drag-top-half .task-line { - border-top: 4px solid blue; - } - } - - livechat-converse-muc-task-list { - &.livechat-drag-bottom-half .task-list-line { - border-bottom: 4px solid blue; - } - - &.livechat-drag-top-half .task-list-line { - border-top: 4px solid blue; - } - } -} diff --git a/conversejs/custom/plugins/tasks/task-list.js b/conversejs/custom/plugins/tasks/task-list.js index e58106de..09564263 100644 --- a/conversejs/custom/plugins/tasks/task-list.js +++ b/conversejs/custom/plugins/tasks/task-list.js @@ -7,7 +7,7 @@ import { Model } from '@converse/skeletor/src/model.js' /** * A chat room task list. * @class - * @namespace _converse.ChatRoomTaskList + * @namespace _converse.exports.ChatRoomTaskList * @memberof _converse */ class ChatRoomTaskList extends Model { @@ -40,7 +40,7 @@ class ChatRoomTaskList extends Model { data.list = this.get('id') if (!data.order) { - data.order = 0 + Math.max( + data.order = 1 + Math.max( 0, ...(this.getTasks().map(t => t.get('order') ?? 0).filter(o => !isNaN(o))) ) diff --git a/conversejs/custom/plugins/tasks/task-lists.js b/conversejs/custom/plugins/tasks/task-lists.js index d74edb81..17130a73 100644 --- a/conversejs/custom/plugins/tasks/task-lists.js +++ b/conversejs/custom/plugins/tasks/task-lists.js @@ -7,9 +7,9 @@ import { ChatRoomTaskList } from './task-list' import { initStorage } from '@converse/headless/utils/storage.js' /** - * A list of {@link _converse.ChatRoomTaskList} instances, representing task lists associated to a MUC. + * A list of {@link _converse.exports.ChatRoomTaskList} instances, representing task lists associated to a MUC. * @class - * @namespace _converse.ChatRoomTaskLists + * @namespace _converse.exports.ChatRoomTaskLists * @memberOf _converse */ class ChatRoomTaskLists extends Collection { diff --git a/conversejs/custom/plugins/tasks/task.js b/conversejs/custom/plugins/tasks/task.js index 7d21b947..9bf9c6f6 100644 --- a/conversejs/custom/plugins/tasks/task.js +++ b/conversejs/custom/plugins/tasks/task.js @@ -7,7 +7,7 @@ import { Model } from '@converse/skeletor/src/model.js' /** * A chat room task. * @class - * @namespace _converse.ChatRoomTask + * @namespace _converse.exports.ChatRoomTask * @memberof _converse */ class ChatRoomTask extends Model { diff --git a/conversejs/custom/plugins/tasks/tasks.js b/conversejs/custom/plugins/tasks/tasks.js index 239a1723..facc16d6 100644 --- a/conversejs/custom/plugins/tasks/tasks.js +++ b/conversejs/custom/plugins/tasks/tasks.js @@ -7,9 +7,9 @@ import { ChatRoomTask } from './task' import { initStorage } from '@converse/headless/utils/storage.js' /** - * A list of {@link _converse.ChatRoomTask} instances, representing all tasks associated to a MUC. + * A list of {@link _converse.exports.ChatRoomTask} instances, representing all tasks associated to a MUC. * @class - * @namespace _converse.ChatRoomTasks + * @namespace _converse.exports.ChatRoomTasks * @memberOf _converse */ class ChatRoomTasks extends Collection { diff --git a/conversejs/custom/plugins/tasks/templates/muc-task-app.js b/conversejs/custom/plugins/tasks/templates/muc-task-app.js index 5dcbd8a3..aeaf68e9 100644 --- a/conversejs/custom/plugins/tasks/templates/muc-task-app.js +++ b/conversejs/custom/plugins/tasks/templates/muc-task-app.js @@ -3,28 +3,24 @@ // SPDX-License-Identifier: AGPL-3.0-only import { converseLocalizedHelpUrl } from '../../../shared/lib/help' +import { tplMUCApp } from '../../../shared/components/muc-app/templates/muc-app.js' import { html } from 'lit' import { __ } from 'i18n' export function tplMUCTaskApp (el, mucModel) { if (!mucModel) { // should not happen - el.classList.add('hidden') // we must do this, otherwise will have CSS side effects return html`` } if (!mucModel.tasklists) { // too soon, not initialized yet (this will happen) - el.classList.add('hidden') // we must do this, otherwise will have CSS side effects return html`` } if (!el.show) { - el.classList.add('hidden') return html`` } - el.classList.remove('hidden') - // eslint-disable-next-line no-undef const i18nTasks = __(LOC_tasks) // eslint-disable-next-line no-undef @@ -33,19 +29,11 @@ export function tplMUCTaskApp (el, mucModel) { page: 'documentation/user/streamers/tasks' }) - return html` - - ${i18nTasks} - - - - - - - - ` + return tplMUCApp( + el, + i18nTasks, + helpUrl, + i18nHelp, + html`` + ) } diff --git a/conversejs/custom/plugins/tasks/templates/muc-task-list.js b/conversejs/custom/plugins/tasks/templates/muc-task-list.js index d226211f..0de8494e 100644 --- a/conversejs/custom/plugins/tasks/templates/muc-task-list.js +++ b/conversejs/custom/plugins/tasks/templates/muc-task-list.js @@ -16,17 +16,17 @@ export default function tplMucTaskList (el, tasklist) { // eslint-disable-next-line no-undef const i18nTaskListName = __(LOC_task_list_name) return html` - + ${el.collapsed ? html` - + ` : html` - + ${tasklist.get('name')} - + - - diff --git a/conversejs/custom/plugins/tasks/templates/muc-task-lists.js b/conversejs/custom/plugins/tasks/templates/muc-task-lists.js index 0c94758f..d875f4c1 100644 --- a/conversejs/custom/plugins/tasks/templates/muc-task-lists.js +++ b/conversejs/custom/plugins/tasks/templates/muc-task-lists.js @@ -24,7 +24,7 @@ export default function tplMucTaskLists (el, tasklists) { }) } - + ${i18nCreateTaskList} @@ -34,6 +34,6 @@ export default function tplMucTaskLists (el, tasklists) { ? '' : html`${el.create_tasklist_error_message}` } - + ` } diff --git a/conversejs/custom/plugins/tasks/templates/muc-task.js b/conversejs/custom/plugins/tasks/templates/muc-task.js index 82f8419f..e003341a 100644 --- a/conversejs/custom/plugins/tasks/templates/muc-task.js +++ b/conversejs/custom/plugins/tasks/templates/muc-task.js @@ -13,7 +13,7 @@ export function tplMucTask (el, task) { const doneId = 'livechat-task-done-id-' + task.get('id') return !el.edit ? html` - + ${task.get('description') ?? ''} - - ` : html` - + ${_tplTaskForm(task)} - + + return html` ${_tplTaskForm(undefined)} - + +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { CustomElement } from 'shared/components/element.js' + +import './styles/draggables.scss' + +/** + * This is the base class for custom elements that contains draggable items. + */ +export class DraggablesCustomElement extends CustomElement { + currentDragged = null + + /** + * The tag name for draggable elements. + * Example: livechat-converse-muc-note. + * Must be set in derived class. + */ + draggableTagName = 'invalid-tag-name' + + /** + * The tag names on which we can drop the element. + * Examples: livechat-converse-muc-note, livechat-converse-muc-task, livechat-converse-muc-task-list. + * Must be set in derived class. + */ + droppableTagNames = [] + + /** + * Tag names for which we will always drop to bottom (for example: task lists) + */ + droppableAlwaysBottomTagNames = [] + + initialize () { + this._handleDragStartBinded = this._handleDragStart.bind(this) + this._handleDragOverBinded = this._handleDragOver.bind(this) + this._handleDragLeaveBinded = this._handleDragLeave.bind(this) + this._handleDragEndBinded = this._handleDragEnd.bind(this) + this._handleDropBinded = this._handleDrop.bind(this) + + return super.initialize() + } + + connectedCallback () { + super.connectedCallback() + this.currentDragged = null + this.addEventListener('dragstart', this._handleDragStartBinded) + this.addEventListener('dragover', this._handleDragOverBinded) + this.addEventListener('dragleave', this._handleDragLeaveBinded) + this.addEventListener('dragend', this._handleDragEndBinded) + this.addEventListener('drop', this._handleDropBinded) + } + + disconnectedCallback () { + super.disconnectedCallback() + this.currentDragged = null + this.removeEventListener('dragstart', this._handleDragStartBinded) + this.removeEventListener('dragover', this._handleDragOverBinded) + this.removeEventListener('dragleave', this._handleDragLeaveBinded) + this.removeEventListener('dragend', this._handleDragEndBinded) + this.removeEventListener('drop', this._handleDropBinded) + } + + _isADraggableEl (target) { + return target.nodeName?.toLowerCase() === this.draggableTagName + } + + _getParentDroppableEl (target) { + return target.closest?.(this.droppableTagNames.join(',')) + } + + _isOnTopHalf (ev, el) { + const y = ev.clientY + const bounding = el.getBoundingClientRect() + return (y <= bounding.y + (bounding.height / 2)) + } + + _resetDropOver () { + document.querySelectorAll('.livechat-drag-bottom-half, .livechat-drag-top-half').forEach( + el => el.classList.remove('livechat-drag-bottom-half', 'livechat-drag-top-half') + ) + } + + _handleDragStart (ev) { + // The draggable=true is on a child bode + const possibleEl = ev.target.parentElement + if (!this._isADraggableEl(possibleEl)) { return } + console.log('[livechat drag&drop] Starting to drag a ' + this.draggableTagName + '...') + this.currentDragged = possibleEl + this._resetDropOver() + } + + _handleDragOver (ev) { + if (!this.currentDragged) { return } + const droppableEl = this._getParentDroppableEl(ev.target) + if (!droppableEl) { return } + + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event says we should preventDefault + ev.preventDefault() + + // Are we on the top or bottom part of the droppableEl? + let topHalf = false + if (!this.droppableAlwaysBottomTagNames.includes(droppableEl.nodeName.toLowerCase())) { + topHalf = this._isOnTopHalf(ev, droppableEl) + } + droppableEl.classList.add(topHalf ? 'livechat-drag-top-half' : 'livechat-drag-bottom-half') + droppableEl.classList.remove(topHalf ? 'livechat-drag-bottom-half' : 'livechat-drag-top-half') + } + + _handleDragLeave (ev) { + if (!this.currentDragged) { return } + const el = this._getParentDroppableEl(ev.target) + if (!el) { return } + el.classList.remove('livechat-drag-bottom-half', 'livechat-drag-top-half') + } + + _handleDragEnd (_ev) { + this.currentDragged = null + this._resetDropOver() + } + + _handleDrop (_ev) { + if (!this.currentDragged) { return } + + let droppedOnEl = document.querySelector('.livechat-drag-bottom-half, .livechat-drag-top-half') + droppedOnEl = this._getParentDroppableEl(droppedOnEl) + if (!droppedOnEl) { return } + + console.log('[livechat drag&drop] ' + this.draggableTagName + ' dropped...') + + try { + this._dropDone(this.currentDragged, droppedOnEl, droppedOnEl.classList.contains('livechat-drag-top-half')) + } catch (err) { + console.error(err) + } + this._resetDropOver() + } + + /** + * The callback when a valid drop occurs. + * Must be overloaded. + */ + _dropDone (draggedEl, droppedOnEl, onTopHalf) { + console.debug('[livechat drag&drop] Drop done:', draggedEl, droppedOnEl, onTopHalf) + } + + /** + * This method can be called from _dropDone to save the new objects orders. + * For it to work, models must respect following constraints: + * * be a Model + * * have the order attribute + * * have an id attribute (for logging) + * * have get, set and saveItem methods + */ + _saveOrders (models, currentModel, newOrder) { + if (typeof newOrder !== 'number' || isNaN(newOrder)) { + console.error('[livechat drag&drop] Computed new order is not a number, aborting.') + return + } + + console.log('[livechat drag&drop] Reordering models... Model new order will be ' + newOrder) + let currentOrder = newOrder + 1 + for (const m of models) { + if (m === currentModel) { + console.log('[livechat drag&drop] Skipping the currently moved model') + continue + } + + let order = m.get('order') ?? 0 + if (typeof order !== 'number' || isNaN(order)) { + console.error('[livechat drag&drop] Found a model with an invalid order, fixing it.') + order = currentOrder // this will cause the code bellow to increment model order + } + if (order < newOrder) { continue } + + currentOrder++ + if (order > currentOrder) { + console.log( + `Object "${m.get('id')}" as already on order greater than ${currentOrder.toString()}, stoping.` + ) + break + } + + console.log(`Changing order of model "${m.get('id')}" to ${currentOrder}`) + m.set('order', currentOrder) + m.saveItem() // TODO: handle errors? + } + + console.log('[livechat drag&drop] Setting new order on the moved model') + currentModel.set('order', newOrder) + currentModel.saveItem() // TODO: handle errors? + } +} diff --git a/conversejs/custom/shared/components/draggables/styles/draggables.scss b/conversejs/custom/shared/components/draggables/styles/draggables.scss new file mode 100644 index 00000000..93279389 --- /dev/null +++ b/conversejs/custom/shared/components/draggables/styles/draggables.scss @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 John Livingston + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +.conversejs { + // FIXME: the use of ">" only works if the draggables-lines is a direct + // child of the element. + // We should find a better way to do this (and that will not break for nested + // elements, like task in tast-list). + .livechat-drag-bottom-half > .draggables-line { + border-bottom: 4px solid blue; + } + + .livechat-drag-top-half > .draggables-line { + border-top: 4px solid blue; + } +} diff --git a/conversejs/custom/shared/components/font-awesome.js b/conversejs/custom/shared/components/font-awesome.js index ce9407aa..18ad7348 100644 --- a/conversejs/custom/shared/components/font-awesome.js +++ b/conversejs/custom/shared/components/font-awesome.js @@ -4,7 +4,7 @@ /* eslint-disable max-len */ import { html } from 'lit' -import tplIcons from '../../../src/shared/templates/icons.js' +import tplIcons from '../../../src/shared/components/templates/icons.js' export default () => { // Here we are adding some additonal icons to ConverseJS defaults @@ -28,6 +28,16 @@ export default () => { + + + + + + + + + + ` } diff --git a/conversejs/custom/shared/components/muc-app/index.js b/conversejs/custom/shared/components/muc-app/index.js new file mode 100644 index 00000000..6c794868 --- /dev/null +++ b/conversejs/custom/shared/components/muc-app/index.js @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { CustomElement } from 'shared/components/element.js' +import { api, _converse } from '@converse/headless' +import './styles/muc-app.scss' + +/** + * Base class for MUC App custom elements (task app, notes app, ...). + * This is an abstract class, should not be called directly. + */ +export class MUCApp extends CustomElement { + restoreSettingName = undefined // must be overloaded + sessionStorageRestoreKey = undefined // must be overloaded + + static get properties () { + return { + model: { type: Object, attribute: true }, // mucModel + show: { type: Boolean, attribute: false } + } + } + + async initialize () { + this.classList.add('livechat-converse-muc-app') + this.show = this.restoreSettingName && + api.settings.get(this.restoreSettingName) && + this.sessionStorageRestoreKey && + (window.sessionStorage?.getItem?.(this.sessionStorageRestoreKey) === '1') + + // we listen for livechatSizeChanged event, + // and close all apps except the first if small or medium width. + // Note: this will also be triggered when we first open the page + this.listenTo(_converse, 'livechatSizeChanged', () => { + if (!this.show || !api.livechat_size?.width_is(['small', 'medium'])) { + return + } + // are we the first opened app? + for (const el of document.querySelectorAll('.livechat-converse-muc-app')) { + if (el === this) { break } + if (!el.show) { continue } + console.debug('The livechat size is small or medium, there is already an opened app, so closing myself', this) + // ok, there is already an opened app. + this.toggleApp() // we know we are open + break + } + }) + } + + render () { // must be overloaded. + return '' + } + + updated () { + if (this.innerText.trim() === '') { + this.classList.add('hidden') // we must do this, otherwise will have CSS side effects + } else { + this.classList.remove('hidden') + } + + super.updated() + } + + toggleApp () { + this.show = !this.show + if (this.sessionStorageRestoreKey) { + window.sessionStorage?.setItem?.(this.sessionStorageRestoreKey, this.show ? '1' : '') + } + + if ( + this.show && + api.livechat_size?.width_is(['small', 'medium']) + ) { + // When showing an App, if the screen width is small or medium, we hide the others. + this._closeOtherApps() + } + } + + showApp () { + if (!this.show) { return this.toggleApp() } + } + + hideApp () { + if (this.show) { return this.toggleApp() } + } + + _closeOtherApps () { + document.querySelectorAll('.livechat-converse-muc-app').forEach((el) => { + if (el !== this && el.show) { + console.debug('Closing another app, because livechat width is small or medium', el) + el.toggleApp() + } + }) + } +} diff --git a/conversejs/custom/plugins/tasks/styles/muc-task-app.scss b/conversejs/custom/shared/components/muc-app/styles/muc-app.scss similarity index 89% rename from conversejs/custom/plugins/tasks/styles/muc-task-app.scss rename to conversejs/custom/shared/components/muc-app/styles/muc-app.scss index e636a57e..59eb23a7 100644 --- a/conversejs/custom/plugins/tasks/styles/muc-task-app.scss +++ b/conversejs/custom/shared/components/muc-app/styles/muc-app.scss @@ -5,7 +5,7 @@ */ .conversejs { - livechat-converse-muc-task-app { + .livechat-converse-muc-app { border: var(--occupants-border-left); display: flex; flex-flow: column nowrap; @@ -42,8 +42,8 @@ &[livechat-converse-root-width="small"], &[livechat-converse-root-width="medium"] { - converse-muc-chatarea livechat-converse-muc-task-app:not(.hidden) ~ * { - // on small and medium width, we hide all subsequent siblings of the task app + converse-muc-chatarea .livechat-converse-muc-app:not(.hidden) ~ * { + // on small and medium width, we hide all subsequent siblings of the app // (when app is not hidden) display: none !important; } diff --git a/conversejs/custom/shared/components/muc-app/templates/muc-app.js b/conversejs/custom/shared/components/muc-app/templates/muc-app.js new file mode 100644 index 00000000..85413da9 --- /dev/null +++ b/conversejs/custom/shared/components/muc-app/templates/muc-app.js @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { html } from 'lit' +import { __ } from 'i18n' + +export function tplMUCApp (el, i18nTitle, helpUrl, i18nHelp, content) { + return html` + + ${i18nTitle} + + + + + + + ${content} + ` +} diff --git a/conversejs/custom/shared/lib/pubsub-manager.js b/conversejs/custom/shared/lib/pubsub-manager.js index ceabf378..9df114ef 100644 --- a/conversejs/custom/shared/lib/pubsub-manager.js +++ b/conversejs/custom/shared/lib/pubsub-manager.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { converse, _converse, api } from '../../../src/headless/core.js' +import { converse, _converse, api } from '../../../src/headless/index.js' const { $build, Strophe, $iq, sizzle } = converse.env /** @@ -50,7 +50,7 @@ export class PubSubManager { async start () { // FIXME: handle errors. Find a way to display to user that this failed. - this.stanzaHandler = _converse.connection.addHandler( + this.stanzaHandler = api.connection.get().addHandler( (message) => { try { this._handleMessage(message) @@ -79,7 +79,7 @@ export class PubSubManager { // Note: no need to unsubscribe from the pubsub node, the backend will do when users leave the room. if (this.stanzaHandler) { - _converse.connection.deleteHandler(this.stanzaHandler) + api.connection.get().deleteHandler(this.stanzaHandler) this.stanzaHandler = undefined } } @@ -123,6 +123,7 @@ export class PubSubManager { if (v === undefined) { continue } data[field] = v } + this._additionalModelToData(item, data) console.log('Saving item...') await this._save(type, data, id) @@ -178,6 +179,8 @@ export class PubSubManager { item.c(fieldName).t(data[fieldName]).up() } + this._additionalDataToItemNode(data, item) + await api.pubsub.publish(this.roomJID, this.node, item) } @@ -336,6 +339,7 @@ export class PubSubManager { } } } + this._additionalParseItemNode(itemNode, type, data) return data } @@ -351,4 +355,19 @@ export class PubSubManager { _typeFromCollection (collection) { return Object.values(this.types).find(type => type.collection === collection) } + + /** + * Overload to add some custom code for model to data conversion. + */ + _additionalModelToData (_item, _data) {} + + /** + * Overload to add some custom code for data to stanza conversion. + */ + _additionalDataToItemNode (_data, _item) {} + + /** + * Overload to add some custom code item parsing. + */ + _additionalParseItemNode (_itemNode, _type, _data) {} } diff --git a/conversejs/custom/shared/modals/livechat-external-login.js b/conversejs/custom/shared/modals/livechat-external-login.js index b0accf00..49f20771 100644 --- a/conversejs/custom/shared/modals/livechat-external-login.js +++ b/conversejs/custom/shared/modals/livechat-external-login.js @@ -4,7 +4,7 @@ import { __ } from 'i18n' import BaseModal from 'plugins/modal/modal.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import { html } from 'lit' import 'livechat-external-login-content.js' @@ -20,8 +20,8 @@ class ExternalLoginModal extends BaseModal { return __(LOC_login_using_external_account) } - onHide () { - super.onHide() + close () { + super.close() // kill the externalAuthGetResult handler if still there try { if (window.externalAuthGetResult) { window.externalAuthGetResult() } diff --git a/conversejs/custom/shared/styles/_peertubetheme.scss b/conversejs/custom/shared/styles/_peertubetheme.scss index ebee8392..6ac3f33d 100644 --- a/conversejs/custom/shared/styles/_peertubetheme.scss +++ b/conversejs/custom/shared/styles/_peertubetheme.scss @@ -8,7 +8,7 @@ .dropdown-menu { // Fixing all dropdown colors --text-color: #212529; // default bootstrap color for dropdown-items - --text-color-lighten-15-percent: #8c8c8c; // default ConverseJS theme color + --inverse-link-color: #8c8c8c; // default ConverseJS theme color background-color: #fff; // this is the default bootstrap color, used by ConverseJS @@ -27,6 +27,7 @@ border: 1px dashed var(--peertube-menu-background); color: var(--peertube-main-foreground); background-color: var(--peertube-main-background); + margin: 0 5px; .livechat-hide-slow-mode-info-box { cursor: pointer; diff --git a/conversejs/custom/shared/styles/_variables.scss b/conversejs/custom/shared/styles/_variables.scss index 4d43fd4c..828592fc 100644 --- a/conversejs/custom/shared/styles/_variables.scss +++ b/conversejs/custom/shared/styles/_variables.scss @@ -34,12 +34,16 @@ body.converse-fullscreen.theme-peertube, body.converse-embedded converse-root.theme-peertube { --foreground: var(--peertube-main-foreground); --background: var(--peertube-main-background); + --badge-color: var(--background); + --button-hover-text-color: var(--background); --subdued-color: #a8aba1; + --muc-color: var(--peertube-button-background); --green: #3aa569; // only in this file --redder-orange: #e77051; // only in this file --orange: #e7a151; // only in this file --light-blue: #578ea9; // only in this file --lighter-blue: #85b47b; // only in this file + --chat-color: var(--green); // FIXME: copied from Converse. Is there side effects? --chat-status-online: var(--green); --chat-status-busy: var(--redder-orange); --chat-status-away: var(--orange); @@ -55,7 +59,6 @@ body.converse-embedded converse-root.theme-peertube { --text-shadow-color: var(--peertube-main-background); // FIXME: should be a little different from background --text-color: var(--peertube-input-foreground); --controlbox-text-color: var(--peertube-input-foreground); // Note: controlbox is not used - --text-color-lighten-15-percent: var(--peertube-input-foreground); --message-text-color: var(--peertube-input-foreground); --message-receipt-color: var(--green); --save-button-color: var(--green); @@ -73,7 +76,6 @@ body.converse-embedded converse-root.theme-peertube { --chat-correcting-color: var(--peertube-grey-background); --chat-head-color-dark: #1e9652; // should not be used in this plugin --chat-head-color-darker: #0e763b; // should not be used in this plugin - --chat-head-color-lighten-50-percent: #e7f7ee; // should not be used in this plugin --chat-head-color: var(--green); --chat-head-text-color: var(--peertube-input-foreground); --chat-toolbar-btn-color: var(--peertube-button-background); @@ -106,7 +108,6 @@ body.converse-embedded converse-root.theme-peertube { --controlbox-pane-background-color: #333; --controlbox-pane-bg-hover-color: #464646; --panel-divider-color: #333; - --chat-gutter: 0.5em; --minimized-chats-width: 130px; --mobile-chat-width: 100%; --mobile-chat-height: 400px; @@ -119,9 +120,10 @@ body.converse-embedded converse-root.theme-peertube { --chatroom-badge-color: var(--peertube-button-background); --chatroom-badge-hover-color: var(--peertube-button-background); --chatroom-correcting-color: var(--peertube-grey-background); - --chatroom-head-bg-color-dark: #d24e2b; + --chatroom-head-bg-color-dark: var(--peertube-button-background); --chatroom-head-bg-color: var(--peertube-menu-background); - --chatroom-head-border-bottom: 1px solid var(--peertube-grey-foreground); + --chatroom-head-border-bottom: 0.15em solid var(--peertube-grey-foreground); + --chatroom-head-fg-color: var(--subdued-color); --chatroom-head-button-color: #999; --chatroom-head-color: var(--peertube-menu-foreground); --chatroom-head-description-border-left: 1px solid #ddd; @@ -163,6 +165,7 @@ body.converse-embedded converse-root.theme-peertube { --fullpage-chat-width: 100%; --fullpage-emoji-picker-height: 300px; --fullpage-max-chat-textarea-height: 15em; + --overlayed-chat-gutter: 1em; --overlayed-chat-head-height: 55px; --overlayed-chat-height: 450px; --overlayed-chat-width: 300px; diff --git a/conversejs/custom/shared/styles/livechat.scss b/conversejs/custom/shared/styles/livechat.scss index 2471de67..1ae46aaf 100644 --- a/conversejs/custom/shared/styles/livechat.scss +++ b/conversejs/custom/shared/styles/livechat.scss @@ -60,7 +60,7 @@ body.livechat-readonly.livechat-noscroll { } } -// Viewer mode +// Viewer mode (before the user has chosen its nickname) .livechat-viewer-mode-content { display: none; @@ -73,7 +73,7 @@ body.livechat-readonly.livechat-noscroll { gap: 0.5em 10px; align-items: baseline; - .form-group, + fieldset, label { margin-bottom: 0 !important; // replaced by the gap on .livechat-viewer-mode-content } @@ -171,7 +171,8 @@ body.converse-embedded { #peertube-plugin-livechat-container { converse-muc-message-form { // For an unknown reason, message field in truncated... so adding a bottom margin. - margin-bottom: 6px; + // We also add left and right margin, as Converse v11 adds a g-0 class on converse-muc-chatarea + margin: 0 1px 6px 5px; } } @@ -187,4 +188,44 @@ body.converse-embedded { // So we must revert appearance: appearance: revert !important; } + + .toolbar-buttons { + // Converse v11 removed the toggle_occupant button on the right. + // To add it back, we must ensure that this toolbar takes all the width, and + // that the toggle-occupants button is on the right. + flex-grow: 2; + + .toggle-occupants { + // Cancelling the flex-grow from btn-group + flex-grow: 0 !important; + + // This margin-left trick is to align the button on the right. + margin-left: auto !important; + order: 99; + } + } +} + +/* stylelint-disable-next-line no-descending-specificity */ +#conversejs { // here we use the id have gretter priority + // These CSS are tricks: Converse v11 tries to hide the MUC when screen width is under 768px. + // We don't want that, so we cancel the d-none. + // FIXME: these hacks should be temporary, waiting for some improvement on Converse. + converse-muc-chatarea { + .chat-area.d-none { + display: flex !important; + } + + /* stylelint-disable-next-line no-descending-specificity */ + converse-muc-sidebar { + // we must not use !important for flex, it would break resizing. + // That's why we use #conversejs insteand of .conversejs for this block. + flex: 0 0 min(400px, 50%); + min-width: min(200px, 50%) !important; + + .occupants { + width: 100%; + } + } + } } diff --git a/conversejs/custom/templates/background_logo.js b/conversejs/custom/templates/background_logo.js index 585de358..4548f2a5 100644 --- a/conversejs/custom/templates/background_logo.js +++ b/conversejs/custom/templates/background_logo.js @@ -5,7 +5,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { html } from 'lit' -import { api } from '@converse/headless/core.js' +import { api } from '@converse/headless/index.js' export default () => html` diff --git a/conversejs/custom/templates/livechat-external-login-modal.js b/conversejs/custom/templates/livechat-external-login-modal.js index a8cbca95..0bba91de 100644 --- a/conversejs/custom/templates/livechat-external-login-modal.js +++ b/conversejs/custom/templates/livechat-external-login-modal.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { _converse, api } from '@converse/headless/core' +import { _converse, api } from '@converse/headless' import { __ } from 'i18n' import { html } from 'lit' @@ -117,7 +117,7 @@ export const tplExternalLoginModal = (el, o) => { ${ externalButtons.map(button => html` - { @@ -187,7 +187,9 @@ export const tplExternalLoginModal = (el, o) => { // eslint-disable-next-line no-undef __(LOC_login_remote_peertube_video_not_found_try_anyway) } - el.openUrlTargetTop(o.remote_peertube_try_anyway_url)}>${ + el.openUrlTargetTop(o.remote_peertube_try_anyway_url)} + >${ // eslint-disable-next-line no-undef __(LOC_login_remote_peertube_video_not_found_try_anyway_button) } diff --git a/conversejs/custom/templates/muc-bottom-panel.js b/conversejs/custom/templates/muc-bottom-panel.js index cfac6d2c..91b12bb4 100644 --- a/conversejs/custom/templates/muc-bottom-panel.js +++ b/conversejs/custom/templates/muc-bottom-panel.js @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { __ } from 'i18n' -import { _converse, api } from '@converse/headless/core' +import { _converse, api } from '@converse/headless' import { html } from 'lit' import tplMucBottomPanel from '../../src/plugins/muc-views/templates/muc-bottom-panel.js' import { CustomElement } from 'shared/components/element.js' @@ -79,7 +79,7 @@ class SlowMode extends CustomElement { api.elements.define('livechat-slow-mode', SlowMode) const tplSlowMode = (o) => { - if (!o.can_edit) { return html`` } + if (!o.can_post) { return html`` } return html`` } @@ -97,7 +97,7 @@ const tplViewerMode = (o) => { setNickname(ev, model)}> ${i18nHeading} - + { class="form-control" placeholder="${i18nNickname}"/> - + @@ -117,7 +117,7 @@ const tplViewerMode = (o) => { : html` - { + { ev.preventDefault() api.modal.show('converse-livechat-external-login') }}>${i18nExternalLogin} @@ -128,17 +128,9 @@ const tplViewerMode = (o) => { } export default (o) => { - // ConverseJS 10.x does not handle properly the visitor role in unmoderated rooms. - // See https://github.com/conversejs/converse.js/issues/3428 for more info. - // So we will do a dirty hack here to fix this case. - // Note: ConverseJS 11.x has changed the code, and could be fixed more cleanly (or will be fixed if #3428 is fixed). - if (o.can_edit && o.model.getOwnRole() === 'visitor') { - o.can_edit = false - } - let mutedAnonymousMessage if ( - !o.can_edit && + !o.can_post && o.model.features?.get?.('x_peertubelivechat_mute_anonymous') && _converse.api.settings.get('livechat_specific_is_anonymous') === true ) { diff --git a/conversejs/custom/templates/muc-chatarea.js b/conversejs/custom/templates/muc-chatarea.js index 726e8fd5..d2067c4e 100644 --- a/conversejs/custom/templates/muc-chatarea.js +++ b/conversejs/custom/templates/muc-chatarea.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import tplMUCChatarea from '../../src/plugins/muc-views/templates/muc-chatarea.js' import { html } from 'lit' @@ -13,5 +13,15 @@ export default (o) => { ? html`` : '' } + ${ + o?.model && api.settings.get('livechat_note_app_enabled') + ? html`` + : '' + } + ${ + o?.model && api.settings.get('livechat_mam_search_app_enabled') + ? html`` + : '' + } ${tplMUCChatarea(o)}` } diff --git a/conversejs/custom/templates/muc-head.js b/conversejs/custom/templates/muc-head.js index d926ed32..ebd1ef7a 100644 --- a/conversejs/custom/templates/muc-head.js +++ b/conversejs/custom/templates/muc-head.js @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { html } from 'lit' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import { until } from 'lit/directives/until.js' import { repeat } from 'lit/directives/repeat.js' import { unsafeHTML } from 'lit/directives/unsafe-html.js' diff --git a/conversejs/custom/templates/muc.js b/conversejs/custom/templates/muc.js index f03ce6f1..d3da4caf 100644 --- a/conversejs/custom/templates/muc.js +++ b/conversejs/custom/templates/muc.js @@ -17,12 +17,12 @@ export default (o) => { ${ o.model ? html` - + - ${getChatRoomBodyTemplate(o)}` + ${getChatRoomBodyTemplate(o)}` : ''} ` } diff --git a/conversejs/custom/webpack.livechat.js b/conversejs/custom/webpack.livechat.js index bf6e041c..6825b35b 100644 --- a/conversejs/custom/webpack.livechat.js +++ b/conversejs/custom/webpack.livechat.js @@ -44,7 +44,7 @@ module.exports = merge(prod, { '../../templates/background_logo.js$': path.resolve(__dirname, 'custom/templates/background_logo.js'), './templates/muc-chatarea.js': path.resolve('custom/templates/muc-chatarea.js'), - '../templates/icons.js': path.resolve(__dirname, 'custom/shared/components/font-awesome.js'), + './templates/icons.js': path.resolve(__dirname, 'custom/shared/components/font-awesome.js'), 'shared/styles/index.scss$': path.resolve(__dirname, 'custom/shared/styles/livechat.scss'), diff --git a/conversejs/lib/converse-params.ts b/conversejs/lib/converse-params.ts index e7ef1c06..cbf28ed8 100644 --- a/conversejs/lib/converse-params.ts +++ b/conversejs/lib/converse-params.ts @@ -71,8 +71,7 @@ function defaultConverseParams ( visible_toolbar_buttons: { call: false, spoiler: false, - emoji: true, - toggle_occupants: true + emoji: true }, theme: theme || 'peertube', dark_theme: theme || 'peertube', // dark theme should be the same as theme @@ -97,6 +96,7 @@ function defaultConverseParams ( pruning_behavior: 'unscrolled', colorize_username: true, send_chat_markers: [], + reuse_scram_keys: false, // for now we don't use this. // This is a specific settings, that is used in ConverseJS customization, to force avatars loading in readonly mode. livechat_load_all_vcards: !!forceReadonly, diff --git a/conversejs/lib/plugins/livechat-mini-muc-head.ts b/conversejs/lib/plugins/livechat-mini-muc-head.ts index 82f71484..49815919 100644 --- a/conversejs/lib/plugins/livechat-mini-muc-head.ts +++ b/conversejs/lib/plugins/livechat-mini-muc-head.ts @@ -16,7 +16,7 @@ export const livechatMiniMucHeadPlugin = { }) _converse.api.listen.on('getHeadingButtons', (view: any, buttons: any[]) => { - if (view.model.get('type') !== _converse.CHATROOMS_TYPE) { + if (view.model.get('type') !== _converse.constants.CHATROOMS_TYPE) { // only on MUC. return buttons } diff --git a/conversejs/lib/plugins/livechat-specific.ts b/conversejs/lib/plugins/livechat-specific.ts index 6c7fdf43..6313564d 100644 --- a/conversejs/lib/plugins/livechat-specific.ts +++ b/conversejs/lib/plugins/livechat-specific.ts @@ -2,6 +2,14 @@ // // SPDX-License-Identifier: AGPL-3.0-only +import { customizeHeading } from './livechat-specific/heading' +import { customizeToolbar } from './livechat-specific/toolbar' +import { initReconnectionStuff } from './livechat-specific/reconnection' +import { chatRoomOverrides } from './livechat-specific/chatroom' +import { chatRoomMessageOverrides } from './livechat-specific/chatroom-message' +import { customizeMessageAction } from './livechat-specific/message-action' +import { customizeProfileModal } from './livechat-specific/profile' + export const livechatSpecificsPlugin = { dependencies: ['converse-muc', 'converse-muc-views'], initialize: function (this: any) { @@ -14,101 +22,17 @@ export const livechatSpecificsPlugin = { livechat_specific_is_anonymous: false }) - _converse.api.listen.on('getHeadingButtons', (view: any, buttons: any[]) => { - if (view.model.get('type') !== _converse.CHATROOMS_TYPE) { - // only on MUC. - return buttons - } - - if (_converse.api.settings.get('livechat_specific_external_authent')) { - // Adding a logout button - buttons.push({ - i18n_text: _converse.__('Log out'), - handler: async (ev: Event) => { - ev.preventDefault() - ev.stopPropagation() - - const messages = [_converse.__('Are you sure you want to leave this groupchat?')] - const result = await _converse.api.confirm(_converse.__('Confirm'), messages) - if (!result) { return } - - // Deleting access token in sessionStorage. - window.sessionStorage.removeItem('peertube-plugin-livechat-external-auth-oidc-token') - - const reconnectMode = _converse.api.settings.get('livechat_external_auth_reconnect_mode') - if (reconnectMode === 'button-close-open') { - const button = document.getElementsByClassName('peertube-plugin-livechat-button-close')[0] - if ((button as HTMLAnchorElement).click) { (button as HTMLAnchorElement).click() } - return - } - - window.location.reload() - }, - a_class: 'close-chatbox-button', - icon_class: 'fa-sign-out-alt', - name: 'signout' - }) - } - - return buttons - }) + customizeHeading(this) + customizeToolbar(this) + customizeMessageAction(this) + customizeProfileModal(this) _converse.api.listen.on('chatRoomViewInitialized', function (this: any, _model: any): void { // Remove the spinner if present... document.getElementById('livechat-loading-spinner')?.remove() }) - // Adding a method on window.converse, so we can close the chat on navigation-end event - // (when chatIncludeMode is peertube-*) - window.converse.livechatDisconnect = function livechatDisconnect () { - if (_converse.api.connection.connected()) { - console.log('[livechatSpecificsPlugin] disconnecting converseJS...') - _converse.api.user.logout() - } - } - - // To reconnect ConverseJS when joining another room (or the same one), - // we store the relevant closure function: - window.reconnectConverse = function reconnectConverse (params: any): void { - console.log('[livechatSpecificsPlugin] reconnecting converseJS...') - - // The new room to join: - _converse.api.settings.set('auto_join_rooms', params.auto_join_rooms) - _converse.api.settings.set('notify_all_room_messages', params.notify_all_room_messages) - - // update connection parameters (in case the user logged in after the first chat) - for (const k of [ - 'bosh_service_url', 'websocket_url', - 'authentication', 'nickname', 'muc_nickname_from_jid', 'auto_login', 'jid', 'password', 'keepalive' - ]) { - _converse.api.settings.set(k, params[k]) - } - - // update other settings - for (const k of [ - 'hide_muc_participants', - 'livechat_enable_viewer_mode', - 'livechat_external_auth_oidc_buttons', - 'livechat_external_auth_reconnect_mode', - 'livechat_mini_muc_head', - 'livechat_specific_external_authent', - 'livechat_task_app_enabled', - 'livechat_task_app_restore', - 'livechat_custom_emojis_url', - 'emoji_categories' - ]) { - _converse.api.settings.set(k, params[k]) - } - - // We also unload emojis, in case there are custom emojis. - window.converse.emojis = { - initialized: false, - initialized_promise: getOpenPromise() - } - - // Then login. - _converse.api.user.login() - } + initReconnectionStuff(this) if (window.location.protocol === 'http:') { // We are probably on a dev instance, so we will add _converse in window: @@ -116,70 +40,7 @@ export const livechatSpecificsPlugin = { } }, overrides: { - ChatRoom: { - getActionInfoMessage: function (this: any, code: string, nick: string, actor: any): any { - if (code === '303') { - // When there is numerous anonymous users joining at the same time, - // they can all change their nicknames at the same time, generating a log of action messages. - // To mitigate this, will don't display nickname changes if the previous nick is something like - // 'Anonymous 12345'. - if (/^Anonymous \d+$/.test(nick)) { - // To avoid displaying the message, we just have to return an empty one - // (createInfoMessage will ignore if !data.message). - return null - } - } - return this.__super__.getActionInfoMessage(code, nick, actor) - } - }, - ChatRoomMessage: { - /* By default, ConverseJS groups messages from the same users for a 10 minutes period. - * This make no sense in a livechat room. So we override isFollowup to ignore. */ - isFollowup: function isFollowup () { return false } - }, - ChatRoomOccupants: { - comparator: function (this: any, occupant1: any, occupant2: any): Number { - // Overriding Occupants comparators, to display anonymous users at the end of the list. - const nick1: string = occupant1.getDisplayName() - const nick2: string = occupant2.getDisplayName() - const b1 = nick1.startsWith('Anonymous ') - const b2 = nick2.startsWith('Anonymous ') - if (b1 === b2) { - // Both startswith anonymous, or non of it: fallback to the standard comparator. - return this.__super__.comparator(occupant1, occupant2) - } - // Else: Anonymous always last. - return b1 ? 1 : -1 - } - } + ChatRoom: chatRoomOverrides(), + ChatRoomMessage: chatRoomMessageOverrides() } } - -// FIXME: this function is copied from @converse. Should not do so. -function getOpenPromise (): any { - const wrapper: any = { - isResolved: false, - isPending: true, - isRejected: false - } - const promise: any = new Promise((resolve, reject) => { - wrapper.resolve = resolve - wrapper.reject = reject - }) - Object.assign(promise, wrapper) - promise.then( - function (v: any) { - promise.isResolved = true - promise.isPending = false - promise.isRejected = false - return v - }, - function (e: any) { - promise.isResolved = false - promise.isPending = false - promise.isRejected = true - throw (e) - } - ) - return promise -} diff --git a/conversejs/lib/plugins/livechat-specific/chatroom-message.ts b/conversejs/lib/plugins/livechat-specific/chatroom-message.ts new file mode 100644 index 00000000..c34ddff7 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/chatroom-message.ts @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +export function chatRoomMessageOverrides (): {[key: string]: Function} { + return { + /* By default, ConverseJS groups messages from the same users for a 10 minutes period. + * This make no sense in a livechat room. So we override isFollowup to ignore. */ + isFollowup: function isFollowup () { return false } + } +} diff --git a/conversejs/lib/plugins/livechat-specific/chatroom.ts b/conversejs/lib/plugins/livechat-specific/chatroom.ts new file mode 100644 index 00000000..297c0ed4 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/chatroom.ts @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +export function chatRoomOverrides (): {[key: string]: Function} { + return { + getActionInfoMessage: function getActionInfoMessage (this: any, code: string, nick: string, actor: any): any { + if (code === '303') { + // When there is numerous anonymous users joining at the same time, + // they can all change their nicknames at the same time, generating a log of action messages. + // To mitigate this, will don't display nickname changes if the previous nick is something like + // 'Anonymous 12345'. + if (/^Anonymous \d+$/.test(nick)) { + // To avoid displaying the message, we just have to return an empty one + // (createInfoMessage will ignore if !data.message). + return null + } + } + return this.__super__.getActionInfoMessage(code, nick, actor) + }, + canPostMessages: function canPostMessages (this: any) { + // ConverseJS does not handle properly the visitor role in unmoderated rooms. + // See https://github.com/conversejs/converse.js/issues/3428 for more info. + // FIXME: if #3428 is fixed, remove this override. + return this.isEntered() && this.getOwnRole() !== 'visitor' + }, + initOccupants: function initOccupants (this: any) { + const r = this.__super__.initOccupants() + + const originalComparatorFunction: Function = this.occupants.comparator + this.occupants.comparator = function (this: any, occupant1: any, occupant2: any): Number { + // Overriding Occupants comparators, to display anonymous users at the end of the list. + const nick1: string = occupant1.getDisplayName() + const nick2: string = occupant2.getDisplayName() + const b1 = nick1.startsWith('Anonymous ') + const b2 = nick2.startsWith('Anonymous ') + if (b1 === b2) { + // Both startswith anonymous, or non of it: fallback to the standard comparator. + return originalComparatorFunction.call(this, occupant1, occupant2) + } + // Else: Anonymous always last. + return b1 ? 1 : -1 + } + + return r + } + } +} diff --git a/conversejs/lib/plugins/livechat-specific/heading.ts b/conversejs/lib/plugins/livechat-specific/heading.ts new file mode 100644 index 00000000..38170d14 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/heading.ts @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { destroyMUC } from './utils' + +/** + * Do some customization on MUCHeading: + * * adds a logout button for users that are authenticated with an external account + * * change the destroyMUC handler + * + * @param plugin The plugin object + */ +export function customizeHeading (plugin: any): void { + const _converse = plugin._converse + _converse.api.listen.on('getHeadingButtons', getHeadingButtons.bind(plugin)) + overrideMUCHeadingElement(_converse) +} + +function getHeadingButtons (this: any, view: any, buttons: any[]): any { + const _converse = this._converse + + if (view.model.get('type') !== _converse.constants.CHATROOMS_TYPE) { + // only on MUC. + return buttons + } + + if (_converse.api.settings.get('livechat_specific_external_authent')) { + // Adding a logout button + buttons.push({ + i18n_text: _converse.__('Log out'), + handler: async (ev: Event) => { + ev.preventDefault() + ev.stopPropagation() + + const messages = [_converse.__('Are you sure you want to leave this groupchat?')] + const result = await _converse.api.confirm(_converse.__('Confirm'), messages) + if (!result) { return } + + // Deleting access token in sessionStorage. + window.sessionStorage.removeItem('peertube-plugin-livechat-external-auth-oidc-token') + + const reconnectMode = _converse.api.settings.get('livechat_external_auth_reconnect_mode') + if (reconnectMode === 'button-close-open') { + const button = document.getElementsByClassName('peertube-plugin-livechat-button-close')[0] + if ((button as HTMLAnchorElement).click) { (button as HTMLAnchorElement).click() } + return + } + + window.location.reload() + }, + a_class: 'close-chatbox-button', + icon_class: 'fa-sign-out-alt', + name: 'signout' + }) + } + + return buttons +} + +/** + * Override the MUCHeading custom element, to customize the destroyMUC function. + */ +function overrideMUCHeadingElement (_converse: any): void { + const MUCHeading = _converse.api.elements.registry['converse-muc-heading'] + if (MUCHeading) { + class MUCHeadingOverloaded extends MUCHeading { + async destroy (ev: Event): Promise { + ev.preventDefault() + await destroyMUC(_converse, this.model) // here we call a custom version of destroyMUC + } + } + _converse.api.elements.define('converse-muc-heading', MUCHeadingOverloaded) + } +} diff --git a/conversejs/lib/plugins/livechat-specific/message-action.ts b/conversejs/lib/plugins/livechat-specific/message-action.ts new file mode 100644 index 00000000..87bb70ca --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/message-action.ts @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * Do some customization on Message actions custom elements: + * * override the copy text method to add meta data + * + * @param plugin The plugin object + */ +export function customizeMessageAction (plugin: any): void { + const _converse = plugin._converse + const MessageActions = _converse.api.elements.registry['converse-message-actions'] + if (MessageActions) { + class MessageActionsOverloaded extends MessageActions { + async onMessageCopyButtonClicked (ev?: Event): Promise { + ev?.preventDefault?.() + let txt = '' + try { + txt += this.model.getDisplayName() as string + txt += ' - ' + const date = new Date(this.model.get('edited') || this.model.get('time')) + txt += date.toLocaleDateString() + ' ' + date.toLocaleTimeString() + txt += '\n' + } catch {} + txt += this.model.getMessageText() as string + await navigator.clipboard.writeText(txt) + } + } + _converse.api.elements.define('converse-message-actions', MessageActionsOverloaded) + } +} diff --git a/conversejs/lib/plugins/livechat-specific/profile.ts b/conversejs/lib/plugins/livechat-specific/profile.ts new file mode 100644 index 00000000..fbb60bd6 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/profile.ts @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * Livechat Converse does not include plugins/profile, + * so the default profile modal is broken. + * Moreover this modal includes some features that we do not want (password change, ...). + * So we simply define the converse-profile-modal to be converse-muc-occupant-modal! + * @param plugin The plugin object + */ +export function customizeProfileModal (plugin: any): void { + const _converse = plugin._converse + const OccupantModal = _converse.api.elements.registry['converse-muc-occupant-modal'] + if (!OccupantModal) { return } + class ProfileModal extends OccupantModal { + initialize (): any { + // We just need to change the modal for the occupant: + if (this.model?.getOccupant) { + this.model = this.model.getOccupant() + } + return super.initialize() + } + } + _converse.api.elements.define('converse-profile-modal', ProfileModal) +} diff --git a/conversejs/lib/plugins/livechat-specific/reconnection.ts b/conversejs/lib/plugins/livechat-specific/reconnection.ts new file mode 100644 index 00000000..b75bf488 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/reconnection.ts @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { getOpenPromise } from './utils' + +/** + * Initialiaze some function on `window` that will be used for the reconnection process. + * + * @param plugin The plugin object + */ +export function initReconnectionStuff (plugin: any): void { + const _converse = plugin._converse + + // Adding a method on window.converse, so we can close the chat on navigation-end event + // (when chatIncludeMode is peertube-*) + window.converse.livechatDisconnect = function livechatDisconnect () { + if (_converse.api.connection.connected()) { + console.log('[livechatSpecificsPlugin] disconnecting converseJS...') + _converse.api.user.logout() + } + } + + // To reconnect ConverseJS when joining another room (or the same one), + // we store the relevant closure function: + window.reconnectConverse = function reconnectConverse (params: any): void { + console.log('[livechatSpecificsPlugin] reconnecting converseJS...') + + // The new room to join: + _converse.api.settings.set('auto_join_rooms', params.auto_join_rooms) + _converse.api.settings.set('notify_all_room_messages', params.notify_all_room_messages) + + // update connection parameters (in case the user logged in after the first chat) + for (const k of [ + 'bosh_service_url', 'websocket_url', + 'authentication', 'nickname', 'muc_nickname_from_jid', 'auto_login', 'jid', 'password', 'keepalive' + ]) { + _converse.api.settings.set(k, params[k]) + } + + // update other settings + for (const k of [ + 'hide_muc_participants', + 'livechat_enable_viewer_mode', + 'livechat_external_auth_oidc_buttons', + 'livechat_external_auth_reconnect_mode', + 'livechat_mini_muc_head', + 'livechat_specific_external_authent', + 'livechat_task_app_enabled', + 'livechat_task_app_restore', + 'livechat_custom_emojis_url', + 'emoji_categories' + ]) { + _converse.api.settings.set(k, params[k]) + } + + // We also unload emojis, in case there are custom emojis. + window.converse.emojis = { + initialized: false, + initialized_promise: getOpenPromise() + } + + // Then login. + _converse.api.user.login() + } +} diff --git a/conversejs/lib/plugins/livechat-specific/toolbar.ts b/conversejs/lib/plugins/livechat-specific/toolbar.ts new file mode 100644 index 00000000..32c4ce6f --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/toolbar.ts @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * Do some customization on the toolbar: + * * change the appearance of the toggle occupants button + * + * @param plugin The plugin object + */ +export function customizeToolbar (plugin: any): void { + const _converse = plugin._converse + _converse.api.listen.on('getToolbarButtons', getToolbarButtons.bind(plugin)) +} + +function getToolbarButtons (this: any, toolbarEl: any, buttons: any[]): any { + const _converse = this._converse + + // Adding a toggle_occupants button. + // Note: we don't need to test conditions, we know the button was here. + const i18nHideOccupants = _converse.__('Hide participants') + const i18nShowOccupants = _converse.__('Show participants') + const html = window.converse.env.html + const icon = toolbarEl.hidden_occupants + ? html` + + + ` + : html` + + + ` + buttons.push(html` + { + ev?.preventDefault() + toolbarEl.model.save({ + hidden_occupants: !toolbarEl.model.get('hidden_occupants') + }) + }}> + ${icon} + ` + ) + return buttons +} diff --git a/conversejs/lib/plugins/livechat-specific/utils.ts b/conversejs/lib/plugins/livechat-specific/utils.ts new file mode 100644 index 00000000..14644041 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/utils.ts @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +// FIXME: this function is copied from @converse. Should not do so. +export function getOpenPromise (): any { + const wrapper: any = { + isResolved: false, + isPending: true, + isRejected: false + } + const promise: any = new Promise((resolve, reject) => { + wrapper.resolve = resolve + wrapper.reject = reject + }) + Object.assign(promise, wrapper) + promise.then( + function (v: any) { + promise.isResolved = true + promise.isPending = false + promise.isRejected = false + return v + }, + function (e: any) { + promise.isResolved = false + promise.isPending = false + promise.isRejected = true + throw (e) + } + ) + return promise +} + +export async function destroyMUC (_converse: any, model: any): Promise { + const __ = _converse.__ + const messages = [__('Are you sure you want to destroy this groupchat?')] + // Note: challenge and newjid make no sens for peertube-plugin-livechat, + // we remove them comparing to the original function. + let fields = [ + { + name: 'reason', + label: __('Optional reason for destroying this groupchat'), + placeholder: __('Reason'), + value: undefined + } + ] + try { + fields = await _converse.api.confirm(__('Confirm'), messages, fields) + const reason = fields.filter(f => f.name === 'reason').pop()?.value + const newjid = undefined + return model.sendDestroyIQ(reason, newjid).then(() => model.close()) + } catch (e) { + console.error(e) + } +} diff --git a/conversejs/lib/plugins/livechat-viewer-mode.ts b/conversejs/lib/plugins/livechat-viewer-mode.ts index b01fffab..4d889c88 100644 --- a/conversejs/lib/plugins/livechat-viewer-mode.ts +++ b/conversejs/lib/plugins/livechat-viewer-mode.ts @@ -17,11 +17,11 @@ export const livechatViewerModePlugin = { livechat_external_auth_reconnect_mode: undefined }) - const originalGetDefaultMUCNickname = _converse.getDefaultMUCNickname + const originalGetDefaultMUCNickname = _converse.exports.getDefaultMUCNickname if (!originalGetDefaultMUCNickname) { console.error('[livechatViewerModePlugin] getDefaultMUCNickname is not initialized.') } else { - Object.assign(_converse, { + Object.assign(_converse.exports, { getDefaultMUCNickname: function (this: any): any { if (!_converse.api.settings.get('livechat_enable_viewer_mode')) { return originalGetDefaultMUCNickname.apply(this, arguments) diff --git a/conversejs/loc.keys.js b/conversejs/loc.keys.js index 7a6f9283..ed3ebe95 100644 --- a/conversejs/loc.keys.js +++ b/conversejs/loc.keys.js @@ -49,7 +49,20 @@ const locKeys = [ 'poll_vote_instructions_xmpp', 'poll_is_over', 'poll_choice_invalid', - 'poll_anonymous_vote_ok' + 'poll_anonymous_vote_ok', + 'moderator_notes', + 'moderator_notes_create_error', + 'moderator_note_create', + 'moderator_note_description', + 'moderator_note_delete', + 'moderator_note_delete_confirm', + 'moderator_note_create_for_participant', + 'moderator_note_search_for_participant', + 'moderator_note_filters', + 'moderator_note_original_nick', + 'search_occupant_message', + 'message_search', + 'message_search_original_nick' ] module.exports = locKeys diff --git a/languages/ar.yml b/languages/ar.yml index e1a30bbc..4eef9af0 100644 --- a/languages/ar.yml +++ b/languages/ar.yml @@ -29,3 +29,67 @@ use_current_theme_color: استخدم ألوان الحلة الحالية connect_using_xmpp: للاتصال عبر XMPP important_note_title: ملاحظات هامة chat_title: الدردشة +moderator_note_delete: حذف الملاحظة +task_list_name: اسم قائمة المهام +tasks: المهام +avatar_set_option_cat: قطط +avatar_set_option_bird: عصافير +avatar_set_label: مجموعة الصور الرمزية +system_prosody_label: استخدم خدمة Prosody المثبتة على النظام +menu_configuration_label: غرف المحادثات +livechat_configuration_channel_command_label: سطر أوامر الروبوت +livechat_configuration_channel_command_cmd_label: سطر الأمر +moderator_note_description: الوصف +moderator_notes: ملاحظات الإشراف +moderator_note_filters: عوامل تصفية البحث +livechat_configuration_channel_forbidden_words_label_label: الملصقة +avatar_set_option_none: لا شيء +successfully_saved: تم الحفظ بنجاح +livechat_configuration_channel_slow_mode_label: الوضع البطيء +livechat_configuration_channel_forbidden_words_reason_label: السبب +livechat_configuration_channel_bot_options_title: خيارات روبوت الإشراف +livechat_configuration_channel_forbidden_words_comments_label: التعليقات +livechat_configuration_channel_command_message_label: رسالة +livechat_configuration_channel_bot_nickname: الاسم المستعار للروبوت +task_name: اسم المهمة +task_list_delete: حذف قائمة المهام +livechat_emojis_shortname: الإسم القصير +livechat_emojis_file: الملف +livechat_emojis_file_desc: "ملف الوجوه التعبيرية.\n" +share_chat_embed: الإدماج +action_remove_entry: احذف هذا الإدخال +poll: استطلاع رأي +message_search: البحث عن رسائل +search_occupant_message: البحث عن كل الرسائل +livechat_configuration_channel_title: خيارات القناة +moderator_note_search_for_participant: البحث عن ملاحظة +copied: تم نسخه +token_label: ملصقة +autocolors_label: استكشاف تلقائي للألوان +show_scrollbarr: إظهار شريط التمرير +livechat_configuration_channel_emojis_title: الوجوه التعبيرية الخاصة بالقناة +poll_choice_n: 'الخيار {{N}}:' +poll_end: 'ينتهي استطلاع الرأي :' +chat: المحادثة +livechat_configuration_channel_quote_label2: الرسائل +task_description: الوصف +action_import: استيراد +action_export: تصدير +token_date: التاريخ +poll_question: سؤال +prosody_firewall_file_enabled: مفعّل +prosody_firewall_name: الاسم +invalid_value: القيمة غير صالحة. +task_delete: حذف المهمة +poll_title: استطلاع رأي جديد +prosody_firewall_content: محتوى الملف +livechat_configuration_channel_enable_bot_label: تفعيل روبوت الإشراف +action_add_entry: إضافة إدخال +cancel: ألغِ +save: حِفْظ +external_auth_google_oidc_label: استخدم جوجل +external_auth_facebook_oidc_label: استخدم فايسبوك +chat_terms_label: شروط الاستخدام +list_rooms_description: "قائمة الغرف\n" +online_help: المساعدة على الويب diff --git a/languages/de.yml b/languages/de.yml index 3d209a22..f4a54595 100644 --- a/languages/de.yml +++ b/languages/de.yml @@ -121,7 +121,7 @@ converse_theme_description: "Bitte wählen Sie, welches ConverseJS Thema Sie nut möchten." converse_theme_option_peertube: "Peertube Thema" converse_theme_option_default: "Default ConverseJS Thema" -converse_theme_option_concord: "ConverseJS Concord Thema" +converse_theme_option_cyberpunk: "ConverseJS Cyberpunk Thema" autocolors_label: "Automatische Farberkennung" autocolors_description: | @@ -557,3 +557,47 @@ livechat_configuration_channel_moderation_delay_desc: "Standardwert der Moderati ganze Zahl: Nachrichten werden für Nicht-Moderator-Teilnehmer um X Sekunden verzögert, so dass Moderatoren die Nachricht löschen können, bevor ein anderer Benutzer sie lesen kann.\n\n" +avatar_set_option_none: Nichts +livechat_configuration_channel_anonymize_moderation_label: Moderationsaktionen anonymisieren +livechat_configuration_channel_anonymize_moderation_desc: "Moderationsaktionen anonymisieren + Standardwert für neue Räume.\nWenn dies aktiviert ist, werden Moderationsaktionen + anonymisiert, um zu vermeiden, dass bekannt wird, wer Teilnehmer bannt/verweist/....\n" +moderator_notes_create_error: Fehler beim Speichern der Notiz +moderator_note_description: Beschreibung +moderator_note_delete: Notiz löschen +moderator_note_create_for_participant: Eine neue Notiz erstellen +moderator_note_search_for_participant: Notizen suchen +moderator_note_filters: Suchfilter +moderator_note_original_nick: Nickname des Teilnehmers zum Zeitpunkt der Erstellung + der Notiz +moderator_note_create: Eine neue Notiz erstellen +moderator_note_delete_confirm: Sind Sie sicher, dass Sie diese Notiz löschen wollen? +moderator_notes: Moderationsnotizen +message_search: Suche nach Nachrichten +search_occupant_message: Alle Nachrichten suchen +message_search_original_nick: Nickname des Teilnehmers zum Zeitpunkt des Versendens + der Nachricht +prosody_firewall_label: Prosody mod_firewall einschalten +prosody_firewall_description: "Sie können mod_firewall auf Ihrem Prosody-Server aktivieren.\nWeitere + Informationen finden Sie unter in der Dokumentation.\n" +prosody_firewall_configure_button: "mod_firewall konfigurieren\n" +prosody_firewall_configuration: Prosody mod_firewall Konfiguration +prosody_firewall_configuration_help: "Hier können Sie das Modul Prosody mod_firewall konfigurieren.\n + Sie können mehrere Konfigurationsdateien unten erstellen und deren Reihenfolge ändern.\n + Zögern Sie nicht, Ihre Konfigurationen mit der Gemeinschaft zu teilen (z. B. indem + Sie einige Beispiele zu der Plugin-Dokumentation hinzufügen).\n" +prosody_firewall_disabled_warning: "Warnung: mod_firewall ist in den livechat Plugin-Einstellungen + deaktiviert, du musst es aktivieren, wenn du willst, dass diese Konfiguration + beachtet wird.\n" +prosody_firewall_file_enabled: Aktiviert +prosody_firewall_name: Name +prosody_firewall_name_desc: "Darf nur folgende Zeichen enthalten: alphanumerische + Zeichen, Unterstriche und Bindestriche.\nDie Skripte werden in alphabetischer Reihenfolge + geladen.\n" +prosody_firewall_content: Dateiinhalt +chat: Chat diff --git a/languages/en.yml b/languages/en.yml index 22b31a56..505cc906 100644 --- a/languages/en.yml +++ b/languages/en.yml @@ -1,3 +1,4 @@ +chat: Chat online_help: "Online help" open_chat: "Open chat" open_chat_new_window: "Open chat in a new window" @@ -182,12 +183,13 @@ avatar_set_option_fenec: "Fenecs (Mobilizon mascot)" avatar_set_option_abstract: Abstract avatar_set_option_legacy: "Legacy Sepia avatars (those included in previous plugin versions)" +avatar_set_option_none: None converse_theme_label: "ConverseJS theme" converse_theme_description: "Please choose the converseJS theme you want to use." converse_theme_option_peertube: "Peertube theme" converse_theme_option_default: "Default ConverseJS theme" -converse_theme_option_concord: "ConverseJS concord theme" +converse_theme_option_cyberpunk: "ConverseJS Cyberpunk theme" autocolors_label: "Automatic color detection" autocolors_description: | @@ -568,18 +570,23 @@ livechat_configuration_channel_terms_desc: | new_poll: Create a new poll poll: Poll poll_title: New poll -poll_instructions: Complete and submit this form to create a new poll. This will end and replace any existing poll. +poll_instructions: Complete and submit this form to create a new poll. This will end + and replace any existing poll. poll_question: Question poll_duration: Poll duration (in minutes) poll_anonymous_results: Anonymous results poll_choice_n: 'Choice {{N}}:' poll_end: 'Poll ends at:' -poll_vote_instructions: "To vote, click on your choice or send a message with an exclamation mark followed by your choice number (Example: !1)." -poll_vote_instructions_xmpp: "Send a message with an exclamation mark followed by your choice number to vote. Example: !1" +poll_vote_instructions: "To vote, click on your choice or send a message with an exclamation + mark followed by your choice number (Example: !1)." +poll_vote_instructions_xmpp: "Send a message with an exclamation mark followed by + your choice number to vote. Example: !1" poll_is_over: This poll is now over. poll_choice_invalid: This choice is not valid. -poll_anonymous_vote_ok: Your vote is taken into account. Votes are anonymous, they will not be shown to other participants. -poll_vote_ok: Your vote has been taking into account, the counters will be updated in a moment. +poll_anonymous_vote_ok: Your vote is taken into account. Votes are anonymous, they + will not be shown to other participants. +poll_vote_ok: Your vote has been taking into account, the counters will be updated + in a moment. moderation_delay: Moderation delay livechat_configuration_channel_moderation_delay_desc: | @@ -588,3 +595,45 @@ livechat_configuration_channel_moderation_delay_desc: | 0: moderation delay disabled Any positive integer: messages will be delayed for X seconds for non-moderator participants, allowing moderators to delete message before any user can read it. + +livechat_configuration_channel_anonymize_moderation_label: "Anonymize moderation actions" +livechat_configuration_channel_anonymize_moderation_desc: | + Anonymize moderation actions default value for new rooms. + When this is enabled, moderation actions will be anonymized, to avoid disclosing who is banning/kicking/… occupants. + +moderator_notes: Moderation notes +moderator_notes_create_error: 'Error when saving the note' +moderator_note_create: 'Create a new note' +moderator_note_description: 'Description' +moderator_note_delete: 'Delete note' +moderator_note_delete_confirm: 'Are you sure you want to delete this note?' +moderator_note_create_for_participant: 'Create a new note' +moderator_note_search_for_participant: 'Search notes' +moderator_note_filters: 'Search filters' +moderator_note_original_nick: 'Nickname of the participant at the time of the note + creation' + +search_occupant_message: 'Search all messages' +message_search: 'Message search' +message_search_original_nick: Nickname of the participant when the message was sent + +prosody_firewall_label: 'Enable Prosody mod_firewall' +prosody_firewall_description: | + You can enable mod_firewall on your Prosody server. + For more information, please check the documentation. +prosody_firewall_configure_button: | + Configure mod_firewall + +prosody_firewall_configuration: Prosody mod_firewall configuration +prosody_firewall_configuration_help: | + Here you can configure Prosody mod_firewall module. + You can create multiple configuration files bellow, and change their order. + Don't hesitate to share your configurations with the community (for example by adding some examples in the plugin documentation). +prosody_firewall_disabled_warning: | + Warning: mod_firewall is disabled in the livechat plugin settings, you have to enable it if you want this configuration to be taken into account. +prosody_firewall_file_enabled: Enabled +prosody_firewall_name: Name +prosody_firewall_name_desc: | + Can only contain: alphanumerical characters, underscores and hyphens. + Scripts will be loaded in alphabetical order. +prosody_firewall_content: File content diff --git a/languages/es.yml b/languages/es.yml index 225dc960..854dd306 100644 --- a/languages/es.yml +++ b/languages/es.yml @@ -103,7 +103,7 @@ no_anonymous_description: "Si se marca, los usuarios anónimos de Peertube no ve el chat.\nFuncionalidad en desarrollo.\nSi la activas, es muy recomendable marcar también \"No publicar información del chat\".\nDe lo contrario, algunas herramientas de terceros podrían intentar abrir el chat , generando comportamientos imprevistos.\n" -converse_theme_option_concord: Tema concord de ConverseJS +converse_theme_option_cyberpunk: Tema cyberpunk de ConverseJS help_builtin_prosody_description: "Este plugin utiliza el servidor XMPP Prosody para manejar salas de chat.\nEste plugin viene con una AppImage de Prosody, que se utilizará para ejecutar el servicio.\n" diff --git a/languages/fr.yml b/languages/fr.yml index fc444fe5..a6a4cba3 100644 --- a/languages/fr.yml +++ b/languages/fr.yml @@ -130,7 +130,7 @@ converse_theme_description: "Merci de choisir le thème ConverseJS que vous voul utiliser." converse_theme_option_peertube: "Thème Peertube" converse_theme_option_default: "Thème par défaut de ConverseJS" -converse_theme_option_concord: "Thème concord de ConverseJS" +converse_theme_option_cyberpunk: "Thème cyberpunk de ConverseJS" autocolors_label: "Détection automatique des couleurs" autocolors_description: | @@ -541,7 +541,8 @@ chat_terms_description: "Ces conditions d'utilisation seront affichées à tous utilisateur⋅rices lorsqu'iels rejoindront les salons de discussion.\nLes streameur⋅euses peuvent également configurer des conditions d'utilisation pour leurs canaux, qui seront affichées juste après les conditions de l'instance.\n" -livechat_configuration_channel_terms_label: Conditions d'utilisation tchat de la chaîne +livechat_configuration_channel_terms_label: Conditions d'utilisation du tchat de la + chaîne livechat_configuration_channel_terms_desc: "Vous pouvez configurer un message de \"\ conditions d'utilisation\" qui sera affiché aux utilisateur⋅rices qui rejoignent vos salons de discussion.\n" @@ -573,3 +574,49 @@ livechat_configuration_channel_moderation_delay_desc: "Valeur par défaut du dé non modérateur⋅rices, ce qui permet à ces derniers de supprimer les messages avant qu'un utilisateur⋅rice ne puisse le lire.\n\n" moderation_delay: Délai de modération +avatar_set_option_none: Aucun +livechat_configuration_channel_anonymize_moderation_label: Anonymiser les actions + de modération +livechat_configuration_channel_anonymize_moderation_desc: "Anonymiser les actions + de modération : valeur par défaut pour les nouveaux salons.\nLorsque cette option + est activée, les actions de modération sont rendues anonymes, afin d'éviter de révéler + qui bannit/expulse/... les occupants.\n" +moderator_notes_create_error: Erreur lors de l'enregistrement de la note +moderator_note_description: Description +moderator_note_delete: Supprimer la note +moderator_note_delete_confirm: Êtes-vous sûr de vouloir supprimer cette note ? +moderator_note_search_for_participant: Recherche de notes +moderator_note_filters: Filtres de recherche +moderator_note_original_nick: Pseudonyme du participant⋅e au moment de la création + de la note +moderator_note_create: Créer une nouvelle note +moderator_note_create_for_participant: Créer une nouvelle note +moderator_notes: Notes de modération +message_search: Recherche de messages +search_occupant_message: Rechercher tous les messages +message_search_original_nick: Pseudonyme du participant⋅e au moment de l'envoi du + message +prosody_firewall_configure_button: "Configurer mod_firewall\n" +prosody_firewall_configuration: Configuration de mod_firewall pour Prosody +prosody_firewall_label: Activer mod_firewall pour Prosody +prosody_firewall_description: "Vous pouvez activer mod_firewall sur votre serveur Prosody.\nPour plus d'informations, + veuillez consulter la documentation.\n" +prosody_firewall_configuration_help: "Ici vous pouvez configurer le module Prosody + mod_firewall.\n + Vous pouvez créer plusieurs fichiers de configuration ci-dessous, et changer leur + ordre.\nN'hésitez pas à partager vos configurations avec la communauté (par exemple + en ajoutant des exemples dans la documentation du plugin).\n" +prosody_firewall_disabled_warning: "Attention : mod_firewall est désactivé dans les + paramètres + du plugin livechat , vous devez l'activer si vous voulez que cette configuration + soit prise en compte.\n" +prosody_firewall_file_enabled: Activé +prosody_firewall_name: Nom +prosody_firewall_name_desc: "Ne peut contenir que des caractères alphanumériques, + des traits de soulignement et des traits d'union.\nLes scripts seront chargés par + ordre alphabétique.\n" +prosody_firewall_content: Contenu du fichier +chat: Tchat diff --git a/languages/gl.yml b/languages/gl.yml index 85f14db7..a0cc8949 100644 --- a/languages/gl.yml +++ b/languages/gl.yml @@ -1,31 +1,30 @@ -open_chat_new_window: Abrir chat en nova ventá -open_chat: Abrir chat -share_chat_link: Compartir ligazón ao chat +open_chat_new_window: Abrir charla en nova xanela +open_chat: Abrir charla +share_chat_link: Compartir ligazón á charla transparent_background: Fondo transparente (para integración coa retransmisión, por exemplo con OBS) -use_chat_help: Se o activas, haberá un chat xunto ao vídeo. +use_chat_help: Se o activas, haberá unha charla xunto ao vídeo. read_only: Só lectura show_scrollbarr: Mostrar barra desprazameto copy: Copiar important_note_title: Notas importantes -important_note_text: "Aquí tes a documentación acerca do complemento:\n\n\ - \ Documentación sobre Peertube Plugin Livechat\n.\n" -chat_title: Chat +important_note_text: "Aquí tes a documentación sobre o complemento:\n\n Documentación sobre Peertube Plugin Livechat\n.\n" +chat_title: Charla connect_using_xmpp_help: Podes conectarte á sala usando unha conta XMPP externa, e o teu cliente XMPP favorito. diagnostic: "Antes de pedir axuda, por favor usa a ferramenta de diagnóstico:\nIniciar o diagnóstico\n - (se o botón non abre outra ventá, intenta actualizar a páxina).\n" + (se o botón non abre outra xanela, intenta actualizar a páxina).\n" online_help: Axuda en liña -close_chat: Pechar chat -use_chat: Usar chat +close_chat: Fechar charla +use_chat: Usar charla link_copied: Ligazón copiada error: Erro open: Abrir use_current_theme_color: Usar esquema de cores actual -generate_iframe: Crear un iframe para incrustar o chat nun sitio web -chat_for_live_stream: 'Chat para a emisión en directo:' +generate_iframe: Crear un marco para incrustar a charla nun sitio web +chat_for_live_stream: 'Charla para a emisión en directo:' room_name: Nome da sala room_description: Descrición da sala not_found: Non se atopa diff --git a/languages/hr.yml b/languages/hr.yml index acae6a4d..fb96a32b 100644 --- a/languages/hr.yml +++ b/languages/hr.yml @@ -209,8 +209,8 @@ share_url_description: Postojat će gumb za dijeljenje URL-a chata (može se na koristiti za OBS integraciju). per_live_video_description: Ako je označeno, sva videa uživo imat će potvrdni okvir u svojim svojstvima za aktiviranje web chata. -per_live_video_label: Korisnici mogu aktivirati chat za svoja videa uživo -all_lives_label: Aktiviraj chat za sva videa uživo +per_live_video_label: Korisnici mogu aktivirati chat za svoje prijenose uživo +all_lives_label: Aktiviraj chat za sve prijenose uživo no_anonymous_label: Sakrij chat za anonimne korisnike theming_advanced_description: Izgled chat_style_description: "Dodatni stilovi koji se dodaju atributu stila iframea. \n @@ -234,15 +234,15 @@ external_auth_custom_oidc_description: "Možeš konfigurirati eksternog pružate external_auth_description: "Eksterna autentifikacija\nZa korisnike koji nemaju Peertube račun možeš aktivirati različite načine autentifikacije pomoću externih pružatelja usluge autentifikacije.\n" -all_lives_description: Ako je označeno, chat će se aktivirati za sva videa uživo. -all_non_lives_description: Ako je označeno, chat će se aktivirati za sva videa koja - nisu uživo. -all_non_lives_label: Aktiviraj chat za sva videa koja nisu uživo -converse_theme_option_concord: ConverseJS concord tema +all_lives_description: Ako je označeno, chat će se aktivirati za sve prijenose uživo. +all_non_lives_description: Ako je označeno, chat će se aktivirati za sve prijenose + koji nisu uživo. +all_non_lives_label: Aktiviraj chat za sve prijenose koji nisu uživo +converse_theme_option_cyberpunk: ConverseJS cyberpunk tema chat_style_label: Atribut stila ifreama za chat prosody_muc_log_by_default_description: "Ako je označeno, sadržaj sobe će se standardno spremati.\nSvaki korisnik koji se pridruži sobi vidjet će što je napisano prije - nego što se pridruži.\nArhiviranje sadržaja se uvijek moguće aktivirati/deaktivirati\n + nego što se pridruži.\nArhiviranje sadržaja se uvijek može aktivirati/deaktivirati\n za određenu sobu, uređivanjem njezinih svojstava.\n" auto_ban_anonymous_ip_label: Blokiraj IP anonimnog korisnika kada je korisniku zabranjen pristup chat sobi @@ -275,3 +275,72 @@ share_chat_embed: Ugradi token_label: Oznaka auth_description: "Autentifikacija\n" chat_terms_label: Uvjeti i odredbe +muted_anonymous_message: Samo registrirani korisnici mogu slati poruke. +livechat_configuration_channel_mute_anonymous_label: Isključi zvuk anonimnih korisnika +login_remote_peertube_video_not_found_try_anyway: U nekim slučajevima, video se ipak + može dohvatiti ako se povežeš s eksternom instancom. +livechat_configuration_channel_moderation_delay_desc: "Standardna vrijednost odgode + moderiranja:\n\n 0: odgoda moderiranja je deaktivirana\n Bilo + koji pozitivni cijeli broj: poruke će se odgoditi za X sekundi za sudionike koji + nisu moderatori, dopuštajući moderatorima da izbrišu poruku prije nego što je bilo + koji korisnik može pročitati.\n\n" +moderation_delay: Odgoda moderiranja +livechat_configuration_channel_forbidden_words_label_desc: Oznaka za ovo pravilo zabranjenih + riječi +livechat_configuration_channel_emojis_desc: "Možeš konfigurirati prilagođene emojije + za svoj kanal.\nOvi emojiji će biti dostupni u alatu za biranje emojija.\nKorisnici + ih također mogu koristiti sa svojim kratkim imenom (na primjer tako da napišu \"\ + :shortname:\").\n" +livechat_emojis_shortname_desc: "Emotikone možeš koristiti u chatu koristeći \":shortname:\"\ + .\nKratko ime može započeti i/ili završiti dvotočkom (:) te koristiti samo alfanumeričke + znakove, podvlake i crtice.\nPreporuča se započeti ih dvotočkom, kako bi korisnici + mogli koristiti automatsko dovršavanje (upisivanjem \":\" i pritiskom tipke tabulatora).\n" +validation_error: Došlo je do greške tijekom provjere valjanosti. +help_builtin_prosody_description: "Ovaj dodatak koristi Prosody XMPP server za upravljanje + sobama za razgovor.\nOvaj dodatak dolazi s Prosody AppImage koji će se koristiti + za pokretanje usluge.\n" +livechat_configuration_channel_terms_desc: "Možeš konfigurirati poruku „uvjeti i odredbe” + koja će se prikazati korisnicima koji se pridružuju tvojim chat sobama.\n" +poll_vote_instructions_xmpp: 'Pošalji poruku s uskličnikom i brojem izbora za glasanje. + Primjer: !1' +invalid_value_file_too_big: 'Datoteka je prevelika (maksimalna veličina: %s).' +per_live_video_warning_description: "\n Postavka „Korisnici mogu aktivirati chat za svoje prijenose uživo” je aktivirana.\n\ + \ Postavka je redundanta s postavkom „Aktiviraj chat za sve prijenose uživo”.\n\ + \n" +new_poll: Stvori novu anketu +invalid_value_missing: Ova je vrijednost obavezna. +invalid_value_wrong_type: Neispravna vrsta vrijednost. +invalid_value_wrong_format: Neispravan format vrijednosti. +invalid_value_not_in_range: Vrijednost ne odgovara dopuštenom rasponu. +invalid_value_duplicate: Dupla vrijednost +too_many_entries: Previše unosa +promote: Postani moderator +livechat_emojis_shortname: Kratko ime +action_import_emojis_info: Ako su uvezeni podaci u redu, ne zaboravi spremiti obrazac. +share_chat_peertube_tips: Ova će poveznica otvoriti chat unutar Peertube sučelja. +token_password: Token lozinke +token_date: Datum +token_action_create: Stvori novi token +token_default_label: Token generiran iz web sučelja +token_action_revoke: Opozovi token +token_action_revoke_confirm: Stvarno želiš opozvati ovaj token? +livechat_token_disabled_label: Deaktiviraj tokene za chat uživo +invalid_value_too_long: Vrijednost je predugačka +livechat_configuration_channel_terms_label: Uvjeti i odredbe kanala chata +poll: Anketa +poll_title: Nova anketa +poll_instructions: Ispuni i pošalji ovaj obrazac za izradu nove ankete. Ovo će zamijeniti + sve postojeće ankete. +poll_question: Pitanje +poll_duration: Trajanje ankete (u minutama) +poll_anonymous_results: Anonimni rezultati +poll_choice_n: 'Izbor {{N}}:' +poll_end: 'Anketa završava:' +poll_vote_instructions: 'Za glasanje klikni na svoj izbor ili pošalji poruku s uskličnikom + iza kojeg slijedi broj tvog izbora (Primjer: !1).' +poll_is_over: Ova anketa je sada završena. +poll_choice_invalid: Ovaj izbor nije valjan. +poll_anonymous_vote_ok: Tvoje se glasanje uzima u obzir. Glasanja su anonimna, neće + se prikazati drugim sudionicima. +poll_vote_ok: Tvoje se glasanje uzima u obzir. Brojači će se uskoro aktualizirati. diff --git a/languages/it.yml b/languages/it.yml index 0f386275..dc416b98 100644 --- a/languages/it.yml +++ b/languages/it.yml @@ -120,7 +120,7 @@ converse_theme_label: "Tema ConverseJS" converse_theme_description: "Scegli il tema converseJS che desideri utilizzare." converse_theme_option_peertube: "Tema Peertube" converse_theme_option_default: "Tema predefinito di ConverseJS" -converse_theme_option_concord: "Tema “Concord“ di ConverseJS" +converse_theme_option_cyberpunk: "Tema “cyberpunk“ di ConverseJS" autocolors_label: "Rilevamento automatico dei colori" autocolors_description: | diff --git a/languages/ja.yml b/languages/ja.yml index 8a796c03..ce141fbd 100644 --- a/languages/ja.yml +++ b/languages/ja.yml @@ -7,7 +7,7 @@ share_chat_link: チャットリンクを共有 read_only: 読み取り専用にする show_scrollbarr: スクロールバーを表示する transparent_background: 透過型背景にする (OBSなどを使用したストリーミング連携に最適) -tips_for_streamers: "ストリーマーの方へ: OBSにチャットを追加する場合は、読み取り専用のチャットリンクを生成し、ブラウザソースで読み込んでください。" +tips_for_streamers: "ストリーマーの方へ: OBSなどを使用して動画ストリームにチャットを埋め込む場合は、読み取り専用のチャットリンクを生成し、ブラウザソースで読み込んでください。\n" copy: コピー link_copied: リンクをコピーしました error: エラー @@ -70,7 +70,7 @@ converse_theme_label: ConverseJSのテーマ converse_theme_description: 使用したいconverseJSのテーマを選択してください。 converse_theme_option_peertube: PeerTubeのテーマ converse_theme_option_default: デフォルトのConverseJSテーマ -converse_theme_option_concord: ConverseJS concordテーマ +converse_theme_option_cyberpunk: ConverseJS cyberpunkテーマ autocolors_label: 色を自動検出 chat_style_label: チャットのiframeスタイル属性 chat_style_description: "追加のスタイル設定をiframeスタイルの属性に追加します。\n例)height:400px;\n" @@ -230,3 +230,150 @@ external_auth_custom_oidc_description: "チャットへのログインに、外 \ target=\"_blank\">設定ページ\n" external_auth_custom_oidc_button_label_description: このラベルは、OIDCプロバイダーを使用して認証するボタン名としてユーザーに表示されます。 copied: コピーしました +chat_terms_label: 利用規約 +tasks: タスク +task_list_create: '新規タスクリストを作成:' +task_list_name: タスクリスト名 +task_list_create_error: タスクリストを保存中にエラーが発生しました +task_delete_confirm: このタスクを削除してもよろしいですか? +task_delete: タスクを削除 +external_auth_oidc_redirect_uris_info_description: "Callback/Redirect URI:\n\ + 外部アプリケーション上に認証されたリダイレクションURIを設定したい場合は、このURLを追加してください:\n" +external_auth_google_oidc_label: Googleを使用 +login_using_external_account: 外部アカウントを使用してログイン +login_remote_peertube: 'ほかのPeerTubeインスタンスのアカウントを使用してログイン:' +login_remote_peertube_url: あなたのPeerTubeインスタンスURL +login_remote_peertube_url_invalid: 無効なPeerTube URLです。 +login_remote_peertube_no_livechat: このPeerTubeインスタンスには、ライブチャットプラグインがインストールされていません。 +login_remote_peertube_video_not_found_try_anyway_button: PeerTubeインスタンスで動画を開く +livechat_configuration_channel_anonymize_moderation_label: モデレーション操作を匿名か +moderator_notes_create_error: ノートを保存中にエラーが発生しました +moderator_note_create: 新規ノートを作成 +moderator_note_description: 概要 +moderator_note_delete: ノートを削除 +moderator_note_delete_confirm: このノートを削除してもよろしいですか? +moderator_note_create_for_participant: 新規ノートを作成 +moderator_note_filters: フィルタ検索 +moderator_note_original_nick: ノート作成時の参加者のニックネーム +prosody_c2s_interfaces_label: クライアントとサーバー間のネットワークインタフェース +login_remote_peertube_video_not_found: この動画はこのPeerTubeインスタンス上では利用できません。 +avatar_set_option_none: なし +validation_error: 検証中にエラーが発生しました。 +invalid_value_missing: この項目は必須です。 +invalid_value_wrong_type: 誤った形式の値です。 +too_many_entries: エントリーが多すぎます +chatroom_not_accessible: このチャットルームは存在しないか、アクセス権限がありません。 +login_remote_peertube_searching: PeerTubeインスタンス上の動画を検索中... +login_remote_peertube_video_open_failed: 'お使いのブラウザーはリモートインスタンスを開くことがブロックされています。このリンクを手動で開いてください:' +task_list_delete: タスクリストを削除 +task_list_delete_confirm: このタスクリストを削除してもよろしいですか? +task_create: 新規タスクを作成 +task_name: タスク名 +task_list_pick_title: タスクリストを選択してください +livechat_emojis_shortname: 短縮名 +livechat_emojis_shortname_desc: "チャット上で、絵文字を\":shortname:\"で使用できます。\n短縮名は、先頭と末尾にコロン(:)があり、英数字、アンダーバー、ハイフンのみが含まれている必要があります。\n\ + 自動補完(\":\"を入力後、Tabキーを押下)を利用できるようコロンから入力することを強くお勧めします。\n" +livechat_emojis_file: ファイル +promote: モデレーターにする +livechat_emojis_file_desc: "絵文字に使用するファイルです。\n" +action_import: インポート +action_export: エクスポート +action_import_emojis_info: インポートしたデータが問題ない場合、フォームを保存することを忘れないでください。 +action_remove_entry: このエントリーを削除 +action_remove_entry_confirm: このエントリーを削除してもよろしいですか? +loading_error: データの読み込み中にエラーが発生しました。 +share_chat_embed: 埋め込み +share_chat_peertube_tips: このリンクで、PeerTubeインスタンス内でチャットが開きます。 +token_password: パスワードトークン +token_date: 日付 +token_action_create: 新規トークンを作成 +token_action_revoke: トークンを無効化 +token_default_label: Web UIから生成されたトークン +livechat_token_disabled_description: "ユーザーはチャットに接続するための長期的なトークンを生成できます。\nこれらのトークンは、OBSのカスタムブラウザドックなどで使用できます。\n + 詳細は、ドキュメントをご確認ください。\nこの設定にチェックをつけることで、この機能を無効にできます。\n" +muted_anonymous_message: 登録済みユーザーのみがメッセージを送信できます。 +livechat_configuration_channel_mute_anonymous_label: 匿名ユーザーをミュート +livechat_configuration_channel_mute_anonymous_desc: "新規チャットルームのデフォルト値です。\n既存のチャットルームは、ルーム設定フォーム内の機能で変更できます。\n\ + この設定が有効の場合、匿名ユーザーはチャットは閲覧のみ可能で、メッセージの送信はできません。\n" +livechat_configuration_channel_terms_label: チャンネルのチャットルームの利用規約 +livechat_configuration_channel_terms_desc: "ユーザーがチャットルームに参加する際に表示される\"利用規約\"メッセージを設定します。\n" +new_poll: 新規投票を作成 +poll: 投票 +poll_title: 新規投票 +poll_instructions: 投票を作成するには、内容を設定して送信してください。既存の投票は終了し、この投票で置き換えられます。 +poll_question: 質問 +poll_duration: 投票期間 (分) +poll_anonymous_results: 匿名の結果 +poll_choice_n: '選択肢 {{N}}:' +poll_end: '投票は次で終了:' +poll_vote_instructions: '投票するには、選択肢をクリックするか、感嘆符(!)を付与した後に選択肢番号を入力してメッセージを送信してください(例: + !1)。' +poll_vote_instructions_xmpp: '投票するには、感嘆符(!)を付与した後に選択肢番号を入力してメッセージを送信してください。例: !1' +poll_is_over: この投票は終了しました。 +poll_vote_ok: あなたの投票は受け付けられ、まもなくカウンターが更新されます。 +moderation_delay: モデレーションの遅延時間 +livechat_configuration_channel_moderation_delay_desc: "モデレーションの遅延時間のデフォルト値:\n\n\ + \ 0: モデレーション遅延無効\n 任意の整数値n: モデレーター以外の参加者からのメッセージはn秒後に表示されるようになり、モデレーターはメッセージが他の参加者に読まれる前に削除することができます。\n\ + \n" +livechat_configuration_channel_forbidden_words_label_label: ラベル +livechat_configuration_channel_forbidden_words_label_desc: この禁止語規則のラベル +login_remote_peertube_video_not_found_try_anyway: 場合によっては、外部インスタンスに接続できれば動画を取得できることがあります。 +login_external_auth_alert_message: 認証に失敗しました +invalid_value_not_in_range: 値が許可された範囲にありません。 +search_occupant_message: 全てのメッセージを検索 +message_search: メッセージ検索 +chat_terms_description: "この利用規約はチャットルームに参加する際に全てのユーザーに表示されます。\n配信者はこの利用規約に加えて、チャンネル独自の利用規約を設定できます。チャンネル独自の利用規約は全てのユーザー向けの利用規約の右側に表示されます。\n" +invalid_value_too_long: 値が大きすぎます +moderator_notes: モデレーションノート +moderator_note_search_for_participant: ノートを検索 +message_search_original_nick: メッセージ送信時の参加者のニックネーム +prosody_firewall_label: Prosody mod_firewallを有効にする +prosody_firewall_configure_button: "mod_firewallを設定\n" +prosody_firewall_file_enabled: 有効 +prosody_firewall_name: 名前 +prosody_firewall_name_desc: "次の値のみ利用可能: 英数字、アンダーバー、ハイフン。\nスクリプトは、英字順で読み込まれます。\n" +prosody_firewall_content: ファイルコンテンツ +token_label: ラベル +task_description: 概要 +livechat_configuration_channel_emojis_title: チャンネルの絵文字 +invalid_value_file_too_big: 'ファイルサイズが大きすぎます(最大ファイルサイズ: %s)。' +action_add_entry: エントリーを追加 +share_chat_dock: ドック +livechat_token_disabled_label: ライブチャットトークンを無効化 +token_action_revoke_confirm: 子のトークンを無効化してよろしいですか? +auth_description: "認証\n" +poll_choice_invalid: この選択肢は無効です。 +poll_anonymous_vote_ok: あなたの投票を受け付けました。投票は匿名で行われ、他の参加者には表示されません。 +task_list_pick_message: "タスクリストを選択すると、新しいタスクが作成されます。\nタスクを確認するには、トップメニューからタスクアプリケーションを開いてください。\n\ + 詳細については、ライブチャットプラグインのドキュメントを参照してください。\n" +share_chat_dock_tips: "チャットページを新規ページかつPeerTubeアカウントにログインした状態で開くリンクを生成できます。\n例えばこのリンクをOBSのカスタムブラウザドックに設定し、OBS上から直接チャットを読んだり、反応することができるようになります。\n\ + このリンクは、あなた自身として接続するため、外部には絶対に公開しないでください。\n既に生成した認証トークンのリストを以下から見つけてください。\n新しいトークンを生成したり、既に生成済みのトークンを無効化できます。\n\ + これらのトークンには有効期限はありません。\n" +livechat_configuration_channel_anonymize_moderation_desc: "新しいルームに対するモデレーションアクションの匿名化をデフォルトにします。 + \nこれを有効にすると、誰がユーザーを追放、キックなどのモデレーションアクションを実施したのかを匿名化します。\n" +prosody_firewall_description: "お使いのProsodyサーバーのmod_firewallを有効にできます。\n詳しくは、ドキュメントをご確認ください。\n" +prosody_firewall_configuration: Prosody mod_firewallの設定 +prosody_firewall_configuration_help: "ここでProsody mod_firewallモジュールの設定が可能です。\n複数の設定ファイルを作成し、以下から順番を変更できます。\n\ + 最適な設定が出来たら、コミュニティメンバーと設定を共有してみてください(プラグインドキュメントに掲載させていただくこともあります)。\n" +prosody_firewall_disabled_warning: "警告: mod_firewallは、ライブチャットのプラグイン設定で無効になっており、この設定を反映するには、機能を有効にする必要があります。\n" +task_list_pick_empty: タスクリストが存在しません。新規作成してください +external_auth_facebook_oidc_label: Facebookを使用 +external_auth_facebook_oidc_description: "有効にすると\"Facebookでログイン\"ボタンを追加します。\nFacebook + OAuthアプリケーションの設定が必要です。\n" +external_auth_google_oidc_description: "有効にすると\"Googleでログイン\"ボタンを追加します。\nGoogle OAuthアプリケーションの設定が必要です。\n" +prosody_c2s_interfaces_description: "クライアントとサーバー間接続のためにリッスンするネットワークインターフェイスです。\n\ + この設定は、より高度な設定が可能なユーザー向けに提供されています。設定内容を完全に理解できていない場合は変更しないでください。\nリッスンするIPのリストをコンマ区切り(スペースは削除されます)で指定してください。\n\ + «*»を指定することで全てのIPv4アドレス、«::»を指定することで全てのIPv6アドレスをリッスンします。\n例:\n\n *, + ::\n *\n 127.0.0.1, ::1\n 127.0.0.1, ::1, 172.18.0.42\n\ + \n" +invalid_value_wrong_format: 誤ったフォーマットの値です。 +invalid_value_duplicate: 重複した値です +livechat_configuration_channel_emojis_desc: "チャンネルのカスタム絵文字を設定できます。\nこれらの絵文字は、絵文字ピッカーから利用できます。\n\ + 絵文字は、短縮名でも使用できます。(例えば\":shortname:\")\n" +chat: チャット diff --git a/languages/sq.yml b/languages/sq.yml index e69de29b..acd56b38 100644 --- a/languages/sq.yml +++ b/languages/sq.yml @@ -0,0 +1,248 @@ +muted_anonymous_message: Mesazhe mund të dërgojnë vetëm përdorues të regjistruar. +moderation_delay: Vonesë moderimi +share_url_label: Shfaq butonin “Ndani me të tjerët lidhje fjalosjeje” +no_anonymous_label: Fshihe fjalosjen për përdorues anonimë +theming_advanced_description: Aplikim temash +login_remote_peertube_url_invalid: URL PeerTube e pavlefshme. +tasks: Punë +task_list_create: 'Krijoni një listë të re punësh:' +task_list_create_error: Gabim teksa ruhej lista e punëve +task_list_delete_confirm: Jeni i sigurt se doni të fshihet kjo listë punësh? +avatar_set_option_abstract: Abstrakte +prosody_s2s_interfaces_label: Ndërfaqe rrjeti shërbyes te shërbyes +per_live_video_label: Përdoruesit mund të aktivizojnë fjalosje për transmetimet e + tyre të drejtpërdrejta +successfully_saved: U ruajtën me sukses +livechat_configuration_channel_quote_delay_label: Dërgoje çdo X minuta +livechat_configuration_title: Formësoni dhoma fjalosjeje të transmetimit tuaj të drejtpërdrejtë +avatar_set_label: Grup avatarësh +prosody_s2s_port_label: Portë Prosody shërbyes te shërbyes +prosody_c2s_label: Aktivizo lidhje klient te shërbyes +livechat_configuration_channel_bot_options_title: Mundësi roboti moderimi +livechat_configuration_channel_forbidden_words_label: Fjalë, ose shprehje të ndaluara +livechat_configuration_channel_forbidden_words_regexp_label: Konsideroji shprehje + të rregullta +livechat_configuration_channel_command_label: Urdhër roboti +prosody_c2s_interfaces_label: Ndërfaqe rrjeti klient te shërbyes +livechat_configuration_channel_command_message_label: Mesazh +task_list_name: Emër liste punësh +promote: Bëhuni moderator +open_chat: Hapni fjalosje +open_chat_new_window: Hap fjalosjen në dritare të re +read_only: Vetëm për lexim +show_scrollbarr: Shfaq rrëshqitës +close_chat: Mbylle fjalosjen +use_chat: Përdorni fjalosjen +use_chat_help: Në u aktivizoftë, në krah të videos do të ketë një fjalosje. +share_chat_link: Ndajeni lidhjen e fjalosjes me të tjerët +copy: Kopjoje +copied: U kopjua +use_current_theme_color: Përdor ngjyra të temës së tanishme +room_name: Emër dhome +room_description: Përshkrim dhome +not_found: S’u gjet +video: Video +important_note_title: Shënim të rëndësishme +chat_terms_label: Terma & Kushte +channel: Kanal +last_activity: Veprimtaria e fundit +connect_using_xmpp: Lidhu duke përdorur XMPP +connect_using_xmpp_help: Mund të lidheni me dhomën duke përdorur një llogari të jashtme + XMPP dhe klientin tuaj të parapëlqyer XMPP. +list_rooms_label: Paraqit dhoma ekzistuese +list_rooms_description: "Paraqit dhomat\n" +external_auth_description: "Mirëfilltësim së jashtmi\nPër përdorues që s’kanë + llogari Peertube, mund të aktivizoni mënyra të ndryshme mirëfilltësimi, bazuar në + shërbyes të largët mirëfilltësimesh.\n" +external_auth_custom_oidc_button_label_label: Etiketë për butonin e lidhjes +external_auth_oidc_client_id_label: ID Klienti +external_auth_oidc_client_secret_label: E fshehtë klienti +external_auth_facebook_oidc_label: Përdor Facebook +open_blank_description: Do të ketë një buton për hapje dritareje fjalosjeje në një + dritare të re. +room_type_option_video: Çdo video ka dhomën e vet të fjalosjes +share_url_option_nobody: Mos e shfaq për ndonjë +room_type_option_channel: Dhomat e fjalosjes grupohen sipas kanaleve +share_url_option_everyone: Shfaqe për këdo +videos_list_label: Aktivizo fjalosje për këto video +avatar_set_option_sepia: Sepia (simboli i PeerTube-it) +converse_theme_label: Temë ConverseJS +converse_theme_option_peertube: Temë PeerTube +converse_theme_option_default: Tema Parazgjedhje ConverseJS +system_prosody_label: Përdor Prosody sistemi +disable_websocket_label: Çaktivizo Websocket-in +prosody_peertube_uri_label: URL Peertube për thirrje API +cancel: Anuloje +livechat_configuration_channel_slow_mode_label: Mënyra ngadalë +livechat_configuration_channel_enable_bot_label: Aktivizo robot moderimi +prosody_muc_expiration_description: "Këtu mund të zgjidhni se për sa kohë mbahet lënda + e dhomës së fjalosjes nga shërbyesi. Vlera mund të jetë:\n\n 60: + lënda do të ruhet për 60 sekonda. 60-n mund ta zëvendësoni me çfarëdo vlere + numër të plotë.\n 1d: lënda do të ruhet për 1 ditë. 1-shin + mund ta zëvendësoni me çfarëdo vlere numër të plotë.\n 1w: lënda + do të ruhet për 1 javë. 1-shin mund ta zëvendësoni me çfarëdo vlere numër + të plotë.\n 1m: lënda do të ruhet për 1 muaj. 1-shin mund + ta zëvendësoni me çfarëdo vlere numër të plotë.\n 1y: lënda do + të ruhet për 1 vit. 1-shin mund ta zëvendësoni me çfarëdo vlere numër të + plotë.\n never: lënda nuk skadon kurrë dhe do të mbahet përgjithnjë.\n\ + \n" +livechat_configuration_channel_forbidden_words_reason_label: Arsye +livechat_configuration_channel_forbidden_words_comments_label: Komente +livechat_configuration_channel_quote_label2: Mesazhe +livechat_configuration_channel_command_message_desc: Mesazhi për t’u dërguar. +livechat_configuration_channel_banned_jids_label: Përdorues dhe rregullsi të dëbuara +validation_error: Pati një gabim gjatë vleftësimit. +invalid_value: Vlerë e pavlefshme. +invalid_value_missing: Kjo vlerë është e domosdoshme. +invalid_value_wrong_type: Vlera është e një lloji të gabuar. +invalid_value_wrong_format: Vlera është në format të gabuar. +invalid_value_not_in_range: Vlerë jo brenda intervalit të autorizuar. +invalid_value_duplicate: Vlerë e përsëdytur +invalid_value_too_long: Vlerë shumë e gjatë +too_many_entries: Shumë zëra +chatroom_not_accessible: Kjo dhomë fjalosjeje s’ekziston, ose s’lejoheni të hyni në + të. +login_using_external_account: Bëni hyrjen duke përdorur një llogari të jashtme +login_remote_peertube_url: URL e instancës tuaj Peertube +login_remote_peertube_video_not_found: Kjo video s’është në këtë instancë Peertube. +login_external_auth_alert_message: Mirëfilltësimi dështoi +task_list_delete: Fshi listë punësh +task_create: Krijoni një punë të re +task_name: Emër pune +task_description: Përshkrim +task_delete_confirm: Jeni i sigurt se doni të fshihet kjo punë? +task_list_pick_title: Ju lutemi, zgjidhni një listë punësh +livechat_configuration_channel_emojis_title: Emoxhi kanali +livechat_emojis_shortname: Emër i shkurtër +livechat_emojis_file_desc: "Kartela e emoji-t.\n" +action_export: Eksportoje +action_remove_entry_confirm: Jeni i sigurt se doni të hiqet ky zë? +loading_error: Ndodhi gabim teksa ngarkoheshin të dhëna. +token_password: Token fjalëkalimi +token_date: Datë +token_action_revoke: Hiqe token-in +token_default_label: Token i prodhuar nga ndërfaqja web +token_action_create: Krijoni një token të ri +token_action_revoke_confirm: Jeni i sigurt se doni të hiqet ky token? +livechat_configuration_channel_mute_anonymous_label: Heshto përdorues anonimë +livechat_configuration_channel_terms_label: Terma & kushte kanali fjalosjeje +new_poll: Krijoni një pyetësor të ri +poll_title: Pyetësor i ri +poll_question: Pyetje +poll_duration: Kohëzgjatje pyetësori (në minuta) +poll_anonymous_results: Përfundime anonime +poll_is_over: Ky pyetësor tanimë ka përfunduar. +poll_choice_invalid: Kjo zgjedhje s’është e vlefshme. +chat_behaviour_description: Sjellje fjalosjeje +room_type_label: Lloj dhome +auto_display_description: Kur shihet një video, dritarja e fjalosjes do të hapet vetvetiu. +open_blank_label: Shfaq butonin “hape në dritare të re” +all_lives_label: Aktivizo fjalosje për krejt të drejtpërdrejtat +converse_theme_description: Ju lutemi, zgjidhni temën converseJS që doni të përdoret. +prosody_port_label: Portë Prosody +prosody_muc_log_by_default_label: Regjistro ç’bëhet në dhomë, si parazgjedhje +prosody_room_allow_s2s_label: Aktivizoni lidhje te dhoma duke përdorur llogari të + jashtme XMPP +prosody_certificates_dir_label: Dosje dëshmish +livechat_configuration_channel_forbidden_words_label_label: Etiketë +task_delete: Fshije punën +avatar_set_option_bird: Zogj +autocolors_label: Pikasje e vetvetishme ngjyrash +help_builtin_prosody_label: Shërbyes Prosody +prosody_c2s_port_label: Klient Prosody te portë shërbyesi +prosody_components_list_label: Përbërës të jashtëm +prosody_components_port_label: Portë përbërësish Prosody të jashtëm +livechat_configuration_channel_command_cmd_label: Urdhër +livechat_configuration_channel_title: Mundësi kanali +prosody_muc_expiration_label: Skadim regjistrash dhome +livechat_configuration_channel_quote_label: Kohëmatës +livechat_configuration_channel_bot_nickname: Nofkë roboti +federation_no_remote_chat_label: Mos shfaq fjalosje nga larg +federation_no_remote_chat_description: "Duke i vënë shenjë këtij rregullimi, instanca + juaj s’do të shfaqë kurrë fjalosje prej videosh të largëta.\n" +federation_dont_publish_remotely_label: Mos boto hollësifjalosjeje +auto_display_label: Hape vetvetiu fjalosjen +livechat_emojis_file: Kartelë +action_import: Importoje +share_url_option_owner: Shfaq të zotin e videos +avatar_set_option_cat: Mace +share_chat_embed: Trupëzojeni +share_chat_peertube_tips: Kjo lidhje do ta hapë fjalosjen brenda ndërfaqes së Peertube-it. +auth_description: "Mirëfilltësim\n" +external_auth_google_oidc_label: Përdor Google +save: Ruaje +chat_title: Fjalosje +action_add_entry: Shtoni një zë +action_remove_entry: Hiqe këtë zë +token_label: Etiketë +poll: Pyetësor +poll_choice_n: 'Zgjedhje {{N}}:' +poll_end: 'Pyetësori përfundon më:' +link_copied: Lidhja u kopjua +error: Gabim +open: Hape +web: Web +online_help: Ndihmë në Internet +federation_description: "Federim\nRregulliemt vijuese kanë të bëjnë me federimin + me instanca të tjera\nPeertube dhe programe të tjera fediversi.\n" +invalid_value_file_too_big: 'Madhësia e kartelës është shumë e madhe (madhësi maksimum: + %s).' +all_non_lives_description: Në iu vëntë shenjë, fjalosja do të jetë e aktivizuar për + krejt videot që s’janë transmetimet e drejtpërdrejta. +external_auth_custom_oidc_title: OpenID Connect +external_auth_custom_oidc_label: Përdorni një shërbim OpenID Connect +external_auth_custom_oidc_button_label_description: Kjo etiketë do t’ju shfaqet përdoruesve, + si etiketa e butonit për kryerje mirëfilltësimi me këtë shërbim OIDC. +per_live_video_description: Në iu vëntë shenjë, krejt videot e transmetuara drejtpërdrejt + do të kenë një kutizë te vetitë e tyre, për aktivizim fjalosjeje. +livechat_configuration_channel_moderation_delay_desc: "Vlerë parazgjedhjeje vonese + moderimi:\n\n 0: vonesë moderimi e çaktivizuar\n Çfarëdo numër + i plotë pozitiv: mesazhet do të vonohen për X sekonda për pjesëmarrës që s’janë + moderatorë, duke u lejuar moderatorëve të fshijnë mesazhe para se të mund t’i lexojë + cilido përdorues.\n\n" +all_lives_description: Në iu vëntë shenjë, fjalosja do të jetë e aktivizuar për krejt + transmetimet e drejtpërdrejta. +all_non_lives_label: Aktivizo fjalosje për krejt transmetimet jo të drejtpërdrejta +room_type_description: Këtu mund të zgjidhni të keni dhoma më vete për çdo video, + ose t’i gruponi sipas kanalesh. +share_url_option_owner_moderators: Shfaqe për të zotin e videos dhe moderatorët e + instancës +poll_vote_instructions: 'Që të votoni, klikoni mbi zgjedhjen tuaj, ose dërgoni një + mesazh me një pikëçuditje pasuar nga numri që keni zgjedhur (Shembull: !1).' +external_auth_custom_oidc_description: "Mund të formësoni një shërbim të jashtëm OpenID + që mund të përdoret për të bërë hyrjen te fjalosja.\nJu lutemi, shihni te dokumentimi:\n + Rregullime.\n" +external_auth_google_oidc_description: "Aktivizimi i kësaj shton një buton “hyrje + me Google”.\nJu duhet të formësoni një aplikacion Google OAuth.\n" +external_auth_facebook_oidc_description: "Aktivizimi i kësaj shton një buton “hyrje + me Facebook”.\nJu duhet të formësoni një aplikacion Facebook OAuth.\n" +share_url_description: Do të ketë një buton për dhënien e URL-së së fjalosjes (mund + të përdoret për integrim OBS, për shembull). +avatar_set_description: "Avatarët parazgjedhje që do të përdoren për përdoruesit i + fjalosjes mund t’i zgjidhni nga disa grupe të ndryshme.\nJu lutemi, shihni te dokumentimi:\n + Rregullime.\n" +livechat_configuration_channel_mute_anonymous_desc: "Vlerë parazgjedhje për dhoma + të reja fjalosjeje.\nPër dhoma ekzistuese, mund të ndryshoni veçorinë te formulari + i formësimit të dhomave.\nKur kjo veçori është e aktivizuar, përdoruesit anonimë + vetëm se do të lexojnë fjalosjen dhe s’mund të dërgojnë mesazhe.\n" +transparent_background: Sfond i tejdukshëm (për integrim transmetimi, me OBS për shembull) +livechat_configuration_channel_emojis_desc: "Mund të formësoni emoxhi vetjake për + kanalin tuaj.\nKëto emoxhi do të jenë të përdorshëm që nga zgjedhësi i tyre.\nPërdoruesit + mund t’i përdoren edhe përmes emrit të shkurtër përkatës (për shembull, duke shkruar + \":emrineshkurtër:\").\n" +important_note_text: "Dokumentimin e shtojcës mund ta gjeni këtu:\n\n Dokumentim Shtojce Peertube Livechat\n.\n" +action_import_emojis_info: Nëse të dhënat e importuara janë në rregull, mos harroni + të ruani formularin. +livechat_configuration_channel_terms_desc: "Mund të formësoni një ndarje “terma & + kushte” që do t’u shfaqet përdoruesve që hyjnë në dhomat tuaja.\n" +poll_vote_instructions_xmpp: 'Që të votoni, dërgoni një mesazh me një pikëçuditje + pasuar nga numri që keni zgjedhur. Shembull: !1' +poll_anonymous_vote_ok: Vota juaj është marrë parasysh. Votat janë anonime, s’do t’u + shfaqen pjesëmarrësve të tjerë. +poll_vote_ok: Vota juaj është marrë parasysh, numëratorët do të përditësohen në çast. +moderator_notes: Shënime moderimi +chat: Fjalosje diff --git a/languages/sv.yml b/languages/sv.yml index f78c464e..3ed89487 100644 --- a/languages/sv.yml +++ b/languages/sv.yml @@ -28,7 +28,7 @@ room_type_description: Här kan du välja om du vill ha separata rum för varje theming_advanced_description: Utseende converse_theme_option_peertube: PeerTubes tema converse_theme_option_default: ConverseJS standardtema -converse_theme_option_concord: ConverseJS-temat concord +converse_theme_option_cyberpunk: ConverseJS-temat cyberpunk autocolors_label: Automatisk färgigenkänning save: Spara cancel: Avbryt diff --git a/languages/zh-Hant.yml b/languages/zh-Hant.yml index e69de29b..6f2e048d 100644 --- a/languages/zh-Hant.yml +++ b/languages/zh-Hant.yml @@ -0,0 +1,174 @@ +no_anonymous_description: "若勾選,匿名 Peertube 用戶將看不到聊天資訊。\n此功能仍處於實驗階段。\n如果您啟用了它,強烈建議您也勾選「不發布聊天訊息」。\n\ + 否則,某些第三方工具可能會嘗試開啟聊天,並出現不可預測的行為。\n" +external_auth_custom_oidc_description: "您可以設定可用於登入聊天的外部 OpenID Connect 提供者。\n請參考檔案:\n + 設定。\n" +room_type_option_video: 每個影片都有自己的網路聊天室 +login_external_auth_alert_message: 驗證失敗 +task_list_pick_empty: 現在沒有任何任務,請創建第一個 +task_list_pick_message: "選擇任務清單後,將建立新任務。\n若要查看任務,請使用頂部選單開啟任務應用程式。\n即時聊天外掛檔案中的更多資訊。\n" +livechat_configuration_channel_enable_bot_label: 啟用審核機器人 +login_remote_peertube_video_not_found: 該影片在此 Peertube 實例上不可用。 +menu_configuration_label: 聊天室 +livechat_configuration_title: 您的直播聊天室設定 +livechat_configuration_desc: 您可以在此處為與直播相關的聊天室設定一些進階選項。 +system_prosody_description: "警告:如果您不確定自己在做什麼,請不要檢查此設定。\n透過檢查此設置,您的 Peertube 將使用系統附帶的 + Prosody 伺服器,\n而不是嵌入的 AppImage。\n僅當您遇到嵌入式 Prosody 問題時才使用此選項。\n" +configuration_description: "頻道進階組態\n以下設定涉及進階頻道選項:\n使用者將能夠在他們的頻道上新增客製化,\n\ + 啟動審核機器人,...\n" +disable_websocket_label: 停用 Websocket +save: 儲存 +successfully_saved: 存檔成功 +livechat_configuration_please_select: 請在下面選擇您的頻道之一,以設定其聊天選項。 +livechat_configuration_channel_title: 頻道選項 +livechat_configuration_channel_desc: 您可以在此處為此頻道設定一些選項(審核策略,...)。 +livechat_configuration_channel_forbidden_words_label: 禁止使用的單字或句子 +livechat_configuration_channel_forbidden_words_desc: "您可以組態一些由機器人自動審核的單字(包含此類單字的訊息將立即刪除)。\n\ + 您也可以新增一個可選原因,該原因將顯示在已刪除郵件的位置。\n檔案頁面上提供了幾個範例。\n" +livechat_configuration_channel_forbidden_words_desc2: "每行只能一個單字或句子。如果將多個單字放在一行上,它將只配取到包含整行的訊息。\n" +livechat_configuration_channel_command_cmd_label: 命令 +livechat_configuration_channel_for_more_info: "有關如何設定此功能的更多資訊,請按一下幫助按鈕參考檔案。\n" +livechat_configuration_channel_banned_jids_label: 禁止的使用者和模式 +validation_error: 驗證時出現錯誤。 +invalid_value: 無效的值。 +invalid_value_missing: 該欄必填。 +invalid_value_wrong_type: 該欄不接受這種型別。 +invalid_value_wrong_format: 錯誤的格式。 +invalid_value_not_in_range: 值不在符合範圍內。 +invalid_value_file_too_big: '檔案太大(max size: %s).' +invalid_value_duplicate: 重覆值 +too_many_entries: 太多項目 +chatroom_not_accessible: 這個聊天室不存在或者您無法訪問。 +login_using_external_account: 使用外部帳戶登入 +login_remote_peertube: 使用另一個 Peertube 實例上的帳號登入: +login_remote_peertube_url: 您的Peertube實例網址 +login_remote_peertube_searching: 在 Peertube 實例上搜尋影片... +login_remote_peertube_video_not_found_try_anyway: 在某些情況下,如果您連接到遠端實例,仍然可以檢索視訊。 +login_remote_peertube_video_not_found_try_anyway_button: 無論如何嘗試在 Peertube 實例上打開影片 +login_remote_peertube_video_open_failed: 您的瀏覽器阻止了遠端實例的啟動,請嘗試手動開啟此連結: +tasks: 任務 +task_list_create: '創建新任務清單:' +task_list_create_error: 儲存任務清單時發生錯誤 +task_list_name: 任務清單名稱 +task_create: 創建新任務 +task_name: 任務名稱 +task_description: 任務用途 +task_delete: 刪除任務 +task_delete_confirm: 您確定要刪除此任務? +task_list_pick_title: 請選擇任務清單 +promote: 成為版主 +livechat_configuration_channel_emojis_title: 頻道emojis +livechat_configuration_channel_bot_options_title: 審核機器人選項 +livechat_configuration_channel_forbidden_words_reason_label: 原因 +livechat_configuration_channel_command_message_desc: 發送訊息。 +slow_mode_info: 限速模式已啟用,使用者可以每 %1$s 秒發送一則訊息。 +disable_channel_configuration_label: 停用進階頻道設定和聊天機器人 +cancel: 取消 +livechat_configuration_channel_forbidden_words_reason_desc: 除已刪除訊息外還顯示的原因 +error: 錯誤 +use_current_theme_color: 使用當前主題顏色 +chat_title: 聊天 +federation_no_remote_chat_description: "透過檢查此設置,您的實例將永遠不會顯示來自遠端視訊的聊天。\n" +federation_dont_publish_remotely_label: 不公開聊天資訊 +all_lives_label: 所有直播啟動聊天室 +no_anonymous_label: 隱藏匿名使用者的聊天 +converse_theme_label: ConverseJS 主題 +converse_theme_description: 請選擇您要使用的 converseJS 主題。 +converse_theme_option_default: 預設ConverseJS主題 +chat_style_label: 聊天 iframe 風格屬性 +prosody_peertube_uri_label: Peertube API呼叫網址 +task_list_delete: 刪除任務清單 +task_list_delete_confirm: 您確定要刪除此任務清單? +federation_description: "聯盟\n以下設定涉及與其他 Peertube 實例的聯合,\n和其他聯邦多元化軟體。\n" +chat_terms_label: 條款及條件 +list_rooms_label: 列出現有聊天室 +federation_no_remote_chat_label: 不顯示遠端聊天 +external_auth_description: "外部身份驗證\n對於沒有 Peertube 帳戶的用戶,您可以基於遠端身份驗證提供者啟用各種身份驗證模式。\n" +prosody_advanced_description: 聊天伺服器進階設定 +invalid_value_too_long: 值超出範圍 +login_remote_peertube_no_livechat: 此 Peertube 實例上未安裝即時聊天外掛程式。 +login_remote_peertube_url_invalid: 無效的Peertube網址。 +theming_advanced_description: 主題 +link_copied: 連結已複製 +show_scrollbarr: 顯示聊天捲軸 +transparent_background: 透明背景(用於串流媒體整合,例如OBS) +room_type_option_channel: 網路聊天室按頻道分組 +auto_display_label: 自動開啟聊天 +share_url_option_everyone: 任何人皆可看到 +share_url_option_owner: 只對影片擁有者顯示 +livechat_configuration_channel_slow_mode_label: 限速模式 +livechat_configuration_channel_slow_mode_desc: "限速模式預設值:\n\n 0:停用限速模式\n + 任何正整數:使用者可以每 X 秒發送一條訊息(審核者不受限制)\n\n" +prosody_firewall_name: 名字 +prosody_firewall_file_enabled: 啟用 +livechat_configuration_channel_emojis_desc: "您可以為您的頻道組態自訂表情符號。\n這些表情符號將在表情符號選擇器中提供。\n\ + 使用者也可以透過其短名稱來使用它們(例如透過編寫“:shortname:”)。\n" +livechat_emojis_shortname: 簡稱 +livechat_emojis_file: 檔案 +action_import: 匯入 +action_export: 匯出 +list_rooms_description: "列出聊天室\n" +autocolors_description: "嘗試自動偵測使用者目前主題的顏色。\n啟用此設定後,外掛程式會嘗試自動偵測顏色以套用至聊天主題。\n如果這對於您的某些 + Peertube 主題無法正常運作,您可以停用此選項。\n您可以向官方報告該錯誤\n\n 問題追蹤器\n。不要忘記指定哪個主題不起作用。\n" +prosody_firewall_content: 檔案內容 +prosody_firewall_disabled_warning: "警告:mod_firewall 在即時聊天外掛設定中被停用,如果您希望此組態生效,則必須啟用它予以考慮。\n" +disable_websocket_description: "當 Peertube >= 5.0.0 時,外掛嘗試使用 Websocket 連線進行聊天。\n如果使用者的瀏覽器或連線不相容,瀏覽器將自動轉而使用 + BOSH 協定。\n\n但在極少數情況下,這可能會失敗。例如,如果您在 Peertube 前面有一個反向代理,但它不\n允許外掛的 Websocket + 連線。\n在這種情況下,您可以檢查此設定以停用 Websocket 連線。\n" +prosody_firewall_name_desc: "只能包含:字母數字字元、底線和連字元。\n指令將按字母順序讀取。\n" +open_chat: 開始聊天 +copy: 複製 +federation_dont_publish_remotely_description: "透過檢查此設置,您的實例將不會在 fediverse 上發布聊天訊息。\n + 遠端 Peertube 實例不會意識到它們是與您的視訊關聯的聊天室。\n請注意:如果您已經進行過聊天,則該資訊可能已經發布。\n您必須等待下一次視訊更新,訊息才會取消發布。\n + 此外,如果您停用此設置,則必須等待視訊更新後才能顯示資訊。\n再次發布。當現場活動恢復或停止時,會發生此更新。\n請注意:此設定僅影響透過 ActivityPub + 協定發布資訊。\n它不會阻止遠端應用程式檢測聊天的存在並嘗試連接到它。\n" +auto_ban_anonymous_ip_description: "透過啟用此選項,每次匿名用戶被禁止進入聊天室時,其 IP 也將被聊天伺服器禁止。\n警告:如果您的執行個體開放註冊,任何使用者都可以建立陷阱房間、邀請使用者加入,並自動禁止所有匿名使用者的 + IP。\n禁止的IP清單不會被存儲,它會在伺服器重新啟動或更改某些外掛的設定時被清除。\n被禁止的IP會記錄在Prosody伺服器日誌檔案中,因此伺服器管理員最終可以使用一些外部工具(如fail2ban)來更廣泛地禁止IP。\n" +avatar_set_description: "您可以從多個不同的設定中選擇將用於聊天使用者的預設頭像。\n請參考檔案:\n設定。\n" +avatar_set_option_sepia: 棕褐色(Peertube 吉祥物) +converse_theme_option_peertube: Peertube主題 +autocolors_label: 自動色彩檢測 +open_chat_new_window: 在新視窗開啟聊天 +close_chat: 關閉聯天 +tips_for_streamers: "給直播主的提示:例如,要使用 OBS 將聊天嵌入到視訊直播中,\n產生唯讀連結並將其供瀏覽器使用。\n" +chat_terms_description: "這些條款和條件將在所有使用者加入聊天室時顯示。\n直播主還可以為其頻道配置條款和條件,這些條款和條件將立即顯示在這些全域條款和條件之後。\n" +auto_display_description: 觀看影片時,聊天視窗會自動開啟。 +open_blank_label: 顯示“在新視窗中開啟”按鈕 +open_blank_description: 此按鈕可在新視窗中開啟網路聊天。 +share_url_label: 顯示「共享聊天連結」按鈕 +share_url_description: 用於共享聊天 URL 的按鈕(例如,可用於 OBS 整合)。 +share_url_option_nobody: 無人可見 +share_url_option_owner_moderators: 只有影片擁有者與管理者可見 +auto_ban_anonymous_ip_label: 當用戶被禁止進入聊天室時,禁止匿名用戶的 IP +copied: 已複製 +generate_iframe: 產生 iframe 以將聊天嵌入到網站中 +chat_for_live_stream: '直播聊天:' +use_chat: 使用聊天功能 +use_chat_help: 如果啟用,影片旁邊將會出現聊天視窗。 +share_chat_link: 分享聊天連結 +read_only: 唯讀 +external_auth_custom_oidc_button_label_label: 連接按鈕的標籤 +external_auth_custom_oidc_button_label_description: 此標籤將作為向該 OIDC 提供者進行身份驗證的按鈕標籤顯示給使用者。 +external_auth_custom_oidc_discovery_url_label: OIDC_custom_dicovery_url +room_type_label: 聊天室類型 +room_type_description: 您可以在此處選擇為每個影片設定單獨的聊天室,或按頻道對它們進行分組。 +video: 影片 +channel: 頻道 +last_activity: 最後異動 +important_note_title: 注意事項 +online_help: 線上幫助 +not_found: 未找到 +important_note_text: "您可以在這裡找到外掛文件:\n\n Peertube 外掛即時聊天檔案\n。\n" +diagnostic: "在尋求協助之前,請使用診斷工具:\n啟動診斷\n(如果此按鈕沒有開啟新視窗,請嘗試重新整理頁面)。\n" +room_description: 聊天室說明 +room_name: 聊天室名稱 +per_live_video_label: 使用者在直播時可啟用聊天 +per_live_video_warning_description: "\n 您已啟用「使用者可以自已啟用聊天室」設定。\n 它與“啟動所有直播都有聊天室”設定是互斥的。\n\n" +all_lives_description: 若勾選,則所有直播都啟用聊天室。 diff --git a/package-lock.json b/package-lock.json index 0f02b0db..1137c37c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "peertube-plugin-livechat", - "version": "10.3.3", + "version": "11.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "peertube-plugin-livechat", - "version": "10.3.3", + "version": "11.0.1", "license": "AGPL-3.0", "dependencies": { "@xmpp/jid": "^0.13.1", @@ -56,7 +56,7 @@ "stylelint-config-recommended-scss": "^5.0.1", "stylelint-config-standard-scss": "^2.0.1", "svgo": "^2.8.0", - "typescript": "^4.3.5", + "typescript": "^4.9.5", "yaml": "^2.2.1" }, "engines": { @@ -85,989 +85,1102 @@ } }, "node_modules/@aws-crypto/crc32": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", - "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "dev": true, "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, + "node_modules/@aws-crypto/crc32/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/@aws-crypto/crc32c": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz", - "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", "dev": true, "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } + "node_modules/@aws-crypto/crc32c/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true }, "node_modules/@aws-crypto/sha1-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz", - "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", "dev": true, "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "dev": true, "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dev": true, "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "dev": true, "dependencies": { - "tslib": "^1.11.1" + "tslib": "^2.6.2" } }, + "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "dev": true, "dependencies": { "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/chunked-blob-reader": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/chunked-blob-reader/-/chunked-blob-reader-3.310.0.tgz", - "integrity": "sha512-CrJS3exo4mWaLnWxfCH+w88Ou0IcAZSIkk4QbmxiHl/5Dq705OLoxf4385MVyExpqpeVJYOYQ2WaD8i/pQZ2fg==", + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/chunked-blob-reader/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/client-s3": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.363.0.tgz", - "integrity": "sha512-LNnfg/t8wG5Fqj6l+PSV/t+IXDq9r3Kj9jEHn84513+p7bewXYSSreSpmLjG8OcKuMfHc9EJGNQ3DkMyFaLoWg==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.637.0.tgz", + "integrity": "sha512-y6UC94fsMvhKbf0dzfnjVP1HePeGjplfcYfilZU1COIJLyTkMcUv4XcT4I407CGIrvgEafONHkiC09ygqUauNA==", "dev": true, "dependencies": { - "@aws-crypto/sha1-browser": "3.0.0", - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.363.0", - "@aws-sdk/credential-provider-node": "3.363.0", - "@aws-sdk/hash-blob-browser": "3.357.0", - "@aws-sdk/hash-stream-node": "3.357.0", - "@aws-sdk/md5-js": "3.357.0", - "@aws-sdk/middleware-bucket-endpoint": "3.363.0", - "@aws-sdk/middleware-expect-continue": "3.363.0", - "@aws-sdk/middleware-flexible-checksums": "3.363.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-location-constraint": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-sdk-s3": "3.363.0", - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/middleware-ssec": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/signature-v4-multi-region": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@aws-sdk/xml-builder": "3.310.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/eventstream-serde-browser": "^1.0.1", - "@smithy/eventstream-serde-config-resolver": "^1.0.1", - "@smithy/eventstream-serde-node": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-stream": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "@smithy/util-waiter": "^1.0.1", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/client-s3/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/client-sso": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.363.0.tgz", - "integrity": "sha512-PZ+HfKSgS4hlMnJzG+Ev8/mgHd/b/ETlJWPSWjC/f2NwVoBQkBnqHjdyEx7QjF6nksJozcVh5Q+kkYLKc/QwBQ==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz", + "integrity": "sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==", "dev": true, "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.363.0.tgz", - "integrity": "sha512-V3Ebiq/zNtDS/O92HUWGBa7MY59RYSsqWd+E0XrXv6VYTA00RlMTbNcseivNgp2UghOgB9a20Nkz6EqAeIN+RQ==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.637.0.tgz", + "integrity": "sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==", "dev": true, "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" } }, "node_modules/@aws-sdk/client-sso-oidc/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/client-sso/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/client-sts": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.363.0.tgz", - "integrity": "sha512-0jj14WvBPJQ8xr72cL0mhlmQ90tF0O0wqXwSbtog6PsC8+KDE6Yf+WsxsumyI8E5O8u3eYijBL+KdqG07F/y/w==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz", + "integrity": "sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==", "dev": true, "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/credential-provider-node": "3.363.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-sdk-sts": "3.363.0", - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.1", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/smithy-client": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/client-sts/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@aws-sdk/core": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", + "integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==", + "dev": true, + "dependencies": { + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.363.0.tgz", - "integrity": "sha512-VAQ3zITT2Q0acht0HezouYnMFKZ2vIOa20X4zQA3WI0HfaP4D6ga6KaenbDcb/4VFiqfqiRHfdyXHP0ThcDRMA==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz", + "integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.363.0.tgz", - "integrity": "sha512-ZYN+INoqyX5FVC3rqUxB6O8nOWkr0gHRRBm1suoOlmuFJ/WSlW/uUGthRBY5x1AQQnBF8cpdlxZzGHd41lFVNw==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz", + "integrity": "sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==", "dev": true, "dependencies": { - "@aws-sdk/credential-provider-env": "3.363.0", - "@aws-sdk/credential-provider-process": "3.363.0", - "@aws-sdk/credential-provider-sso": "3.363.0", - "@aws-sdk/credential-provider-web-identity": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" } }, "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.363.0.tgz", - "integrity": "sha512-C1qXFIN2yMxD6pGgug0vR1UhScOki6VqdzuBHzXZAGu7MOjvgHNdscEcb3CpWnITHaPL2ztkiw75T1sZ7oIgQg==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.637.0.tgz", + "integrity": "sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==", "dev": true, "dependencies": { - "@aws-sdk/credential-provider-env": "3.363.0", - "@aws-sdk/credential-provider-ini": "3.363.0", - "@aws-sdk/credential-provider-process": "3.363.0", - "@aws-sdk/credential-provider-sso": "3.363.0", - "@aws-sdk/credential-provider-web-identity": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.363.0.tgz", - "integrity": "sha512-fOKAINU7Rtj2T8pP13GdCt+u0Ml3gYynp8ki+1jMZIQ+Ju/MdDOqZpKMFKicMn3Z1ttUOgqr+grUdus6z8ceBQ==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.363.0.tgz", - "integrity": "sha512-5RUZ5oM0lwZSo3EehT0dXggOjgtxFogpT3cZvoLGtIwrPBvm8jOQPXQUlaqCj10ThF1sYltEyukz/ovtDwYGew==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.637.0.tgz", + "integrity": "sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==", "dev": true, "dependencies": { - "@aws-sdk/client-sso": "3.363.0", - "@aws-sdk/token-providers": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/client-sso": "3.637.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.363.0.tgz", - "integrity": "sha512-Z6w7fjgy79pAax580wdixbStQw10xfyZ+hOYLcPudoYFKjoNx0NQBejg5SwBzCF/HQL23Ksm9kDfbXDX9fkPhA==", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/hash-blob-browser": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.357.0.tgz", - "integrity": "sha512-RDd6UgrGHDmleTnXM9LRSSVa69euSAG2mlNhZMEDWk3OFseXVYqBDaqroVbQ01rM2UAe8MeBFchlV9OmxuVgvw==", - "dev": true, - "dependencies": { - "@aws-sdk/chunked-blob-reader": "3.310.0", - "@aws-sdk/types": "3.357.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/hash-blob-browser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/hash-stream-node": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-stream-node/-/hash-stream-node-3.357.0.tgz", - "integrity": "sha512-KZjN1VAw1KHNp+xKVOWBGS+MpaYQTjZFD5f+7QQqW4TfbAkFFwIAEYIHq5Q8Gw+jVh0h61OrV/LyW3J2PVzc+w==", - "dev": true, - "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/hash-stream-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/is-array-buffer": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.310.0.tgz", - "integrity": "sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==", - "dev": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/is-array-buffer/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/md5-js": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/md5-js/-/md5-js-3.357.0.tgz", - "integrity": "sha512-to42sFAL7KgV/X9X40LLfEaNMHMGQL6/7mPMVCL/W2BZf3zw5OTl3lAaNyjXA+gO5Uo4lFEiQKAQVKNbr8b8Nw==", - "dev": true, - "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/md5-js/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.363.0.tgz", - "integrity": "sha512-kR8+0X50zslpzRW29q4JbpPMadE1z39ZfGwPaBLKpoWvSGt4x+75FaoK71TH7urPPoFyD2Y+XKGA6YRYTUNHSQ==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", + "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "@smithy/util-config-provider": "^1.0.1", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.363.0.tgz", - "integrity": "sha512-I88xneZp6jRwySmIl9uI7eZCcTsqRVnTDfUr1JiXt7zonqNNm80PVYMs6pwaw7t97ec1AQJcsONjuXZyCMnu5g==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", + "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-expect-continue/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.363.0.tgz", - "integrity": "sha512-FBYmrMRX01uNximNN0WLgpf97GN4xNTLaKsDlkjYRWKJ+J97ICkvLG0FcSu7+SNCpCdJJBeQ5tRVOPVpUu6nmA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", + "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", "dev": true, "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.357.0", - "@smithy/is-array-buffer": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-sdk/types": "3.609.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.363.0.tgz", - "integrity": "sha512-FobpclDCf5Y1ueyJDmb9MqguAdPssNMlnqWQpujhYVABq69KHu73fSCWSauFPUrw7YOpV8kG1uagDF0POSxHzA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-host-header/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.363.0.tgz", - "integrity": "sha512-piNzpNNI/fChSGOZxcq/2msN2qFUSEAbhqs91zbcpv8CEPekVLc4W9laXCG764BEMyfG97ZU8MtzwHeMhELhBA==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-location-constraint/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.363.0.tgz", - "integrity": "sha512-SSGgthScYnFGTOw8EzbkvquqweFmvn7uJihkpFekbtBNGC/jGOGO+8ziHjTQ8t/iI/YKubEwv+LMi0f77HKSEg==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-logger/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.363.0.tgz", - "integrity": "sha512-MWD/57QgI/N7fG8rtzDTUdSqNpYohQfgj9XCFAoVeI/bU4usrkOrew43L4smJG4XrDxlNT8lSJlDtd64tuiUZA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.363.0.tgz", - "integrity": "sha512-npC8vLCero+vULizrK0QPjNanWbgH4A/2Llc1nO8N005uvUe7co6WglILF2W3guZrFk/0uGEdX67OnLxUD97pw==", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.635.0.tgz", + "integrity": "sha512-RLdYJPEV4JL/7NBoFUs7VlP90X++5FlJdxHz0DzCjmiD3qCviKy+Cym3qg1gBgHwucs5XisuClxDrGokhAdTQw==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.363.0.tgz", - "integrity": "sha512-1yy2Ac50FO8BrODaw5bPWvVrRhaVLqXTFH6iHB+dJLPUkwtY5zLM3Mp+9Ilm7kME+r7oIB1wuO6ZB1Lf4ZszIw==", - "dev": true, - "dependencies": { - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.363.0.tgz", - "integrity": "sha512-/7qia715pt9JKYIPDGu22WmdZxD8cfF/5xB+1kmILg7ZtjO0pPuTaCNJ7xiIuFd7Dn7JXp5lop08anX/GOhNRQ==", - "dev": true, - "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/signature-v4": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.363.0.tgz", - "integrity": "sha512-pN+QN1rMShYpJnTJSCIYnNRhD0S8xSZsTn6ThgcO559Xiwz5LMHFOfOXUCEyxtbVW5kMHLUh3w101AMUKae99A==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-ssec/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.363.0.tgz", - "integrity": "sha512-ri8YaQvXP6odteVTMfxPqFR26Q0h9ejtqhUDv47P34FaKXedEM4nC6ix6o+5FEYj6l8syGyktftZ5O70NoEhug==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz", + "integrity": "sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.363.0.tgz", - "integrity": "sha512-iWamQSpaBKg88LKuiUq8xO/7iyxJ+ORkA3qDhAwUqyTJOg87ma47yFf4ycCKqINnflc3AIGLGzBHnkBc4cMF5g==", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.635.0.tgz", + "integrity": "sha512-J6QY4/invOkpogCHjSaDON1hF03viPpOnsrzVuCvJMmclS/iG62R4EY0wq1alYll0YmSdmKlpJwHMWwGtqK63Q==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/signature-v4": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/signature-v4-crt": "^3.118.0" - }, - "peerDependenciesMeta": { - "@aws-sdk/signature-v4-crt": { - "optional": true - } + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/token-providers": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.363.0.tgz", - "integrity": "sha512-6+0aJ1zugNgsMmhTtW2LBWxOVSaXCUk2q3xyTchSXkNzallYaRiZMRkieW+pKNntnu0g5H1T0zyfCO0tbXwxEA==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", "dev": true, "dependencies": { - "@aws-sdk/client-sso-oidc": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" } }, "node_modules/@aws-sdk/token-providers/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/types": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.357.0.tgz", - "integrity": "sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/types/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.310.0.tgz", - "integrity": "sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==", + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", + "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/util-arn-parser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/util-buffer-from": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.310.0.tgz", - "integrity": "sha512-i6LVeXFtGih5Zs8enLrt+ExXY92QV25jtEnTKHsmlFqFAuL3VBeod6boeMXkN2p9lbSVVQ1sAOOYZOHYbYkntw==", - "dev": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.310.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-buffer-from/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.357.0.tgz", - "integrity": "sha512-XHKyS5JClT9su9hDif715jpZiWHQF9gKZXER8tW0gOizU3R9cyWc9EsJ2BRhFNhi7nt/JF/CLUEc5qDx3ETbUw==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.637.0.tgz", + "integrity": "sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/util-endpoints/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz", - "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==", + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.363.0.tgz", - "integrity": "sha512-fk9ymBUIYbxiGm99Cn+kAAXmvMCWTf/cHAcB79oCXV4ELXdPa9lN5xQhZRFNxLUeXG4OAMEuCAUUuZEj8Fnc1Q==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-browser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.363.0.tgz", - "integrity": "sha512-Fli/dvgGA9hdnQUrYb1//wNSFlK2jAfdJcfNXA6SeBYzSeH5pVGYF4kXF0FCdnMA3Fef+Zn1zAP/hw9v8VJHWQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -1079,61 +1192,28 @@ } }, "node_modules/@aws-sdk/util-user-agent-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/util-utf8": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.310.0.tgz", - "integrity": "sha512-DnLfFT8uCO22uOJc0pt0DsSNau1GTisngBCDw8jQuWT5CqogMJu4b/uXmwEqfj8B3GX6Xsz8zOd6JpRlPftQoA==", - "dev": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.310.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "dev": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-utf8-browser/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, - "node_modules/@aws-sdk/util-utf8/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.310.0.tgz", - "integrity": "sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/xml-builder/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@babel/cli": { @@ -3071,838 +3151,1000 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-1.0.1.tgz", - "integrity": "sha512-An6irzp9NCji2JtJHhrEFlDbxLwHd6c6Y9fq3ZeomyUR8BIXlGXVTxsemUSZVVgOq3166iYbYs/CrPAmgRSFLw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/abort-controller/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz", + "integrity": "sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz", + "integrity": "sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==", + "dev": true, + "dependencies": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/chunked-blob-reader-native/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/chunked-blob-reader/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/config-resolver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-1.0.1.tgz", - "integrity": "sha512-quj0xUiEVG/UHfY82EtthR/+S5/17p3IxXArC3NFSNqryMobWbG9oWgJy2s2cgUSVZLzxevjKKvxrilK7JEDaA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "@smithy/util-config-provider": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/config-resolver/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", + "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", + "dev": true, + "dependencies": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/credential-provider-imds": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-1.0.1.tgz", - "integrity": "sha512-hkRJoxVCh4CEt1zYOBElE+G/MV6lyx3g68hSJpesM4pwMT/bzEVo5E5XzXY+6dVq8yszeatWKbFuqCCBQte8tg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", + "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", "dev": true, "dependencies": { - "@smithy/node-config-provider": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/credential-provider-imds/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/eventstream-codec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-1.0.1.tgz", - "integrity": "sha512-cpcTXQEOEs2wEvIyxW/iTHJ2m0RVqoEOTjjWEXD6SY8Gcs3FCFP6E8MXadC098tdH5ctMIUXc8POXyMpxzGnjw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", "dev": true, "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^1.1.0", - "@smithy/util-hex-encoding": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/eventstream-codec/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-1.0.1.tgz", - "integrity": "sha512-oc8vxe+AU2RzvXH/Ehh0TzM/Nsw3I3ywu7V3qaCzqdkBIntAwK9JGZqcSDsqTK0WxZKBRgFIEwopcuZ2slVnFQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.6.tgz", + "integrity": "sha512-2hM54UWQUOrki4BtsUI1WzmD13/SeaqT/AB3EUJKbcver/WgKNaiJ5y5F5XXuVe6UekffVzuUDrBZVAA3AWRpQ==", "dev": true, "dependencies": { - "@smithy/eventstream-serde-universal": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-browser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-1.0.1.tgz", - "integrity": "sha512-TJwaXima0djnNY819utO1j93qZHaheFH1bhHxBkMrImtEOuXY48Tjma/L2m8swkIq8dy8jFC9hrYOkD0eYHkFA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/eventstream-serde-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-1.0.1.tgz", - "integrity": "sha512-JEj8w7IRs4l+kcwKxbv3pNuu8n7ORC4pMFrIOrM4rERzrRnI7vMNTRzvAPGYA53rqm/Y9tBA9dw4C+H6hLXcsA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.5.tgz", + "integrity": "sha512-+upXvnHNyZP095s11jF5dhGw/Ihzqwl5G+/KtMnoQOpdfC3B5HYCcDVG9EmgkhJMXJlM64PyN5gjJl0uXFQehQ==", "dev": true, "dependencies": { - "@smithy/eventstream-serde-universal": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-1.0.1.tgz", - "integrity": "sha512-c6m9DH7m6D2S93dof4wSxysaGSQdauO20TNcSePzrgHd4rkTnz5pqZ1a7Pt22q2SKf09SvTugq5cV2Sy4r8zHw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.5.tgz", + "integrity": "sha512-5u/nXbyoh1s4QxrvNre9V6vfyoLWuiVvvd5TlZjGThIikc3G+uNiG9uOTCWweSRjv1asdDIWK7nOmN7le4RYHQ==", "dev": true, "dependencies": { - "@smithy/eventstream-codec": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-universal/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/fetch-http-handler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-1.0.1.tgz", - "integrity": "sha512-/e2A8eOMk4FVZBQ0o6uF/ttLtFZcmsK5MIwDu1UE3crM4pCAIP19Ul8U9rdLlHhIu81X4AcJmSw55RDSpVRL/w==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", "dev": true, "dependencies": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/querystring-builder": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-base64": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/fetch-http-handler/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", + "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", + "dev": true, + "dependencies": { + "@smithy/chunked-blob-reader": "^3.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-blob-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/hash-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-1.0.1.tgz", - "integrity": "sha512-eCz08BySBcOjVObjbRAS/XMKUGY4ujnuS+GoWeEpzpCSKDnO8/YQ0rStRt4C0llRmhApizYc1tK9DiJwfvXcBg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "@smithy/util-buffer-from": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/hash-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/hash-stream-node": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", + "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", + "dev": true, + "dependencies": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-stream-node/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/invalid-dependency": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-1.0.1.tgz", - "integrity": "sha512-kib63GFlAzRn/wf8M0cRWrZA1cyOy5IvpTkLavCY782DPFMP0EaEeD6VrlNIOvD6ncf7uCJ68HqckhwK1qLT3g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/invalid-dependency/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/is-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-1.0.1.tgz", - "integrity": "sha512-fHSTW70gANnzPYWNDcWkPXpp+QMbHhKozbQm/+Denkhp4gwSiPuAovWZRpJa9sXO+Q4dOnNzYN2max1vTCEroA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/is-array-buffer/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/md5-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz", + "integrity": "sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==", + "dev": true, + "dependencies": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/md5-js/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/middleware-content-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-1.0.1.tgz", - "integrity": "sha512-vWWigayk5i2cFp9xPX5vdzHyK+P0t/xZ3Ovp4Ss+c8JQ1Hlq2kpJZVWtTKsmdfND5rVo5lu0kD5wgAMUCcmuhw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", + "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", "dev": true, "dependencies": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-content-length/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/middleware-endpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-1.0.2.tgz", - "integrity": "sha512-F3CyXgjtDI4quGFkDmVNytt6KMwlzzeMxtopk6Edue4uKdKcMC1vUmoRS5xTbFzKDDp4XwpnEV7FshPaL3eCPw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", "dev": true, "dependencies": { - "@smithy/middleware-serde": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-endpoint/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/middleware-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-1.0.3.tgz", - "integrity": "sha512-ZRsjG8adtxQ456FULPqPFmWtrW44Fq8IgdQvQB+rC2RSho3OUzS+TiEIwb5Zs6rf2IoewITKtfdtsUZcxXO0ng==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", + "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", "dev": true, "dependencies": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/service-error-classification": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/util-middleware": "^1.0.1", - "@smithy/util-retry": "^1.0.3", - "tslib": "^2.5.0", - "uuid": "^8.3.2" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-retry/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@smithy/middleware-serde": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-1.0.1.tgz", - "integrity": "sha512-bn5lWk8UUeXFCQfkrNErz5SbeNd+2hgYegHMLsOLPt4URDIsyREar6wMsdsR+8UCdgR5s8udG3Zalgc7puizIQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-serde/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/middleware-stack": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-1.0.1.tgz", - "integrity": "sha512-T6+gsAO1JYamOJqmORCrByDeQ/NB+ggjHb33UDOgdX4xIjXz/FB/3UqHgQu6PL1cSFrK+i4oteDIwqARDs/Szw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-stack/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/node-config-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-1.0.1.tgz", - "integrity": "sha512-FRxifH/J2SgOaVLihIqBFuGhiHR/NfzbZYp5nYO7BGgT/gc/f9nAuuRJcEy/hwO3aI6ThyG5apH4tGec6A2sCw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", "dev": true, "dependencies": { - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/node-config-provider/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/node-http-handler": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-1.0.2.tgz", - "integrity": "sha512-PzPrGRSt3kNuruLCeR4ffJp57ZLVnIukMXVL3Ppr65ZoxiE+HBsOVAa/Z/T+4HzjCM6RaXnnmB8YKfsDjlb0iA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", "dev": true, "dependencies": { - "@smithy/abort-controller": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/querystring-builder": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/node-http-handler/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/property-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-1.0.1.tgz", - "integrity": "sha512-3EG/61Ls1MrgEaafpltXBJHSqFPqmTzEX7QKO7lOEHuYGmGYzZ08t1SsTgd1vM74z0IihoZyGPynZ7WmXKvTeg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/property-provider/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/protocol-http": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.1.0.tgz", - "integrity": "sha512-H5y/kZOqfJSqRkwtcAoVbqONmhdXwSgYNJ1Glk5Ry8qlhVVy5qUzD9EklaCH8/XLnoCsLO/F/Giee8MIvaBRkg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/protocol-http/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/querystring-builder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-1.0.1.tgz", - "integrity": "sha512-J5Tzkw1PMtu01h6wl+tlN5vsyROmS6/z5lEfNlLo/L4ELHeVkQ4Q0PEIjDddPLfjVLCm8biQTESE5GCMixSRNQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "@smithy/util-uri-escape": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/querystring-builder/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/querystring-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-1.0.1.tgz", - "integrity": "sha512-zauxdMc3cwxoLitI5DZqH7xN6Fk0mwRxrUMAETbav2j6Se2U0UGak/55rZcDg2yGzOURaLYi5iOm1gHr98P+Bw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/querystring-parser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/service-error-classification": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-1.0.2.tgz", - "integrity": "sha512-Q5CCuzYL5FGo6Rr/O+lZxXHm2hrRgbmMn8MgyjqZUWZg20COg20DuNtIbho2iht6CoB7jOpmpBqhWizLlzUZgg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", "dev": true, + "dependencies": { + "@smithy/types": "^3.3.0" + }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-1.0.1.tgz", - "integrity": "sha512-EztziuIPoNronENGqh+MWVKJErA4rJpaPzJCPukzBeEoG2USka0/q4B5Mr/1zszOnrb49fPNh4u3u5LfiH7QzA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/signature-v4": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-1.0.1.tgz", - "integrity": "sha512-2D69je14ou1vBTnAQeysSK4QVMm0j3WHS3MDg/DnHnFFcXRCzVl/xAARO7POD8+fpi4tMFPs8Z4hzo1Zw40L0Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", "dev": true, "dependencies": { - "@smithy/eventstream-codec": "^1.0.1", - "@smithy/is-array-buffer": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-hex-encoding": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "@smithy/util-uri-escape": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/signature-v4/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/smithy-client": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-1.0.3.tgz", - "integrity": "sha512-Wh1mNP/1yUZK0uYkgCQ6NMxpBT3Fmc45TMdUfOlH1xD2zGYL7U4yDHFOhEZdi/suyjaelFobXB2p9pPIw6LjRQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", "dev": true, "dependencies": { - "@smithy/middleware-stack": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-stream": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/smithy-client/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.1.0.tgz", - "integrity": "sha512-KzmvisMmuwD2jZXuC9e65JrgsZM97y5NpDU7g347oB+Q+xQLU6hQZ5zFNNbEfwwOJHoOvEVTna+dk1h/lW7alw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/types/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/url-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-1.0.1.tgz", - "integrity": "sha512-33vWEtE6HzmwjEcEb4I58XMLRAchwPS93YhfDyXAXr1jwDCzfXmMayQwwpyW847rpWj0XJimxqia8q0z+k/ybw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", "dev": true, "dependencies": { - "@smithy/querystring-parser": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/url-parser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-base64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-1.0.1.tgz", - "integrity": "sha512-rJcpRi/yUi6TyCEkjdTH86/ExBuKlfctEXhG9/4gMJ3/cnPcHJJnr0mQ9evSEO+3DbpT/Nxq90bcTBdTIAmCig==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", "dev": true, "dependencies": { - "@smithy/util-buffer-from": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-base64/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-body-length-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-1.0.1.tgz", - "integrity": "sha512-Pdp744fmF7E1NWoSb7256Anhm8eYoCubvosdMwXzOnHuPRVbDa15pKUz2027K3+jrfGpXo1r+MnDerajME1Osw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" } }, "node_modules/@smithy/util-body-length-browser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-body-length-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-1.0.1.tgz", - "integrity": "sha512-4PIHjDFwG07SNensAiVq/CJmubEVuwclWSYOTNtzBNTvxOeGLznvygkGYgPzS3erByT8C4S9JvnLYgtrsVV3nQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-body-length-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-buffer-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-1.0.1.tgz", - "integrity": "sha512-363N7Wq0ceUgE5lLe6kaR6GlJs2/m4r9V6bRMfIszb6P1FZbbRRM2FQYUWWPFSsRymm9mJL18b3fjiVsIvhDGg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "dev": true, "dependencies": { - "@smithy/is-array-buffer": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-buffer-from/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-config-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-1.0.1.tgz", - "integrity": "sha512-4Qy38Oy5/q43MpTwCLV1P+7NeaOp4W2etQDxMjgEeRlOyGGNlgttn0syi4g2rVSukFVqQ6FbeRs5xbnFmS6kaQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-config-provider/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-1.0.1.tgz", - "integrity": "sha512-/9ObwNch4Z/NJYfkO4AvqBWku60Ju+c2Ck32toPOLmWe/V6eI9FLn8C1abri+GxDRCkLIqvkaWU1lgZ3nWZIIw==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", + "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", "dev": true, "dependencies": { - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@smithy/util-defaults-mode-browser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-1.0.1.tgz", - "integrity": "sha512-XQM3KvqRLgv7bwAzVkXTITkOmcOINoG9icJiGT8FA0zV35lY5UvyIsg5kHw01xigQS8ufa/33AwG3ZoXip+V5g==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", + "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", "dev": true, "dependencies": { - "@smithy/config-resolver": "^1.0.1", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/config-resolver": "^3.0.5", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@smithy/util-defaults-mode-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/util-endpoints": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", + "dev": true, + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-hex-encoding": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-1.0.1.tgz", - "integrity": "sha512-FPTtMz/t02/rbfq5Pdll/TWUYP+GVFLCQNr+DgifrLzVRU0g8rdRjyFpDh8nPTdkDDusTTo9P1bepAYj68s0eA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-hex-encoding/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-middleware": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-1.0.1.tgz", - "integrity": "sha512-u9akN3Zmbr0vZH4F+2iehG7cFg+3fvDfnvS/hhsXH4UHuhqiQ+ADefibnLzPoz1pooY7rvwaQ/TVHyJmZHdLdQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-middleware/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-1.0.3.tgz", - "integrity": "sha512-gYQnZDD8I2XJFspVwUISyukjPWVikTzKR0IdG8hCWYPTpeULFl1o6yzXlT5SL63TBkuEYl0R1/93cdNtMiNnoA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", "dev": true, "dependencies": { - "@smithy/service-error-classification": "^1.0.2", - "tslib": "^2.5.0" + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-retry/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-1.0.1.tgz", - "integrity": "sha512-4aBCIz35aZAnt2Rbq341KrnUzGhWv2/Zu8HouJqYLvSWCzlrvsNCGlXP4e70Kjzcw8hSuuCNtdUICwQ5qUWLxg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", "dev": true, "dependencies": { - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-buffer-from": "^1.0.1", - "@smithy/util-hex-encoding": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-stream/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-uri-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-1.0.1.tgz", - "integrity": "sha512-IJUrRnXKEIc+PKnU1XzTsIENVR+60jUDPBP3iWX/EvuuT3Xfob47x1FGUe2c3yMXNuU6ax8VDk27hL5LKNoehQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-uri-escape/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-utf8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-1.0.1.tgz", - "integrity": "sha512-iX6XHpjh4DFEUIBSKp2tjy3pYnLQMsJ62zYi1BVAC0kobE6p8AVpiZnxsU3ZkgQatAsUaEspFHUZ7CL7oSqaPQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "dev": true, "dependencies": { - "@smithy/util-buffer-from": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-utf8/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-waiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-1.0.1.tgz", - "integrity": "sha512-dsn8O0s3pFIgxFzySLe1dv0w4tEQizEP6UqbgZ4r/Kar4n8pSdrPi6DJg8BzXwkwEIZpMtV4/nhSeGZ7HksDXA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", + "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", "dev": true, "dependencies": { - "@smithy/abort-controller": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^3.1.1", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-waiter/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@socket.io/component-emitter": { @@ -7818,18 +8060,18 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "dev": true, "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], "dependencies": { @@ -9954,12 +10196,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -13455,9 +13697,9 @@ "dev": true }, "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -14174,1013 +14416,1065 @@ } }, "@aws-crypto/crc32": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", - "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "dev": true, "requires": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/crc32c": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz", - "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==", - "dev": true, - "requires": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - }, - "@aws-crypto/sha1-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz", - "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==", - "dev": true, - "requires": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", - "dev": true, - "requires": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", - "dev": true, - "requires": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - }, - "@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", - "dev": true, - "requires": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "@aws-sdk/chunked-blob-reader": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/chunked-blob-reader/-/chunked-blob-reader-3.310.0.tgz", - "integrity": "sha512-CrJS3exo4mWaLnWxfCH+w88Ou0IcAZSIkk4QbmxiHl/5Dq705OLoxf4385MVyExpqpeVJYOYQ2WaD8i/pQZ2fg==", - "dev": true, - "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "dev": true, + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dev": true, + "requires": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "requires": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/client-s3": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.363.0.tgz", - "integrity": "sha512-LNnfg/t8wG5Fqj6l+PSV/t+IXDq9r3Kj9jEHn84513+p7bewXYSSreSpmLjG8OcKuMfHc9EJGNQ3DkMyFaLoWg==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.637.0.tgz", + "integrity": "sha512-y6UC94fsMvhKbf0dzfnjVP1HePeGjplfcYfilZU1COIJLyTkMcUv4XcT4I407CGIrvgEafONHkiC09ygqUauNA==", "dev": true, "requires": { - "@aws-crypto/sha1-browser": "3.0.0", - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.363.0", - "@aws-sdk/credential-provider-node": "3.363.0", - "@aws-sdk/hash-blob-browser": "3.357.0", - "@aws-sdk/hash-stream-node": "3.357.0", - "@aws-sdk/md5-js": "3.357.0", - "@aws-sdk/middleware-bucket-endpoint": "3.363.0", - "@aws-sdk/middleware-expect-continue": "3.363.0", - "@aws-sdk/middleware-flexible-checksums": "3.363.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-location-constraint": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-sdk-s3": "3.363.0", - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/middleware-ssec": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/signature-v4-multi-region": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@aws-sdk/xml-builder": "3.310.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/eventstream-serde-browser": "^1.0.1", - "@smithy/eventstream-serde-config-resolver": "^1.0.1", - "@smithy/eventstream-serde-node": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-stream": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "@smithy/util-waiter": "^1.0.1", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/client-sso": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.363.0.tgz", - "integrity": "sha512-PZ+HfKSgS4hlMnJzG+Ev8/mgHd/b/ETlJWPSWjC/f2NwVoBQkBnqHjdyEx7QjF6nksJozcVh5Q+kkYLKc/QwBQ==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz", + "integrity": "sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==", "dev": true, "requires": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/client-sso-oidc": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.363.0.tgz", - "integrity": "sha512-V3Ebiq/zNtDS/O92HUWGBa7MY59RYSsqWd+E0XrXv6VYTA00RlMTbNcseivNgp2UghOgB9a20Nkz6EqAeIN+RQ==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.637.0.tgz", + "integrity": "sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==", "dev": true, "requires": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/client-sts": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.363.0.tgz", - "integrity": "sha512-0jj14WvBPJQ8xr72cL0mhlmQ90tF0O0wqXwSbtog6PsC8+KDE6Yf+WsxsumyI8E5O8u3eYijBL+KdqG07F/y/w==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz", + "integrity": "sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==", "dev": true, "requires": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/credential-provider-node": "3.363.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-sdk-sts": "3.363.0", - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.1", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/smithy-client": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-sdk/core": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", + "integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==", + "dev": true, + "requires": { + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-env": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.363.0.tgz", - "integrity": "sha512-VAQ3zITT2Q0acht0HezouYnMFKZ2vIOa20X4zQA3WI0HfaP4D6ga6KaenbDcb/4VFiqfqiRHfdyXHP0ThcDRMA==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-sdk/credential-provider-http": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz", + "integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-ini": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.363.0.tgz", - "integrity": "sha512-ZYN+INoqyX5FVC3rqUxB6O8nOWkr0gHRRBm1suoOlmuFJ/WSlW/uUGthRBY5x1AQQnBF8cpdlxZzGHd41lFVNw==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz", + "integrity": "sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==", "dev": true, "requires": { - "@aws-sdk/credential-provider-env": "3.363.0", - "@aws-sdk/credential-provider-process": "3.363.0", - "@aws-sdk/credential-provider-sso": "3.363.0", - "@aws-sdk/credential-provider-web-identity": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-node": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.363.0.tgz", - "integrity": "sha512-C1qXFIN2yMxD6pGgug0vR1UhScOki6VqdzuBHzXZAGu7MOjvgHNdscEcb3CpWnITHaPL2ztkiw75T1sZ7oIgQg==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.637.0.tgz", + "integrity": "sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==", "dev": true, "requires": { - "@aws-sdk/credential-provider-env": "3.363.0", - "@aws-sdk/credential-provider-ini": "3.363.0", - "@aws-sdk/credential-provider-process": "3.363.0", - "@aws-sdk/credential-provider-sso": "3.363.0", - "@aws-sdk/credential-provider-web-identity": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-process": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.363.0.tgz", - "integrity": "sha512-fOKAINU7Rtj2T8pP13GdCt+u0Ml3gYynp8ki+1jMZIQ+Ju/MdDOqZpKMFKicMn3Z1ttUOgqr+grUdus6z8ceBQ==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-sso": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.363.0.tgz", - "integrity": "sha512-5RUZ5oM0lwZSo3EehT0dXggOjgtxFogpT3cZvoLGtIwrPBvm8jOQPXQUlaqCj10ThF1sYltEyukz/ovtDwYGew==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.637.0.tgz", + "integrity": "sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==", "dev": true, "requires": { - "@aws-sdk/client-sso": "3.363.0", - "@aws-sdk/token-providers": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/client-sso": "3.637.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-web-identity": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.363.0.tgz", - "integrity": "sha512-Z6w7fjgy79pAax580wdixbStQw10xfyZ+hOYLcPudoYFKjoNx0NQBejg5SwBzCF/HQL23Ksm9kDfbXDX9fkPhA==", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/hash-blob-browser": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.357.0.tgz", - "integrity": "sha512-RDd6UgrGHDmleTnXM9LRSSVa69euSAG2mlNhZMEDWk3OFseXVYqBDaqroVbQ01rM2UAe8MeBFchlV9OmxuVgvw==", - "dev": true, - "requires": { - "@aws-sdk/chunked-blob-reader": "3.310.0", - "@aws-sdk/types": "3.357.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/hash-stream-node": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-stream-node/-/hash-stream-node-3.357.0.tgz", - "integrity": "sha512-KZjN1VAw1KHNp+xKVOWBGS+MpaYQTjZFD5f+7QQqW4TfbAkFFwIAEYIHq5Q8Gw+jVh0h61OrV/LyW3J2PVzc+w==", - "dev": true, - "requires": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/is-array-buffer": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.310.0.tgz", - "integrity": "sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==", - "dev": true, - "requires": { - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/md5-js": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/md5-js/-/md5-js-3.357.0.tgz", - "integrity": "sha512-to42sFAL7KgV/X9X40LLfEaNMHMGQL6/7mPMVCL/W2BZf3zw5OTl3lAaNyjXA+gO5Uo4lFEiQKAQVKNbr8b8Nw==", - "dev": true, - "requires": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-bucket-endpoint": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.363.0.tgz", - "integrity": "sha512-kR8+0X50zslpzRW29q4JbpPMadE1z39ZfGwPaBLKpoWvSGt4x+75FaoK71TH7urPPoFyD2Y+XKGA6YRYTUNHSQ==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", + "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "@smithy/util-config-provider": "^1.0.1", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-expect-continue": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.363.0.tgz", - "integrity": "sha512-I88xneZp6jRwySmIl9uI7eZCcTsqRVnTDfUr1JiXt7zonqNNm80PVYMs6pwaw7t97ec1AQJcsONjuXZyCMnu5g==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", + "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-flexible-checksums": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.363.0.tgz", - "integrity": "sha512-FBYmrMRX01uNximNN0WLgpf97GN4xNTLaKsDlkjYRWKJ+J97ICkvLG0FcSu7+SNCpCdJJBeQ5tRVOPVpUu6nmA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", + "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", "dev": true, "requires": { - "@aws-crypto/crc32": "3.0.0", - "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.357.0", - "@smithy/is-array-buffer": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-sdk/types": "3.609.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-host-header": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.363.0.tgz", - "integrity": "sha512-FobpclDCf5Y1ueyJDmb9MqguAdPssNMlnqWQpujhYVABq69KHu73fSCWSauFPUrw7YOpV8kG1uagDF0POSxHzA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-location-constraint": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.363.0.tgz", - "integrity": "sha512-piNzpNNI/fChSGOZxcq/2msN2qFUSEAbhqs91zbcpv8CEPekVLc4W9laXCG764BEMyfG97ZU8MtzwHeMhELhBA==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-logger": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.363.0.tgz", - "integrity": "sha512-SSGgthScYnFGTOw8EzbkvquqweFmvn7uJihkpFekbtBNGC/jGOGO+8ziHjTQ8t/iI/YKubEwv+LMi0f77HKSEg==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-recursion-detection": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.363.0.tgz", - "integrity": "sha512-MWD/57QgI/N7fG8rtzDTUdSqNpYohQfgj9XCFAoVeI/bU4usrkOrew43L4smJG4XrDxlNT8lSJlDtd64tuiUZA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-sdk-s3": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.363.0.tgz", - "integrity": "sha512-npC8vLCero+vULizrK0QPjNanWbgH4A/2Llc1nO8N005uvUe7co6WglILF2W3guZrFk/0uGEdX67OnLxUD97pw==", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.635.0.tgz", + "integrity": "sha512-RLdYJPEV4JL/7NBoFUs7VlP90X++5FlJdxHz0DzCjmiD3qCviKy+Cym3qg1gBgHwucs5XisuClxDrGokhAdTQw==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/middleware-sdk-sts": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.363.0.tgz", - "integrity": "sha512-1yy2Ac50FO8BrODaw5bPWvVrRhaVLqXTFH6iHB+dJLPUkwtY5zLM3Mp+9Ilm7kME+r7oIB1wuO6ZB1Lf4ZszIw==", - "dev": true, - "requires": { - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/middleware-signing": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.363.0.tgz", - "integrity": "sha512-/7qia715pt9JKYIPDGu22WmdZxD8cfF/5xB+1kmILg7ZtjO0pPuTaCNJ7xiIuFd7Dn7JXp5lop08anX/GOhNRQ==", - "dev": true, - "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/signature-v4": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-ssec": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.363.0.tgz", - "integrity": "sha512-pN+QN1rMShYpJnTJSCIYnNRhD0S8xSZsTn6ThgcO559Xiwz5LMHFOfOXUCEyxtbVW5kMHLUh3w101AMUKae99A==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-user-agent": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.363.0.tgz", - "integrity": "sha512-ri8YaQvXP6odteVTMfxPqFR26Q0h9ejtqhUDv47P34FaKXedEM4nC6ix6o+5FEYj6l8syGyktftZ5O70NoEhug==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz", + "integrity": "sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/signature-v4-multi-region": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.363.0.tgz", - "integrity": "sha512-iWamQSpaBKg88LKuiUq8xO/7iyxJ+ORkA3qDhAwUqyTJOg87ma47yFf4ycCKqINnflc3AIGLGzBHnkBc4cMF5g==", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.635.0.tgz", + "integrity": "sha512-J6QY4/invOkpogCHjSaDON1hF03viPpOnsrzVuCvJMmclS/iG62R4EY0wq1alYll0YmSdmKlpJwHMWwGtqK63Q==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/signature-v4": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/token-providers": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.363.0.tgz", - "integrity": "sha512-6+0aJ1zugNgsMmhTtW2LBWxOVSaXCUk2q3xyTchSXkNzallYaRiZMRkieW+pKNntnu0g5H1T0zyfCO0tbXwxEA==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", "dev": true, "requires": { - "@aws-sdk/client-sso-oidc": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/types": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.357.0.tgz", - "integrity": "sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dev": true, "requires": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/util-arn-parser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.310.0.tgz", - "integrity": "sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==", + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", + "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/util-buffer-from": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.310.0.tgz", - "integrity": "sha512-i6LVeXFtGih5Zs8enLrt+ExXY92QV25jtEnTKHsmlFqFAuL3VBeod6boeMXkN2p9lbSVVQ1sAOOYZOHYbYkntw==", - "dev": true, - "requires": { - "@aws-sdk/is-array-buffer": "3.310.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/util-endpoints": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.357.0.tgz", - "integrity": "sha512-XHKyS5JClT9su9hDif715jpZiWHQF9gKZXER8tW0gOizU3R9cyWc9EsJ2BRhFNhi7nt/JF/CLUEc5qDx3ETbUw==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.637.0.tgz", + "integrity": "sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/util-locate-window": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz", - "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==", + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/util-user-agent-browser": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.363.0.tgz", - "integrity": "sha512-fk9ymBUIYbxiGm99Cn+kAAXmvMCWTf/cHAcB79oCXV4ELXdPa9lN5xQhZRFNxLUeXG4OAMEuCAUUuZEj8Fnc1Q==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/util-user-agent-node": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.363.0.tgz", - "integrity": "sha512-Fli/dvgGA9hdnQUrYb1//wNSFlK2jAfdJcfNXA6SeBYzSeH5pVGYF4kXF0FCdnMA3Fef+Zn1zAP/hw9v8VJHWQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/util-utf8": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.310.0.tgz", - "integrity": "sha512-DnLfFT8uCO22uOJc0pt0DsSNau1GTisngBCDw8jQuWT5CqogMJu4b/uXmwEqfj8B3GX6Xsz8zOd6JpRlPftQoA==", - "dev": true, - "requires": { - "@aws-sdk/util-buffer-from": "3.310.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "dev": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/xml-builder": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.310.0.tgz", - "integrity": "sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", "dev": true, "requires": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } @@ -16350,806 +16644,966 @@ "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==" }, "@smithy/abort-controller": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-1.0.1.tgz", - "integrity": "sha512-An6irzp9NCji2JtJHhrEFlDbxLwHd6c6Y9fq3ZeomyUR8BIXlGXVTxsemUSZVVgOq3166iYbYs/CrPAmgRSFLw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/chunked-blob-reader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz", + "integrity": "sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/chunked-blob-reader-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz", + "integrity": "sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==", + "dev": true, + "requires": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/config-resolver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-1.0.1.tgz", - "integrity": "sha512-quj0xUiEVG/UHfY82EtthR/+S5/17p3IxXArC3NFSNqryMobWbG9oWgJy2s2cgUSVZLzxevjKKvxrilK7JEDaA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "@smithy/util-config-provider": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", + "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", + "dev": true, + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/credential-provider-imds": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-1.0.1.tgz", - "integrity": "sha512-hkRJoxVCh4CEt1zYOBElE+G/MV6lyx3g68hSJpesM4pwMT/bzEVo5E5XzXY+6dVq8yszeatWKbFuqCCBQte8tg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", + "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", "dev": true, "requires": { - "@smithy/node-config-provider": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/eventstream-codec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-1.0.1.tgz", - "integrity": "sha512-cpcTXQEOEs2wEvIyxW/iTHJ2m0RVqoEOTjjWEXD6SY8Gcs3FCFP6E8MXadC098tdH5ctMIUXc8POXyMpxzGnjw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", "dev": true, "requires": { - "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^1.1.0", - "@smithy/util-hex-encoding": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/eventstream-serde-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-1.0.1.tgz", - "integrity": "sha512-oc8vxe+AU2RzvXH/Ehh0TzM/Nsw3I3ywu7V3qaCzqdkBIntAwK9JGZqcSDsqTK0WxZKBRgFIEwopcuZ2slVnFQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.6.tgz", + "integrity": "sha512-2hM54UWQUOrki4BtsUI1WzmD13/SeaqT/AB3EUJKbcver/WgKNaiJ5y5F5XXuVe6UekffVzuUDrBZVAA3AWRpQ==", "dev": true, "requires": { - "@smithy/eventstream-serde-universal": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/eventstream-serde-config-resolver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-1.0.1.tgz", - "integrity": "sha512-TJwaXima0djnNY819utO1j93qZHaheFH1bhHxBkMrImtEOuXY48Tjma/L2m8swkIq8dy8jFC9hrYOkD0eYHkFA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/eventstream-serde-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-1.0.1.tgz", - "integrity": "sha512-JEj8w7IRs4l+kcwKxbv3pNuu8n7ORC4pMFrIOrM4rERzrRnI7vMNTRzvAPGYA53rqm/Y9tBA9dw4C+H6hLXcsA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.5.tgz", + "integrity": "sha512-+upXvnHNyZP095s11jF5dhGw/Ihzqwl5G+/KtMnoQOpdfC3B5HYCcDVG9EmgkhJMXJlM64PyN5gjJl0uXFQehQ==", "dev": true, "requires": { - "@smithy/eventstream-serde-universal": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/eventstream-serde-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-1.0.1.tgz", - "integrity": "sha512-c6m9DH7m6D2S93dof4wSxysaGSQdauO20TNcSePzrgHd4rkTnz5pqZ1a7Pt22q2SKf09SvTugq5cV2Sy4r8zHw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.5.tgz", + "integrity": "sha512-5u/nXbyoh1s4QxrvNre9V6vfyoLWuiVvvd5TlZjGThIikc3G+uNiG9uOTCWweSRjv1asdDIWK7nOmN7le4RYHQ==", "dev": true, "requires": { - "@smithy/eventstream-codec": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/fetch-http-handler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-1.0.1.tgz", - "integrity": "sha512-/e2A8eOMk4FVZBQ0o6uF/ttLtFZcmsK5MIwDu1UE3crM4pCAIP19Ul8U9rdLlHhIu81X4AcJmSw55RDSpVRL/w==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", "dev": true, "requires": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/querystring-builder": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-base64": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/hash-blob-browser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", + "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", + "dev": true, + "requires": { + "@smithy/chunked-blob-reader": "^3.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/hash-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-1.0.1.tgz", - "integrity": "sha512-eCz08BySBcOjVObjbRAS/XMKUGY4ujnuS+GoWeEpzpCSKDnO8/YQ0rStRt4C0llRmhApizYc1tK9DiJwfvXcBg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "@smithy/util-buffer-from": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/hash-stream-node": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", + "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", + "dev": true, + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/invalid-dependency": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-1.0.1.tgz", - "integrity": "sha512-kib63GFlAzRn/wf8M0cRWrZA1cyOy5IvpTkLavCY782DPFMP0EaEeD6VrlNIOvD6ncf7uCJ68HqckhwK1qLT3g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/is-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-1.0.1.tgz", - "integrity": "sha512-fHSTW70gANnzPYWNDcWkPXpp+QMbHhKozbQm/+Denkhp4gwSiPuAovWZRpJa9sXO+Q4dOnNzYN2max1vTCEroA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/md5-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz", + "integrity": "sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==", + "dev": true, + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/middleware-content-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-1.0.1.tgz", - "integrity": "sha512-vWWigayk5i2cFp9xPX5vdzHyK+P0t/xZ3Ovp4Ss+c8JQ1Hlq2kpJZVWtTKsmdfND5rVo5lu0kD5wgAMUCcmuhw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", + "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", "dev": true, "requires": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/middleware-endpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-1.0.2.tgz", - "integrity": "sha512-F3CyXgjtDI4quGFkDmVNytt6KMwlzzeMxtopk6Edue4uKdKcMC1vUmoRS5xTbFzKDDp4XwpnEV7FshPaL3eCPw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", "dev": true, "requires": { - "@smithy/middleware-serde": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/middleware-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-1.0.3.tgz", - "integrity": "sha512-ZRsjG8adtxQ456FULPqPFmWtrW44Fq8IgdQvQB+rC2RSho3OUzS+TiEIwb5Zs6rf2IoewITKtfdtsUZcxXO0ng==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", + "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", "dev": true, "requires": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/service-error-classification": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/util-middleware": "^1.0.1", - "@smithy/util-retry": "^1.0.3", - "tslib": "^2.5.0", - "uuid": "^8.3.2" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true } } }, "@smithy/middleware-serde": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-1.0.1.tgz", - "integrity": "sha512-bn5lWk8UUeXFCQfkrNErz5SbeNd+2hgYegHMLsOLPt4URDIsyREar6wMsdsR+8UCdgR5s8udG3Zalgc7puizIQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/middleware-stack": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-1.0.1.tgz", - "integrity": "sha512-T6+gsAO1JYamOJqmORCrByDeQ/NB+ggjHb33UDOgdX4xIjXz/FB/3UqHgQu6PL1cSFrK+i4oteDIwqARDs/Szw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", "dev": true, "requires": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/node-config-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-1.0.1.tgz", - "integrity": "sha512-FRxifH/J2SgOaVLihIqBFuGhiHR/NfzbZYp5nYO7BGgT/gc/f9nAuuRJcEy/hwO3aI6ThyG5apH4tGec6A2sCw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", "dev": true, "requires": { - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/node-http-handler": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-1.0.2.tgz", - "integrity": "sha512-PzPrGRSt3kNuruLCeR4ffJp57ZLVnIukMXVL3Ppr65ZoxiE+HBsOVAa/Z/T+4HzjCM6RaXnnmB8YKfsDjlb0iA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", "dev": true, "requires": { - "@smithy/abort-controller": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/querystring-builder": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/property-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-1.0.1.tgz", - "integrity": "sha512-3EG/61Ls1MrgEaafpltXBJHSqFPqmTzEX7QKO7lOEHuYGmGYzZ08t1SsTgd1vM74z0IihoZyGPynZ7WmXKvTeg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/protocol-http": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.1.0.tgz", - "integrity": "sha512-H5y/kZOqfJSqRkwtcAoVbqONmhdXwSgYNJ1Glk5Ry8qlhVVy5qUzD9EklaCH8/XLnoCsLO/F/Giee8MIvaBRkg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/querystring-builder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-1.0.1.tgz", - "integrity": "sha512-J5Tzkw1PMtu01h6wl+tlN5vsyROmS6/z5lEfNlLo/L4ELHeVkQ4Q0PEIjDddPLfjVLCm8biQTESE5GCMixSRNQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "@smithy/util-uri-escape": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/querystring-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-1.0.1.tgz", - "integrity": "sha512-zauxdMc3cwxoLitI5DZqH7xN6Fk0mwRxrUMAETbav2j6Se2U0UGak/55rZcDg2yGzOURaLYi5iOm1gHr98P+Bw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/service-error-classification": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-1.0.2.tgz", - "integrity": "sha512-Q5CCuzYL5FGo6Rr/O+lZxXHm2hrRgbmMn8MgyjqZUWZg20COg20DuNtIbho2iht6CoB7jOpmpBqhWizLlzUZgg==", - "dev": true - }, - "@smithy/shared-ini-file-loader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-1.0.1.tgz", - "integrity": "sha512-EztziuIPoNronENGqh+MWVKJErA4rJpaPzJCPukzBeEoG2USka0/q4B5Mr/1zszOnrb49fPNh4u3u5LfiH7QzA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0" + } + }, + "@smithy/shared-ini-file-loader": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", + "dev": true, + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/signature-v4": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-1.0.1.tgz", - "integrity": "sha512-2D69je14ou1vBTnAQeysSK4QVMm0j3WHS3MDg/DnHnFFcXRCzVl/xAARO7POD8+fpi4tMFPs8Z4hzo1Zw40L0Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", "dev": true, "requires": { - "@smithy/eventstream-codec": "^1.0.1", - "@smithy/is-array-buffer": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-hex-encoding": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "@smithy/util-uri-escape": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/smithy-client": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-1.0.3.tgz", - "integrity": "sha512-Wh1mNP/1yUZK0uYkgCQ6NMxpBT3Fmc45TMdUfOlH1xD2zGYL7U4yDHFOhEZdi/suyjaelFobXB2p9pPIw6LjRQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", "dev": true, "requires": { - "@smithy/middleware-stack": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-stream": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.1.0.tgz", - "integrity": "sha512-KzmvisMmuwD2jZXuC9e65JrgsZM97y5NpDU7g347oB+Q+xQLU6hQZ5zFNNbEfwwOJHoOvEVTna+dk1h/lW7alw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/url-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-1.0.1.tgz", - "integrity": "sha512-33vWEtE6HzmwjEcEb4I58XMLRAchwPS93YhfDyXAXr1jwDCzfXmMayQwwpyW847rpWj0XJimxqia8q0z+k/ybw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", "dev": true, "requires": { - "@smithy/querystring-parser": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-base64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-1.0.1.tgz", - "integrity": "sha512-rJcpRi/yUi6TyCEkjdTH86/ExBuKlfctEXhG9/4gMJ3/cnPcHJJnr0mQ9evSEO+3DbpT/Nxq90bcTBdTIAmCig==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", "dev": true, "requires": { - "@smithy/util-buffer-from": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-body-length-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-1.0.1.tgz", - "integrity": "sha512-Pdp744fmF7E1NWoSb7256Anhm8eYoCubvosdMwXzOnHuPRVbDa15pKUz2027K3+jrfGpXo1r+MnDerajME1Osw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-body-length-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-1.0.1.tgz", - "integrity": "sha512-4PIHjDFwG07SNensAiVq/CJmubEVuwclWSYOTNtzBNTvxOeGLznvygkGYgPzS3erByT8C4S9JvnLYgtrsVV3nQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-buffer-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-1.0.1.tgz", - "integrity": "sha512-363N7Wq0ceUgE5lLe6kaR6GlJs2/m4r9V6bRMfIszb6P1FZbbRRM2FQYUWWPFSsRymm9mJL18b3fjiVsIvhDGg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "dev": true, "requires": { - "@smithy/is-array-buffer": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-config-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-1.0.1.tgz", - "integrity": "sha512-4Qy38Oy5/q43MpTwCLV1P+7NeaOp4W2etQDxMjgEeRlOyGGNlgttn0syi4g2rVSukFVqQ6FbeRs5xbnFmS6kaQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-defaults-mode-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-1.0.1.tgz", - "integrity": "sha512-/9ObwNch4Z/NJYfkO4AvqBWku60Ju+c2Ck32toPOLmWe/V6eI9FLn8C1abri+GxDRCkLIqvkaWU1lgZ3nWZIIw==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", + "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", "dev": true, "requires": { - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-defaults-mode-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-1.0.1.tgz", - "integrity": "sha512-XQM3KvqRLgv7bwAzVkXTITkOmcOINoG9icJiGT8FA0zV35lY5UvyIsg5kHw01xigQS8ufa/33AwG3ZoXip+V5g==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", + "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", "dev": true, "requires": { - "@smithy/config-resolver": "^1.0.1", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/config-resolver": "^3.0.5", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/util-endpoints": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", + "dev": true, + "requires": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-hex-encoding": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-1.0.1.tgz", - "integrity": "sha512-FPTtMz/t02/rbfq5Pdll/TWUYP+GVFLCQNr+DgifrLzVRU0g8rdRjyFpDh8nPTdkDDusTTo9P1bepAYj68s0eA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-middleware": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-1.0.1.tgz", - "integrity": "sha512-u9akN3Zmbr0vZH4F+2iehG7cFg+3fvDfnvS/hhsXH4UHuhqiQ+ADefibnLzPoz1pooY7rvwaQ/TVHyJmZHdLdQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "dev": true, "requires": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-1.0.3.tgz", - "integrity": "sha512-gYQnZDD8I2XJFspVwUISyukjPWVikTzKR0IdG8hCWYPTpeULFl1o6yzXlT5SL63TBkuEYl0R1/93cdNtMiNnoA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", "dev": true, "requires": { - "@smithy/service-error-classification": "^1.0.2", - "tslib": "^2.5.0" + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-1.0.1.tgz", - "integrity": "sha512-4aBCIz35aZAnt2Rbq341KrnUzGhWv2/Zu8HouJqYLvSWCzlrvsNCGlXP4e70Kjzcw8hSuuCNtdUICwQ5qUWLxg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", "dev": true, "requires": { - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-buffer-from": "^1.0.1", - "@smithy/util-hex-encoding": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-uri-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-1.0.1.tgz", - "integrity": "sha512-IJUrRnXKEIc+PKnU1XzTsIENVR+60jUDPBP3iWX/EvuuT3Xfob47x1FGUe2c3yMXNuU6ax8VDk27hL5LKNoehQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-utf8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-1.0.1.tgz", - "integrity": "sha512-iX6XHpjh4DFEUIBSKp2tjy3pYnLQMsJ62zYi1BVAC0kobE6p8AVpiZnxsU3ZkgQatAsUaEspFHUZ7CL7oSqaPQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "dev": true, "requires": { - "@smithy/util-buffer-from": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-waiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-1.0.1.tgz", - "integrity": "sha512-dsn8O0s3pFIgxFzySLe1dv0w4tEQizEP6UqbgZ4r/Kar4n8pSdrPi6DJg8BzXwkwEIZpMtV4/nhSeGZ7HksDXA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", + "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", "dev": true, "requires": { - "@smithy/abort-controller": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^3.1.1", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } @@ -20148,9 +20602,9 @@ "dev": true }, "fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "dev": true, "requires": { "strnum": "^1.0.5" @@ -21743,12 +22197,12 @@ "dev": true }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -24334,9 +24788,9 @@ "dev": true }, "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, "uid-safe": { diff --git a/package.json b/package.json index b90f9adb..7eecc0c3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "peertube-plugin-livechat", "description": "PeerTube plugin livechat: create chat rooms for your Peertube lives! Comes with many features: federation, moderation tools, chat bot, chat persistence, OBS integration, ...", - "version": "10.3.3", + "version": "11.0.1", "license": "AGPL-3.0", "author": { "name": "John Livingston", @@ -73,7 +73,7 @@ "stylelint-config-recommended-scss": "^5.0.1", "stylelint-config-standard-scss": "^2.0.1", "svgo": "^2.8.0", - "typescript": "^4.3.5", + "typescript": "^4.9.5", "yaml": "^2.2.1" }, "engine": { diff --git a/prosody-modules/mod_firewall/README.markdown b/prosody-modules/mod_firewall/README.markdown new file mode 100644 index 00000000..2c9048f2 --- /dev/null +++ b/prosody-modules/mod_firewall/README.markdown @@ -0,0 +1,820 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'A rule-based stanza filtering module' +rockspec: + build: + modules: + mod_firewall.actions: actions.lib.lua + mod_firewall.conditions: conditions.lib.lua + mod_firewall.definitions: definitions.lib.lua + mod_firewall.marks: marks.lib.lua + mod_firewall.test: test.lib.lua + copy_directories: + - scripts +--- + +------------------------------------------------------------------------ + +**Note:** mod\_firewall is in its very early stages. This documentation +is liable to change, and some described functionality may be missing, +incomplete or contain bugs. + +------------------------------------------------------------------------ + +Introduction +============ + +A firewall is an invaluable tool in the sysadmin's toolbox. However +while low-level firewalls such as iptables and pf are incredibly good at +what they do, they are generally not able to handle application-layer +rules. + +The goal of mod\_firewall is to provide similar services at the XMPP +layer. Based on rule scripts it can efficiently block, bounce, drop, +forward, copy, redirect stanzas and more! Furthermore all rules can be +applied and updated dynamically at runtime without restarting the +server. + +Details +======= + +mod\_firewall loads one or more scripts, and compiles these to Lua code +that reacts to stanzas flowing through Prosody. The firewall script +syntax is unusual, but straightforward. + +A firewall script is dominated by rules. Each rule has two parts: +conditions, and actions. When a stanza matches all of the conditions, +all of the actions are executed in order. + +Here is a simple example to block stanzas from spammer@example.com: + + FROM: spammer@example.com + DROP. + +FROM is a condition, and DROP is an action. This is about as simple as +it gets. How about heading to the other extreme? Let's demonstrate +something more complex that mod\_firewall can do for you: + + %ZONE myorganisation: staff.myorg.example, support.myorg.example + + ENTERING: myorganisation + KIND: message + TIME: 12am-9am, 5pm-12am, Saturday, Sunday + REPLY=Sorry, I am afraid our office is closed at the moment. If you need assistance, please call our 24-hour support line on 123-456-789. + +This rule will reply with a short message whenever someone tries to send +a message to someone at any of the hosts defined in the 'myorganisation' +outside of office hours. + +Specifying rule sets +-------------------- + +Firewall rules should be written into text files, e.g. `ruleset.pfw` file. +One or more rule files can be specified in the configuration using: + + firewall_scripts = { "path/to/ruleset.pfw", "path/to/ruleset2.pfw" } + +If multiple files are specified and they both add rules to the same [chains](#chains), +each file's rules will be processed in order, but the order of files is undefined. + +Reloading Prosody's configuration also reloads firewall rules. + +Make sure that `firewall_scripts` is in the global section of the configuration file +and not below a virtual host or a component - unless you want per-vhost +firewall rules. + +Conditions +---------- + +All conditions must come before any action in a rule block. The +condition name is followed by a colon (':'), and the value to test for. + +A condition can be preceded or followed by `NOT` to negate its match. +For example: + + NOT FROM: user@example.com + KIND NOT: message + +Some conditions do not take parameters, and these should end with just a +question mark, like: + + IN ROSTER? + +### Zones + +A 'zone' is one or more hosts or JIDs. It is possible to match when a +stanza is entering or leaving a zone, while at the same time not +matching traffic passing between JIDs in the same zone. + +Zones are defined at the top of a script with the following syntax (they +are not part of a rule block): + + %ZONE myzone: host1, host2, user@host3, foo.bar.example + +There is an automatic zone named `$local`, which automatically includes +all of the current server's active hosts (including components). It can +be used to match stanzas entering or leaving the current server. + +A host listed in a zone also matches all users on that host (but not +subdomains). + +The following zone-matching conditions are supported: + + Condition Matches + ------------ ------------------------------------------ + `ENTERING` When a stanza is entering the named zone + `LEAVING` When a stanza is leaving the named zone + +### Lists + +It is possible to create or load lists of strings for use in scripts. For +example, you might load a list of blocked JIDs, malware URLs or simple words +that you want to filter messages on. + + List type Example + ----------- ----------------------- + memory %LIST spammers: memory + file %LIST spammers: file:/etc/spammers.txt + http %LIST spammers: http://example.com/spammers.txt + +#### List types +##### memory + +``` +%LIST name: memory (limit: number) +``` + +A memory-only list, with an optional limit. Supports addition and removal of items by scripts. + +If a limit is provided, the oldest item will be discarded to make room for a new item if the +list is full. The limit is useful to prevent infinite memory growth on busy servers. + +##### file + +``` +%LIST name: file:/path/to/file (missing: string) +``` + +Reads a list from a file. The list can be added to and removed from by scripts, but +these changes do not persist between restarts. + +If the file is missing, an error will be raised. The optional 'missing' parameter can be set +to 'ignore' (e.g. `(missing: ignore)`) to ignore a missing file. + +##### http + +``` +%LIST name: http://example.com/ (ttl: number, pattern: pat, hash: sha1, checkcerts: when-sni) +``` + +Fetches a list from a HTTP or HTTPS URL. The following options are accepted: + + Option Description + ------- ----------- + ttl Seconds to cache the list for. After expiry, it will be refetched. Default 3600 (1 hour). + pattern Optional pattern used to extract list entries from the response. Default is to treat each line as a single item. + hash Optional hash to be applied to items before looking them up in the list, e.g. sha1 or sha256. + checkcert Whether to verify HTTPS certificates. May be "always", "never" or "when-sni". Default "when-sni". + +The "when-sni" default disables certificate verification when Prosody's HTTP client API doesn't support SNI, +as in Prosody 0.11.6 and earlier. + +#### CHECK LIST + +Checks whether a simple [expression](#expressions) is found in a given list. + +Example: + + %LIST blocked_jids: file:/etc/prosody/blocked_jids.txt + + # Rule to block presence subscription requests from blocked JIDs + KIND: presence + TYPE: subscribe + CHECK LIST: blocked_jids contains $<@from> + BOUNCE=policy-violation (Your JID is blocked) + +#### SCAN + +SCAN allows you to search inside a stanza for a given pattern, and check each result against a list. For example, +you could scan a message body for words and check if any of the words are found in a given list. + +Before using SCAN, you need to define a search location and a pattern. The search location uses the same 'path' +format as documented under the 'INSPECT' condition. Patterns can be any valid Lua pattern. + +To use the above example: + + # Define a search location called 'body' which fetches the text of the 'body' element + %SEARCH body: body# + # Define a pattern called 'word' which matches any sequence of letters + %PATTERN word: [A-Za-z]+ + # Finally, we also need our list of "bad" words: + %LIST badwords: file:/etc/prosody/bad_words.txt + + # Now we can use these to SCAN incoming stanzas + # If it finds a match, bounce the stanza + SCAN: body for word in badwords + BOUNCE=policy-violation (This word is not allowed!) + +#### COUNT + +COUNT is similar to SCAN, in that it uses a defined SEARCH and breaks it up according to a PATTERN. Then it +counts the number of results. + +For example, to block every message with more than one URL: + + # Define a search location called 'body' which fetches the text of the 'body' element + %SEARCH body: body# + # Define a pattern called 'url' which matches HTTP links + %PATTERN url: https?://%S+ + + COUNT: url in body > 1 + BOUNCE=policy-violation (Up to one HTTP URL is allowed in messages) + +### Stanza matching + + Condition Matches + ----------- ------------------------------------------------------------------------------------------------------------------------------------------------------------ + `KIND` The kind of stanza. May be 'message', 'presence' or 'iq' + `TYPE` The type of stanza. This varies depending on the kind of stanza. See 'Stanza types' below for more information. + `PAYLOAD` The stanza contains a child with the given namespace. Useful for determining the type of an iq request, or whether a message contains a certain extension. + `INSPECT` The node at the specified path exists or matches a given string. This allows you to look anywhere inside a stanza. See below for examples and more. + +#### Stanza types + + Stanza Valid types + ---------- ------------------------------------------------------------------------------------------ + iq get, set, result, error + presence *available*, unavailable, probe, subscribe, subscribed, unsubscribe, unsubscribed, error + message normal, chat, groupchat, headline, error + +**Note:** The type 'available' for presence does not actually appear in +the protocol. Available presence is signalled by the omission of a type. +Similarly, a message stanza with no type is equivalent to one of type +'normal'. mod\_firewall handles these cases for you automatically. + +#### Sender/recipient matching + + Condition Matches + --------------- ------------------------------------------------------- + `FROM` The JID in the 'from' attribute matches the given JID. + `TO` The JID in the 'to' attribute matches the given JID. + `TO SELF` The stanza is sent by any of a user's resources to their own bare JID. + `TO FULL JID` The stanza is addressed to a **valid** full JID on the local server (full JIDs include a resource at the end, and only exist for the lifetime of a single session, therefore the recipient **must be online**, or this check will not match). + `FROM FULL JID` The stanza is from a full JID (unlike `TO FULL JID` this check is on the format of the JID only). + +The TO and FROM conditions both accept wildcards in the JID when it is +enclosed in angle brackets ('\<...\>'). For example: + + # All users at example.com + FROM: <*>@example.com + + # The user 'admin' on any subdomain of example.com + FROM: admin@<*.example.com> + +You can also use [Lua's pattern +matching](http://www.lua.org/manual/5.1/manual.html#5.4.1) for more +powerful matching abilities. Patterns are a lightweight +regular-expression alternative. Simply contain the pattern in double +angle brackets. The pattern is automatically anchored at the start and +end (so it must match the entire portion of the JID). + + # Match admin@example.com, and admin1@example.com, etc. + FROM: <>@example.com + +**Note:** It is important to know that 'example.com' is a valid JID on +its own, and does **not** match 'user@example.com'. To efficiently match +domains we recommend defining them as [Zones](#zones). + + Condition Matches + ---------------- --------------------------------------------------------------- + `FROM_EXACTLY` The JID in the 'from' attribute exactly matches the given JID + `TO_EXACTLY` The JID in the 'to' attribute exactly matches the given JID + +These additional conditions do not support pattern matching, but are +useful to match the exact to/from address on a stanza. For example, if +no resource is specified then only bare JIDs will be matched. TO and FROM +match all resources if no resource is specified to match. + +**Note:** Some chains execute before Prosody has performed any +normalisation or validity checks on the to/from JIDs on an incoming +stanza. It is not advisable to perform access control or similar rules +on JIDs in these chains (see the [chain documentation](#chains) for more info). + +#### GeoIP matching + + Condition Matches + ---------------- -------------------------------------------------------------- + `FROM COUNTRY` Two or three letter country code looked up in GeoIP database + +This condition uses a GeoIP database to look up the origin country of +the IP attached to the current session. + +For example: + + # 3 letter country code + FROM COUNTRY: SWE + + # or 2 letter + FROM COUNTRY: SE + + # Explicit + FROM COUNTRY: code=SE + FROM COUNTRY: code3=SWE + +**Note:** This requires that the `lua-geoip` and `geoip-database` +packages are installed (on Debian, package names may differ on other +operating systems). + +#### INSPECT + +INSPECT takes a 'path' through the stanza to get a string (an attribute +value or text content). An example is the best way to explain. Let's +check that a user is not trying to register an account with the username +'admin'. This stanza comes from [XEP-0077: In-band +Registration](http://xmpp.org/extensions/xep-0077.html#example-4): + +``` xml + + + bill + Calliope + bard@shakespeare.lit + + +``` + + KIND: iq + TYPE: set + PAYLOAD: jabber:iq:register + INSPECT: {jabber:iq:register}query/username#=admin + BOUNCE=not-allowed (The username 'admin' is reserved.) + +That weird string deserves some explanation. It is a path, divided into +segments by '/'. Each segment describes an element by its name, +optionally prefixed by its namespace in curly braces ('{...}'). If the +path ends with a '\#' then the text content of the last element will be +returned. If the path ends with '@name' then the value of the attribute +'name' will be returned. + +You can use INSPECT to test for the existence of an element or attribute, +or you can check if it matches a specific value, e.g. by appending `=VALUE` +(like in the example above, that checks if the content of username is 'admin'). + +#### INSPECT comparison operators + +As well as checking for an exact string match, there are some other modifiers +you can apply to the comparison: + + Comparison Matches when + ------------- ------------------------------------------------------- + `=` The value is exactly the given string. + `/=` The value is or *contains* the given string (e.g. `/=admin` would match `administrator` or `myadmin`). + `~=` The value matches the given [Lua pattern](https://www.lua.org/manual/5.2/manual.html#6.4.1). + +Finally, if the comparison operator is preceded by a `$` character, [expressions](#expressions) +will be interpreted in the string following the comparison operator. + +e.g. `INSPECT: {jabber:iq:register}query/username}$/=$(session.host)` would match +if the username of an account registration contained the session's current hostname +somewhere in it. + +#### INSPECT performance + +INSPECT can be somewhat slower than the other stanza matching conditions. To +minimise performance impact, always place it below other faster +condition checks where possible (e.g. in the example above we first checked KIND, +TYPE and PAYLOAD matched what we wanted before reaching the INSPECT rule). + +### Roster + +These conditions access the roster of the recipient (only). Therefore they cannot (currently) +be used in some [chains](#chains), such as for outgoing messages (the recipient may be on another server). + +Performance note: these checks can potentially cause storage access (especially if the recipient +is currently offline), so you may want to limit their use in high-traffic situations, and place rules +containing a roster check below other rules (such as a rate limiter). The storage access is +performed unconditionally just before evaluation of the first rule that contains a roster-based +condition, even if earlier conditions in that rule do not match. + +#### IN ROSTER + +Tests whether the sender is in the recipient's roster. + + IN ROSTER? + +#### IN ROSTER GROUP + +Tests whether the sender is in the recipient's roster, and in the named group. + + IN ROSTER GROUP: Friends + +#### SUBSCRIBED + +Tests whether the recipient is subscribed to the sender, ie will receive +presence updates from them. + +Note that this *does* work, regardless of direction and which [chain](#chain) is +used, since both the sender and the recipient will have mirrored roster +entries. + +### Groups + +Using Prosody's mod\_groups it is possible to define groups of users on the server. You can +match based on these groups in firewall rules. + + Condition Matches + ----------------- ---------------------------- + `FROM GROUP` When the stanza is being sent from a member of the named group + `TO GROUP` When the stanza is being sent to a member of the named group + `CROSSING GROUPS` When the stanza is being sent between users of different named groups + +#### CROSSING GROUPS + +The `CROSSING GROUPS` condition takes a comma-separated list of groups to check. If the +sender and recipient are not in the same group (only the listed groups are checked), then the +this condition matches and the stanza is deemed to be crossing between groups. + +For example, if you had three groups: Engineering, Marketing and Employees. All users are +members of the 'Employees' group, and the others are for employees of the named department only. + +To prevent employees in the marketing department from communicating with engineers, you could use +the following rule: + +``` +CROSSING GROUPS: Marketing, Engineering +BOUNCE=policy-violation (no communication between these groups is allowed!) +``` + +This works, even though both the users are in the 'Employees' group, because that group is not listed +in the condition. + +In the above example, a user who is member of both groups is not restricted. + +#### SENT DIRECTED PRESENCE TO SENDER + +This condition matches if the recipient of a stanza has previously sent directed presence to the sender of the stanza. This +is often done in XMPP to exchange presence information with JIDs that are not on your roster, such as MUC rooms. + +This condition does not take a parameter - end the condition name with a question mark: + + # Rule to bounce messages from senders not in the roster who haven't been sent directed presence + NOT IN ROSTER? + NOT SENT DIRECTED PRESENCE TO SENDER? + BOUNCE=service-unavailable + +### Permissions + +Rules can consult Prosody's internal role and permissions system to check whether a certain action may +be performed. The acting entity, their role, and appropriate context is automatically inferred. All you +need to do is provide the identifier of the permission that should be checked. + + Condition Description + ----------------------- -------------------------------------------------------------------- + `MAY=permission` Checks whether 'permission' is allowed in the current context. + +As with all other conditions, `MAY` can be combined with `NOT` to negate the result of the check. + +Example, blocking outgoing stanzas from users with roles that do not allow the 'xmpp:federate' permission: + +``` +::deliver_remote +MAY NOT: xmpp:federate +BOUNCE=policy-violation (You are not allowed access to the federation) +``` + +### Roles + + Condition Matches + ---------------- ------------------------------------------------------------------------------------- + `TO ROLE` When the recipient JID of the stanza has the named role + `FROM ROLE` When the sender JID of the stanza has the named role + +**Note:** In most cases, you should avoid checking for specific roles, and instead check for +permissions granted by those roles (using the 'MAY' condition). + +### Admins + +**Deprecated:** These conditions should no longer be used. Prefer 'MAY', 'TO ROLE' or 'FROM ROLE'. + +Prosody allows certain JIDs to be declared as administrators of a host, component or the whole server. + + Condition Matches + ---------------- ------------------------------------------------------------------------------------- + `TO ADMIN` When the recipient of the stanza is admin of the current host + `FROM ADMIN` When the sender of the stanza is admin of the current host + `FROM ADMIN OF` When the sender of the stanza is an admin of the named host on the current server + `TO ADMIN OF` When the recipient of the stanza is an admin of the named host on the current server + +### Time and date + +#### TIME + +Matches stanzas sent during certain time periods. + + Condition Matches + ----------- ------------------------------------------------------------------------------------------- + TIME When the current server local time is within one of the comma-separated time ranges given + + TIME: 10pm-6am, 14:00-15:00 + REPLY=Zzzz. + +#### DAY + +It is also possible to match only on certain days of the week. + + Condition Matches + ----------- ----------------------------------------------------------------------------------------------------- + DAY When the current day matches one, or falls within a rage, in the given comma-separated list of days + +Example: + + DAY: Sat-Sun, Wednesday + REPLY=Sorry, I'm out enjoying life! + +All times and dates are handled in the server's local time. + +### Rate-limiting + +It is possible to selectively rate-limit stanzas, and use rules to +decide what to do with stanzas when over the limit. + +First, you must define any rate limits that you are going to use in your +script. Here we create a limiter called 'normal' that will allow 2 +stanzas per second, and then we define a rule to bounce messages when +over this limit. Note that the `RATE` definition is not part of a rule +(multiple rules can share the same limiter). + + %RATE normal: 2 (burst 3) + + KIND: message + LIMIT: normal + BOUNCE=policy-violation (Sending too fast!) + +The 'burst' parameter on the rate limit allows you to spread the limit +check over a given time period. For example the definition shown above +will allow the limit to be temporarily surpassed, as long as it is +within the limit after 3 seconds. You will almost always want to specify +a burst factor. + +Both the rate and the burst can be fractional values. For example a rate +of 0.1 means only one event is allowed every 10 seconds. + +The LIMIT condition actually does two things; first it counts against +the given limiter, and then it checks to see if the limiter over its +limit yet. If it is, the condition matches, otherwise it will not. + + Condition Matches + ----------- -------------------------------------------------------------------------------------------------- + `LIMIT` When the named limit is 'used up'. Using this condition automatically counts against that limit. + +**Note:** Reloading mod\_firewall resets the current state of any +limiters. + +#### Dynamic limits + +Sometimes you may want to have multiple throttles in a single condition, using some property of the session or stanza +to determine which throttle to use. For example, you might have a limit for incoming stanzas, but you want to limit by +sending JID, instead of all incoming stanzas sharing the same limit. + +You can use the 'on' keyword for this, like so: + + LIMIT: normal on EXPRESSION + +For more information on [expressions](#expressions), see the section later in this document. + +Each value of 'EXPRESSION' has to be tracked individually in a table, which uses a small amount of memory. To prevent +memory exhaustion, the number of tracked values is limited to 1000 by default. You can override this by setting the +maximum number of table entries when you define the rate: + + %RATE normal: 2 (burst 3) (entries 4096) + +Old values are automatically removed from the tracking table. However if the tracking table becomes full, new entries +will be rejected - it will behave as if the rate limit was reached, even for values that have not been seen before. Since +this opens up a potential denial of service (innocent users may be affected if malicious users can fill up the tracking +table within the limit period). You can choose to instead "fail open", and allow the rate limit to be temporarily bypassed +when the table is full. To choose this behaviour, add `(allow overflow)` to the RATE definition. + +### Session marking + +It is possible to 'mark' sessions (see the MARK_ORIGIN action below). To match stanzas from marked sessions, use the +`ORIGIN_MARKED` condition. + + Condition Description + ------------------------------- --------------------------------------------------------------- + ORIGIN MARKED: markname Matches if the origin has been marked with 'markname'. + ORIGIN MARKED: markname (Xs) Matches if the origin has been marked with 'markname' within the past X seconds. + +Example usage: + + # This rule drops messages from sessions that have been marked as spammers in the past hour + ORIGIN MARKED: spammer (3600s) + DROP. + + # This rule marks the origin session as a spammer if they send a message to a honeypot JID + KIND: message + TO: honeypot@example.com + MARK ORIGIN=spammer + +Actions +------- + +Actions come after all conditions in a rule block. There must be at +least one action, though conditions are optional. + +An action without parameters ends with a full-stop/period ('.'), and one +with parameters uses an equals sign ('='): + + # An action with no parameters: + DROP. + + # An action with a parameter: + REPLY=Hello, this is a reply. + +### Route modification + +The following common actions modify the stanza's route in some way. These +rules will halt further processing of the stanza - no further actions will be +executed, and no further rules will be checked. + + Action Description + ----------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------- + `PASS.` Stop executing actions and rules on this stanza, and let it through this chain and any calling chains. + `DROP.` Stop executing actions and rules on this stanza, and discard it. + `DEFAULT.` Stop executing actions and rules on this stanza, prevent any other scripts/modules from handling it, to trigger the appropriate default "unhandled stanza" behaviour. Do not use in custom chains (it is treated as PASS). + `REDIRECT=jid` Redirect the stanza to the given JID. + `BOUNCE.` Bounce the stanza with the default error (usually service-unavailable) + `BOUNCE=error` Bounce the stanza with the given error (MUST be a defined XMPP stanza error, see [RFC6120](http://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions). + `BOUNCE=error (text)` As above, but include the supplied human-readable text with a description of the error + +**Note:** It is incorrect behaviour to reply to an 'error' stanza with another error, so BOUNCE will simply act the same as 'DROP' for stanzas that should not be bounced (error stanzas and iq results). + +### Replying and forwarding + +These actions cause a new stanza to be generated and sent somewhere. +Processing of the original stanza will continue beyond these actions. + + Action Description + ------------------------ --------------------------------------------------------------------------------------------------------------------------------------------------------- + `REPLY=text` Reply to the stanza (assumed to be a message) with the given text. + `COPY=jid` Make a copy of the stanza and send the copy to the specified JID. The copied stanza flows through Prosody's routing code, and as such is affected by firewall rules. Be careful to avoid loops. + `FORWARD=jid` Forward a copy of the stanza to the given JID (using XEP-0297). The stanza will be sent from the current host's JID. + +### Reporting + + Action Description + ------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------- + `REPORT TO=jid [reason] [text]` Forwards the full stanza to `jid` with a XEP-0377 abuse report attached. + +Only the `jid` is mandatory. The `reason` parameter should be either `abuse`, `spam` or a custom URI. If not specified, it defaults to `abuse`. +After the reason, some human-readable text may be included to explain the report. + +Example: + +``` +KIND: message +TO: honeypot@example.com +REPORT TO=antispam.example.com spam Caught by the honeypot! +DROP. +``` + +### Stanza modification + +These actions make it possible to modify the content and structure of a +stanza. + + Action Description + ------------------------ ------------------------------------------------------------------------ + `STRIP=name` Remove any child elements with the given name in the default namespace + `STRIP=name namespace` Remove any child elements with the given name and the given namespace + `INJECT=xml` Inject the given XML into the stanza as a child element + +### Sessions + +It is possible to mark sessions, and then use these marks to match rules later on. + + Action Description + ------------------------ -------------------------------------------------------------------------- + `MARK ORIGIN=mark` Marks the originating session with the given flag. + `UNMARK ORIGIN=mark` Removes the given mark from the origin session (if it is set). + +**Note:** Marks apply to sessions, not JIDs. E.g. if marking in a rule that matches a stanza received +over s2s, it is the s2s session that is marked. + +It is possible to have multiple marks on an origin at any given time. + +### Informational + + Action Description + --------------- ------------------------------------------------------------------------------------------------------------------------ + `LOG=message` Logs the given message to Prosody's log file. Optionally prefix it with a log level in square brackets, e.g. `[debug]` + +You can include [expressions](#expressions) in log messages, using `$(...)` syntax. For example, to log the stanza that matched the rule, +you can use `$(stanza)`, or to log just the top tag of the stanza, use `$(stanza:top_tag())`. To fetch the sender JID, use `$(stanza.attr.from)`. + +Example: + + # Log all stanzas to user@example.com: + TO: user@example.com + LOG=[debug] User received: $(stanza) + +More info about expressions can be found below. + +Chains +------ + +Rules are grouped into "chains", which are injected at particular points in Prosody's routing code. + +Available built-in chains are: + + Chain Description + -------------- ------------------------------------------------------------------------------------------- + deliver Applies to stanzas delivered to local recipients (regardless of the stanza's origin) + deliver_remote Applies to stanzas delivered to remote recipients (just before they leave the local server) + preroute Applies to incoming stanzas from local users, before any routing rules are applied + +A chain is begun by a line `::name` where 'name' is the name of the chain you want the following rules to be +inserted into. If no chain is specified, rules are put into the 'deliver' chain. + +It is possible to create custom chains (useful with the `JUMP CHAIN` action described below). User-created +chains must begin with "user/", e.g. "user/spam_filtering". + +Example of chain use: + + # example.com's firewall script + + # This line is optional, because 'deliver' is the default chain anyway: + ::deliver + + # This rule matches any stanzas delivered to our local user bob: + TO: bob@example.com + DROP. + + # Oops! This rule will never match, because alice is not a local user, + # and only stanzas to local users go through the 'deliver' chain: + TO: alice@remote.example.com + DROP. + + # Create a 'preroute' chain of rules (matched for incoming stanzas from local clients): + ::preroute + # These rules are matched for outgoing stanzas from local clients + + # This will match any stanzas sent to alice from a local user: + TO: alice@remote.example.com + DROP. + + Action Description + ------------------------ ------------------------------------------------------------------------ + `JUMP CHAIN=name` Switches chains, and passes the stanza through the rules in chain 'name'. If the new chain causes the stanza to be dropped/redirected, the current chain halts further processing. + `RETURN.` Stops executing the current chain and returns to the parent chain. For built-in chains, equivalent to PASS. RETURN is implicit at the end of every chain. + +It is possible to jump to chains defined by other scripts and modules. + +Expressions +----------- + +Some conditions and actions in rules support "expressions" in their parameters (their documentation will indicate if this is the case). Most parameters +are static once the firewall script is loaded and compiled internally, however parameters that allow expressions can be dynamically calculated when a +rule is being run. + +There are two kinds of expression that you can use: stanza expressions, and code expressions. + +### Stanza expressions + +Stanza expressions are of the form `$<...>`, where `...` is a stanza path. For syntax of stanza paths, see the documentation for the 'INSPECT' condition +above. + +Example: + + LOG=Matched a stanza from $<@from> to $<@to> + +There are built in functions which can be applied to the output of a stanza expression, by appending the pipe ('|') operator, followed by the function +name. These functions are: + + Function Description + ------------ --------------------------------------- + bare Given a JID, strip any resource + node Return the node ('user part') of a JID + host Return the host ('domain') part of a JID + resource Return the resource part of a JID + +For example, to apply a rate limit to stanzas per sender domain: + + LIMIT normal on $<@from|host> + +If the path does not match (e.g. the element isn't found, or the attribute doesn't exist) or any of the functions fail to produce an output (e.g. an invalid +JID was passed to a function that only handles valid JIDs) the expression will return the text ``. You can override this by ending the expression +with a double pipe ('||') followed by a quoted string to use as a default instead. E.g. to default to the string "normal" when there is no 'type' attribute: + + LOG=Stanza type is $<@type||"normal"> + +### Code expressions + +Code expressions use `$(...)` syntax. Code expressions are powerful, and allow unconstrained access to Prosody's internal environment. Therefore +code expressions are typically for advanced use-cases only. You may want to refer to Prosody's [developer documentation](https://prosody.im/doc/developers) +for more information. In particular, within code expressions you may access the 'session' object, which is the session object of the origin of the stanza, +and the 'stanza' object, which is the stanza being considered within the current rule. Whatever value the expression returns will be converted to a string. + +Example to limit stanzas per session type: + + LIMIT: normal on $(session.type) diff --git a/prosody-modules/mod_firewall/actions.lib.lua b/prosody-modules/mod_firewall/actions.lib.lua new file mode 100644 index 00000000..961a5c0e --- /dev/null +++ b/prosody-modules/mod_firewall/actions.lib.lua @@ -0,0 +1,280 @@ +local unpack = table.unpack or unpack; + +local interpolation = require "util.interpolation"; +local template = interpolation.new("%b$$", function (s) return ("%q"):format(s) end); + +--luacheck: globals meta idsafe +local action_handlers = {}; + + +-- Takes an XML string and returns a code string that builds that stanza +-- using st.stanza() +local function compile_xml(data) + local code = {}; + local first, short_close = true, nil; + for tagline, text in data:gmatch("<([^>]+)>([^<]*)") do + if tagline:sub(-1,-1) == "/" then + tagline = tagline:sub(1, -2); + short_close = true; + end + if tagline:sub(1,1) == "/" then + code[#code+1] = (":up()"); + else + local name, attr = tagline:match("^(%S*)%s*(.*)$"); + local attr_str = {}; + for k, _, v in attr:gmatch("(%S+)=([\"'])([^%2]-)%2") do + if #attr_str == 0 then + table.insert(attr_str, ", { "); + else + table.insert(attr_str, ", "); + end + if k:find("^%a%w*$") then + table.insert(attr_str, string.format("%s = %q", k, v)); + else + table.insert(attr_str, string.format("[%q] = %q", k, v)); + end + end + if #attr_str > 0 then + table.insert(attr_str, " }"); + end + if first then + code[#code+1] = (string.format("st.stanza(%q %s)", name, #attr_str>0 and table.concat(attr_str) or ", nil")); + first = nil; + else + code[#code+1] = (string.format(":tag(%q%s)", name, table.concat(attr_str))); + end + end + if text and text:find("%S") then + code[#code+1] = (string.format(":text(%q)", text)); + elseif short_close then + short_close = nil; + code[#code+1] = (":up()"); + end + end + return table.concat(code, ""); +end + +function action_handlers.PASS() + return "do return pass_return end" +end + +function action_handlers.DROP() + return "do return true end"; +end + +function action_handlers.DEFAULT() + return "do return false end"; +end + +function action_handlers.RETURN() + return "do return end" +end + +function action_handlers.STRIP(tag_desc) + local code = {}; + local name, xmlns = tag_desc:match("^(%S+) (.+)$"); + if not name then + name, xmlns = tag_desc, nil; + end + if name == "*" then + name = nil; + end + code[#code+1] = ("local stanza_xmlns = stanza.attr.xmlns; "); + code[#code+1] = "stanza:maptags(function (tag) if "; + if name then + code[#code+1] = ("tag.name == %q and "):format(name); + end + if xmlns then + code[#code+1] = ("(tag.attr.xmlns or stanza_xmlns) == %q "):format(xmlns); + else + code[#code+1] = ("tag.attr.xmlns == stanza_xmlns "); + end + code[#code+1] = "then return nil; end return tag; end );"; + return table.concat(code); +end + +function action_handlers.INJECT(tag) + return "stanza:add_child("..compile_xml(tag)..")", { "st" }; +end + +local error_types = { + ["bad-request"] = "modify"; + ["conflict"] = "cancel"; + ["feature-not-implemented"] = "cancel"; + ["forbidden"] = "auth"; + ["gone"] = "cancel"; + ["internal-server-error"] = "cancel"; + ["item-not-found"] = "cancel"; + ["jid-malformed"] = "modify"; + ["not-acceptable"] = "modify"; + ["not-allowed"] = "cancel"; + ["not-authorized"] = "auth"; + ["payment-required"] = "auth"; + ["policy-violation"] = "modify"; + ["recipient-unavailable"] = "wait"; + ["redirect"] = "modify"; + ["registration-required"] = "auth"; + ["remote-server-not-found"] = "cancel"; + ["remote-server-timeout"] = "wait"; + ["resource-constraint"] = "wait"; + ["service-unavailable"] = "cancel"; + ["subscription-required"] = "auth"; + ["undefined-condition"] = "cancel"; + ["unexpected-request"] = "wait"; +}; + + +local function route_modify(make_new, to, drop) + local reroute, deps = "session.send(newstanza)", { "st" }; + if to then + reroute = ("newstanza.attr.to = %q; core_post_stanza(session, newstanza)"):format(to); + deps[#deps+1] = "core_post_stanza"; + end + return ([[do local newstanza = st.%s; %s;%s end]]) + :format(make_new, reroute, drop and " return true" or ""), deps; +end + +function action_handlers.BOUNCE(with) + local error = with and with:match("^%S+") or "service-unavailable"; + local error_type = error:match(":(%S+)"); + if not error_type then + error_type = error_types[error] or "cancel"; + else + error = error:match("^[^:]+"); + end + error, error_type = string.format("%q", error), string.format("%q", error_type); + local text = with and with:match(" %((.+)%)$"); + if text then + text = string.format("%q", text); + else + text = "nil"; + end + local route_modify_code, deps = route_modify(("error_reply(stanza, %s, %s, %s)"):format(error_type, error, text), nil, true); + deps[#deps+1] = "type"; + deps[#deps+1] = "name"; + return [[if type == "error" or (name == "iq" and type == "result") then return true; end -- Don't reply to 'error' stanzas, or iq results + ]]..route_modify_code, deps; +end + +function action_handlers.REDIRECT(where) + return route_modify("clone(stanza)", where, true); +end + +function action_handlers.COPY(where) + return route_modify("clone(stanza)", where, false); +end + +function action_handlers.REPLY(with) + return route_modify(("reply(stanza):body(%q)"):format(with)); +end + +function action_handlers.FORWARD(where) + local code = [[ + local newstanza = st.stanza("message", { to = %q, from = current_host }):tag("forwarded", { xmlns = "urn:xmpp:forward:0" }); + local tmp_stanza = st.clone(stanza); tmp_stanza.attr.xmlns = "jabber:client"; newstanza:add_child(tmp_stanza); + core_post_stanza(session, newstanza); + ]]; + return code:format(where), { "core_post_stanza", "current_host" }; +end + +function action_handlers.LOG(string) + local level = string:match("^%[(%a+)%]") or "info"; + string = string:gsub("^%[%a+%] ?", ""); + local meta_deps = {}; + local code = meta(("(session.log or log)(%q, '%%s', %q);"):format(level, string), meta_deps); + return code, meta_deps; +end + +function action_handlers.RULEDEP(dep) + return "", { dep }; +end + +function action_handlers.EVENT(name) + return ("fire_event(%q, event)"):format(name); +end + +function action_handlers.JUMP_EVENT(name) + return ("do return fire_event(%q, event); end"):format(name); +end + +function action_handlers.JUMP_CHAIN(name) + return template([[do + local ret = fire_event($chain_event$, event); + if ret ~= nil then + if ret == false then + log("debug", "Chain %q accepted stanza (ret %s)", $chain_name$, tostring(ret)); + return pass_return; + end + log("debug", "Chain %q rejected stanza (ret %s)", $chain_name$, tostring(ret)); + return ret; + end + end]], { chain_event = "firewall/chains/"..name, chain_name = name }); +end + +function action_handlers.MARK_ORIGIN(name) + return [[session.firewall_marked_]]..idsafe(name)..[[ = current_timestamp;]], { "timestamp" }; +end + +function action_handlers.UNMARK_ORIGIN(name) + return [[session.firewall_marked_]]..idsafe(name)..[[ = nil;]] +end + +function action_handlers.MARK_USER(name) + return ([[if session.username and session.host == current_host then + fire_event("firewall/marked/user", { + username = session.username; + mark = %q; + timestamp = current_timestamp; + }); + else + log("warn", "Attempt to MARK a remote user - only local users may be marked"); + end]]):format(assert(idsafe(name), "Invalid characters in mark name: "..name)), { + "current_host"; + "timestamp"; + }; +end + +function action_handlers.UNMARK_USER(name) + return ([[if session.username and session.host == current_host then + fire_event("firewall/unmarked/user", { + username = session.username; + mark = %q; + }); + else + log("warn", "Attempt to UNMARK a remote user - only local users may be marked"); + end]]):format(assert(idsafe(name), "Invalid characters in mark name: "..name)); +end + +function action_handlers.ADD_TO(spec) + local list_name, value = spec:match("(%S+) (.+)"); + local meta_deps = {}; + value = meta(("%q"):format(value), meta_deps); + return ("list_%s:add(%s);"):format(list_name, value), { "list:"..list_name, unpack(meta_deps) }; +end + +function action_handlers.UNSUBSCRIBE_SENDER() + return "rostermanager.unsubscribed(to_node, to_host, bare_from);\ + rostermanager.roster_push(to_node, to_host, bare_from);\ + core_post_stanza(session, st.presence({ from = bare_to, to = bare_from, type = \"unsubscribed\" }));", + { "rostermanager", "core_post_stanza", "st", "split_to", "bare_to", "bare_from" }; +end + +function action_handlers.REPORT_TO(spec) + local where, reason, text = spec:match("^%s*(%S+) *(%S*) *(.*)$"); + if reason == "spam" then + reason = "urn:xmpp:reporting:spam"; + elseif reason == "abuse" or not reason or reason == "" then + reason = "urn:xmpp:reporting:abuse"; + end + local code = [[ + local newstanza = st.stanza("message", { to = %q, from = current_host, id = new_short_id() }):tag("forwarded", { xmlns = "urn:xmpp:forward:0" }); + local tmp_stanza = st.clone(stanza); tmp_stanza.attr.xmlns = "jabber:client"; newstanza:add_child(tmp_stanza):up(); + newstanza:tag("report", { xmlns = "urn:xmpp:reporting:1", reason = %q }) + do local text = %q; if text ~= "" then newstanza:text_tag("text", text); end end + newstanza:up(); + core_post_stanza(session, newstanza); + ]]; + return code:format(where, reason, text), { "core_post_stanza", "current_host", "st", "new_short_id" }; +end + +return action_handlers; diff --git a/prosody-modules/mod_firewall/conditions.lib.lua b/prosody-modules/mod_firewall/conditions.lib.lua new file mode 100644 index 00000000..718089de --- /dev/null +++ b/prosody-modules/mod_firewall/conditions.lib.lua @@ -0,0 +1,410 @@ +--luacheck: globals meta idsafe +local condition_handlers = {}; + +local jid = require "util.jid"; +local unpack = table.unpack or unpack; + +-- Helper to convert user-input strings (yes/true//no/false) to a bool +local function string_to_boolean(s) + s = s:lower(); + return s == "yes" or s == "true"; +end + +-- Return a code string for a condition that checks whether the contents +-- of variable with the name 'name' matches any of the values in the +-- comma/space/pipe delimited list 'values'. +local function compile_comparison_list(name, values) + local conditions = {}; + for value in values:gmatch("[^%s,|]+") do + table.insert(conditions, ("%s == %q"):format(name, value)); + end + return table.concat(conditions, " or "); +end + +function condition_handlers.KIND(kind) + assert(kind, "Expected stanza kind to match against"); + return compile_comparison_list("name", kind), { "name" }; +end + +local wildcard_equivs = { ["*"] = ".*", ["?"] = "." }; + +local function compile_jid_match_part(part, match) + if not match then + return part.." == nil"; + end + local pattern = match:match("^<(.*)>$"); + if pattern then + if pattern == "*" then + return part; + end + if pattern:find("^<.*>$") then + pattern = pattern:match("^<(.*)>$"); + else + pattern = pattern:gsub("%p", "%%%0"):gsub("%%(%p)", wildcard_equivs); + end + return ("(%s and %s:find(%q))"):format(part, part, "^"..pattern.."$"); + else + return ("%s == %q"):format(part, match); + end +end + +local function compile_jid_match(which, match_jid) + local match_node, match_host, match_resource = jid.split(match_jid); + local conditions = {}; + conditions[#conditions+1] = compile_jid_match_part(which.."_node", match_node); + conditions[#conditions+1] = compile_jid_match_part(which.."_host", match_host); + if match_resource then + conditions[#conditions+1] = compile_jid_match_part(which.."_resource", match_resource); + end + return table.concat(conditions, " and "); +end + +function condition_handlers.TO(to) + return compile_jid_match("to", to), { "split_to" }; +end + +function condition_handlers.FROM(from) + return compile_jid_match("from", from), { "split_from" }; +end + +function condition_handlers.FROM_FULL_JID() + return "not "..compile_jid_match_part("from_resource", nil), { "split_from" }; +end + +function condition_handlers.FROM_EXACTLY(from) + local metadeps = {}; + return ("from == %s"):format(metaq(from, metadeps)), { "from", unpack(metadeps) }; +end + +function condition_handlers.TO_EXACTLY(to) + local metadeps = {}; + return ("to == %s"):format(metaq(to, metadeps)), { "to", unpack(metadeps) }; +end + +function condition_handlers.TO_SELF() + -- Intentionally not using 'to' here, as that defaults to bare JID when nil + return ("stanza.attr.to == nil"); +end + +function condition_handlers.TYPE(type) + assert(type, "Expected 'type' value to match against"); + return compile_comparison_list("(type or (name == 'message' and 'normal') or (name == 'presence' and 'available'))", type), { "type", "name" }; +end + +local function zone_check(zone, which) + local zone_var = zone; + if zone == "$local" then zone_var = "_local" end + local which_not = which == "from" and "to" or "from"; + return ("(zone_%s[%s_host] or zone_%s[%s] or zone_%s[bare_%s]) " + .."and not(zone_%s[%s_host] or zone_%s[%s] or zone_%s[bare_%s])" + ) + :format(zone_var, which, zone_var, which, zone_var, which, + zone_var, which_not, zone_var, which_not, zone_var, which_not), { + "split_to", "split_from", "bare_to", "bare_from", "zone:"..zone + }; +end + +function condition_handlers.ENTERING(zone) + return zone_check(zone, "to"); +end + +function condition_handlers.LEAVING(zone) + return zone_check(zone, "from"); +end + +-- IN ROSTER? (parameter is deprecated) +function condition_handlers.IN_ROSTER(yes_no) + local in_roster_requirement = string_to_boolean(yes_no or "yes"); -- COMPAT w/ older scripts + return "not "..(in_roster_requirement and "not" or "").." roster_entry", { "roster_entry" }; +end + +function condition_handlers.IN_ROSTER_GROUP(group) + return ("not not (roster_entry and roster_entry.groups[%q])"):format(group), { "roster_entry" }; +end + +function condition_handlers.SUBSCRIBED() + return "(bare_to == bare_from or to_node and rostermanager.is_contact_subscribed(to_node, to_host, bare_from))", + { "rostermanager", "split_to", "bare_to", "bare_from" }; +end + +function condition_handlers.PENDING_SUBSCRIPTION_FROM_SENDER() + return "(bare_to == bare_from or to_node and rostermanager.is_contact_pending_in(to_node, to_host, bare_from))", + { "rostermanager", "split_to", "bare_to", "bare_from" }; +end + +function condition_handlers.PAYLOAD(payload_ns) + return ("stanza:get_child(nil, %q)"):format(payload_ns); +end + +function condition_handlers.INSPECT(path) + if path:find("=") then + local query, match_type, value = path:match("(.-)([~/$]*)=(.*)"); + if not(query:match("#$") or query:match("@[^/]+")) then + error("Stanza path does not return a string (append # for text content or @name for value of named attribute)", 0); + end + local meta_deps = {}; + local quoted_value = ("%q"):format(value); + if match_type:find("$", 1, true) then + match_type = match_type:gsub("%$", ""); + quoted_value = meta(quoted_value, meta_deps); + end + if match_type == "~" then -- Lua pattern match + return ("(stanza:find(%q) or ''):match(%s)"):format(query, quoted_value), meta_deps; + elseif match_type == "/" then -- find literal substring + return ("(stanza:find(%q) or ''):find(%s, 1, true)"):format(query, quoted_value), meta_deps; + elseif match_type == "" then -- exact match + return ("stanza:find(%q) == %s"):format(query, quoted_value), meta_deps; + else + error("Unrecognised comparison '"..match_type.."='", 0); + end + end + return ("stanza:find(%q)"):format(path); +end + +function condition_handlers.FROM_GROUP(group_name) + return ("group_contains(%q, bare_from)"):format(group_name), { "group_contains", "bare_from" }; +end + +function condition_handlers.TO_GROUP(group_name) + return ("group_contains(%q, bare_to)"):format(group_name), { "group_contains", "bare_to" }; +end + +function condition_handlers.CROSSING_GROUPS(group_names) + local code = {}; + for group_name in group_names:gmatch("([^, ][^,]+)") do + group_name = group_name:match("^%s*(.-)%s*$"); -- Trim leading/trailing whitespace + -- Just check that's it is crossing from outside group to inside group + table.insert(code, ("(group_contains(%q, bare_to) and group_contains(%q, bare_from))"):format(group_name, group_name)) + end + return "not "..table.concat(code, " or "), { "group_contains", "bare_to", "bare_from" }; +end + +-- COMPAT w/0.12: Deprecated +function condition_handlers.FROM_ADMIN_OF(host) + return ("is_admin(bare_from, %s)"):format(host ~= "*" and metaq(host) or nil), { "is_admin", "bare_from" }; +end + +-- COMPAT w/0.12: Deprecated +function condition_handlers.TO_ADMIN_OF(host) + return ("is_admin(bare_to, %s)"):format(host ~= "*" and metaq(host) or nil), { "is_admin", "bare_to" }; +end + +-- COMPAT w/0.12: Deprecated +function condition_handlers.FROM_ADMIN() + return ("is_admin(bare_from, current_host)"), { "is_admin", "bare_from", "current_host" }; +end + +-- COMPAT w/0.12: Deprecated +function condition_handlers.TO_ADMIN() + return ("is_admin(bare_to, current_host)"), { "is_admin", "bare_to", "current_host" }; +end + +-- MAY: permission_to_check +function condition_handlers.MAY(permission_to_check) + return ("module:may(%q, event)"):format(permission_to_check); +end + +function condition_handlers.TO_ROLE(role_name) + return ("recipient_role and recipient_role.name == %q"):format(role_name), { "recipient_role" }; +end + +function condition_handlers.FROM_ROLE(role_name) + return ("sender_role and sender_role.name == %q"):format(role_name), { "sender_role" }; +end + +local day_numbers = { sun = 0, mon = 2, tue = 3, wed = 4, thu = 5, fri = 6, sat = 7 }; + +local function current_time_check(op, hour, minute) + hour, minute = tonumber(hour), tonumber(minute); + local adj_op = op == "<" and "<" or ">="; -- Start time inclusive, end time exclusive + if minute == 0 then + return "(current_hour"..adj_op..hour..")"; + else + return "((current_hour"..op..hour..") or (current_hour == "..hour.." and current_minute"..adj_op..minute.."))"; + end +end + +local function resolve_day_number(day_name) + return assert(day_numbers[day_name:sub(1,3):lower()], "Unknown day name: "..day_name); +end + +function condition_handlers.DAY(days) + local conditions = {}; + for day_range in days:gmatch("[^,]+") do + local day_start, day_end = day_range:match("(%a+)%s*%-%s*(%a+)"); + if day_start and day_end then + local day_start_num, day_end_num = resolve_day_number(day_start), resolve_day_number(day_end); + local op = "and"; + if day_end_num < day_start_num then + op = "or"; + end + table.insert(conditions, ("current_day >= %d %s current_day <= %d"):format(day_start_num, op, day_end_num)); + elseif day_range:find("%a") then + local day = resolve_day_number(day_range:match("%a+")); + table.insert(conditions, "current_day == "..day); + else + error("Unable to parse day/day range: "..day_range); + end + end + assert(#conditions>0, "Expected a list of days or day ranges"); + return "("..table.concat(conditions, ") or (")..")", { "time:day" }; +end + +function condition_handlers.TIME(ranges) + local conditions = {}; + for range in ranges:gmatch("([^,]+)") do + local clause = {}; + range = range:lower() + :gsub("(%d+):?(%d*) *am", function (h, m) return tostring(tonumber(h)%12)..":"..(tonumber(m) or "00"); end) + :gsub("(%d+):?(%d*) *pm", function (h, m) return tostring(tonumber(h)%12+12)..":"..(tonumber(m) or "00"); end); + local start_hour, start_minute = range:match("(%d+):(%d+) *%-"); + local end_hour, end_minute = range:match("%- *(%d+):(%d+)"); + local op = tonumber(start_hour) > tonumber(end_hour) and " or " or " and "; + if start_hour and end_hour then + table.insert(clause, current_time_check(">", start_hour, start_minute)); + table.insert(clause, current_time_check("<", end_hour, end_minute)); + end + if #clause == 0 then + error("Unable to parse time range: "..range); + end + table.insert(conditions, "("..table.concat(clause, " "..op.." ")..")"); + end + return table.concat(conditions, " or "), { "time:hour,min" }; +end + +function condition_handlers.LIMIT(spec) + local name, param = spec:match("^(%w+) on (.+)$"); + local meta_deps = {}; + + if not name then + name = spec:match("^%w+$"); + if not name then + error("Unable to parse LIMIT specification"); + end + else + param = meta(("%q"):format(param), meta_deps); + end + + if not param then + return ("not global_throttle_%s:poll(1)"):format(name), { "globalthrottle:"..name, unpack(meta_deps) }; + end + return ("not multi_throttle_%s:poll_on(%s, 1)"):format(name, param), { "multithrottle:"..name, unpack(meta_deps) }; +end + +function condition_handlers.ORIGIN_MARKED(name_and_time) + local name, time = name_and_time:match("^%s*([%w_]+)%s+%(([^)]+)s%)%s*$"); + if not name then + name = name_and_time:match("^%s*([%w_]+)%s*$"); + end + if not name then + error("Error parsing mark name, see documentation for usage examples"); + end + if time then + return ("(current_timestamp - (session.firewall_marked_%s or 0)) < %d"):format(idsafe(name), tonumber(time)), { "timestamp" }; + end + return ("not not session.firewall_marked_"..idsafe(name)); +end + +function condition_handlers.USER_MARKED(name_and_time) + local name, time = name_and_time:match("^%s*([%w_]+)%s+%(([^)]+)s%)%s*$"); + if not name then + name = name_and_time:match("^%s*([%w_]+)%s*$"); + end + if not name then + error("Error parsing mark name, see documentation for usage examples"); + end + if time then + return ([[( + current_timestamp - (session.firewall_marks and session.firewall_marks.%s or 0) + ) < %d]]):format(idsafe(name), tonumber(time)), { "timestamp" }; + end + return ("not not (session.firewall_marks and session.firewall_marks."..idsafe(name)..")"); +end + +function condition_handlers.SENT_DIRECTED_PRESENCE_TO_SENDER() + return "not not (session.directed and session.directed[from])", { "from" }; +end + +-- TO FULL JID? +function condition_handlers.TO_FULL_JID() + return "not not full_sessions[to]", { "to", "full_sessions" }; +end + +-- CHECK LIST: spammers contains $<@from> +function condition_handlers.CHECK_LIST(list_condition) + local list_name, expr = list_condition:match("(%S+) contains (.+)$"); + if not (list_name and expr) then + error("Error parsing list check, syntax: LISTNAME contains EXPRESSION"); + end + local meta_deps = {}; + expr = meta(("%q"):format(expr), meta_deps); + return ("list_%s:contains(%s) == true"):format(list_name, expr), { "list:"..list_name, unpack(meta_deps) }; +end + +-- SCAN: body for word in badwords +function condition_handlers.SCAN(scan_expression) + local search_name, pattern_name, list_name = scan_expression:match("(%S+) for (%S+) in (%S+)$"); + if not (search_name) then + error("Error parsing SCAN expression, syntax: SEARCH for PATTERN in LIST"); + end + return ("scan_list(list_%s, %s)"):format( + list_name, + "tokens_"..search_name.."_"..pattern_name + ), { + "scan_list", + "tokens:"..search_name.."-"..pattern_name, "list:"..list_name + }; +end + +-- COUNT: lines in body < 10 +local valid_comp_ops = { [">"] = ">", ["<"] = "<", ["="] = "==", ["=="] = "==", ["<="] = "<=", [">="] = ">=" }; +function condition_handlers.COUNT(count_expression) + local pattern_name, search_name, comparator_expression = count_expression:match("(%S+) in (%S+) (.+)$"); + if not (pattern_name) then + error("Error parsing COUNT expression, syntax: PATTERN in SEARCH COMPARATOR"); + end + local value; + comparator_expression = comparator_expression:gsub("%d+", function (value_string) + value = tonumber(value_string); + return ""; + end); + if not value then + error("Error parsing COUNT expression, expected value"); + end + local comp_op = comparator_expression:gsub("%s+", ""); + assert(valid_comp_ops[comp_op], "Error parsing COUNT expression, unknown comparison operator: "..comp_op); + return ("it_count(search_%s:gmatch(pattern_%s)) %s %d"):format( + search_name, pattern_name, comp_op, value + ), { + "it_count", + "search:"..search_name, "pattern:"..pattern_name + }; +end + +-- FROM COUNTRY: SE +-- FROM COUNTRY: code=SE +-- FROM COUNTRY: SWE +-- FROM COUNTRY: code3=SWE +-- FROM COUNTRY: continent=EU +-- FROM COUNTRY? --> NOT FROM COUNTRY: -- (for unknown/invalid) +-- TODO list support? +function condition_handlers.FROM_COUNTRY(geoip_spec) + local condition = "=="; + if not geoip_spec then + geoip_spec = "--"; + condition = "~="; + end + local field, country = geoip_spec:match("(%w+)=(%w+)"); + if not field then + if #geoip_spec == 3 then + field, country = "code3", geoip_spec; + elseif #geoip_spec == 2 then + field, country = "code", geoip_spec; + else + error("Unknown country code type"); + end + end + return ("get_geoip(session.ip, %q) %s %q"):format(field:lower(), condition, country:upper()), { "geoip_country" }; +end + +return condition_handlers; diff --git a/prosody-modules/mod_firewall/definitions.lib.lua b/prosody-modules/mod_firewall/definitions.lib.lua new file mode 100644 index 00000000..a35ba804 --- /dev/null +++ b/prosody-modules/mod_firewall/definitions.lib.lua @@ -0,0 +1,335 @@ + +-- Name arguments are unused here +-- luacheck: ignore 212 + +local definition_handlers = {}; + +local http = require "net.http"; +local timer = require "util.timer"; +local set = require"util.set"; +local new_throttle = require "util.throttle".create; +local hashes = require "util.hashes"; +local jid = require "util.jid"; +local lfs = require "lfs"; + +local multirate_cache_size = module:get_option_number("firewall_multirate_cache_limit", 1000); + +function definition_handlers.ZONE(zone_name, zone_members) + local zone_member_list = {}; + for member in zone_members:gmatch("[^, ]+") do + zone_member_list[#zone_member_list+1] = member; + end + return set.new(zone_member_list)._items; +end + +-- Helper function used by RATE handler +local function evict_only_unthrottled(name, throttle) + throttle:update(); + -- Check whether the throttle is at max balance (i.e. totally safe to forget about it) + if throttle.balance < throttle.max then + -- Not safe to forget + return false; + end +end + +function definition_handlers.RATE(name, line) + local rate = assert(tonumber(line:match("([%d.]+)")), "Unable to parse rate"); + local burst = tonumber(line:match("%(%s*burst%s+([%d.]+)%s*%)")) or 1; + local max_throttles = tonumber(line:match("%(%s*entries%s+([%d]+)%s*%)")) or multirate_cache_size; + local deny_when_full = not line:match("%(allow overflow%)"); + return { + single = function () + return new_throttle(rate*burst, burst); + end; + + multi = function () + local cache = require "util.cache".new(max_throttles, deny_when_full and evict_only_unthrottled or nil); + return { + poll_on = function (_, key, amount) + assert(key, "no key"); + local throttle = cache:get(key); + if not throttle then + throttle = new_throttle(rate*burst, burst); + if not cache:set(key, throttle) then + module:log("warn", "Multirate '%s' has hit its maximum number of active throttles (%d), denying new events", name, max_throttles); + return false; + end + end + return throttle:poll(amount); + end; + } + end; + }; +end + +local list_backends = { + -- %LIST name: memory (limit: number) + memory = { + init = function (self, type, opts) + if opts.limit then + local have_cache_lib, cache_lib = pcall(require, "util.cache"); + if not have_cache_lib then + error("In-memory lists with a size limit require Prosody 0.10"); + end + self.cache = cache_lib.new((assert(tonumber(opts.limit), "Invalid list limit"))); + if not self.cache.table then + error("In-memory lists with a size limit require a newer version of Prosody 0.10"); + end + self.items = self.cache:table(); + else + self.items = {}; + end + end; + add = function (self, item) + self.items[item] = true; + end; + remove = function (self, item) + self.items[item] = nil; + end; + contains = function (self, item) + return self.items[item] == true; + end; + }; + + -- %LIST name: http://example.com/ (ttl: number, pattern: pat, hash: sha1) + http = { + init = function (self, url, opts) + local poll_interval = assert(tonumber(opts.ttl or "3600"), "invalid ttl for <"..url.."> (expected number of seconds)"); + local pattern = opts.pattern or "([^\r\n]+)\r?\n"; + assert(pcall(string.match, "", pattern), "invalid pattern for <"..url..">"); + if opts.hash then + assert(opts.hash:match("^%w+$") and type(hashes[opts.hash]) == "function", "invalid hash function: "..opts.hash); + self.hash_function = hashes[opts.hash]; + end + local etag; + local failure_count = 0; + local retry_intervals = { 60, 120, 300 }; + -- By default only check the certificate if net.http supports SNI + local sni_supported = http.feature and http.features.sni; + local insecure = false; + if opts.checkcert == "never" then + insecure = true; + elseif (opts.checkcert == nil or opts.checkcert == "when-sni") and not sni_supported then + insecure = false; + end + local function update_list() + http.request(url, { + insecure = insecure; + headers = { + ["If-None-Match"] = etag; + }; + }, function (body, code, response) + local next_poll = poll_interval; + if code == 200 and body then + etag = response.headers.etag; + local items = {}; + for entry in body:gmatch(pattern) do + items[entry] = true; + end + self.items = items; + module:log("debug", "Fetched updated list from <%s>", url); + elseif code == 304 then + module:log("debug", "List at <%s> is unchanged", url); + elseif code == 0 or (code >= 400 and code <=599) then + module:log("warn", "Failed to fetch list from <%s>: %d %s", url, code, tostring(body)); + failure_count = failure_count + 1; + next_poll = retry_intervals[failure_count] or retry_intervals[#retry_intervals]; + end + if next_poll > 0 then + timer.add_task(next_poll+math.random(0, 60), update_list); + end + end); + end + update_list(); + end; + add = function () + end; + remove = function () + end; + contains = function (self, item) + if self.hash_function then + item = self.hash_function(item); + end + return self.items and self.items[item] == true; + end; + }; + + -- %LIST: file:/path/to/file + file = { + init = function (self, file_spec, opts) + local n, items = 0, {}; + self.items = items; + local filename = file_spec:gsub("^file:", ""); + if opts.missing == "ignore" and not lfs.attributes(filename, "mode") then + module:log("debug", "Ignoring missing list file: %s", filename); + return; + end + local file, err = io.open(filename); + if not file then + module:log("warn", "Failed to open list from %s: %s", filename, err); + return; + else + for line in file:lines() do + if not items[line] then + n = n + 1; + items[line] = true; + end + end + end + module:log("debug", "Loaded %d items from %s", n, filename); + end; + add = function (self, item) + self.items[item] = true; + end; + remove = function (self, item) + self.items[item] = nil; + end; + contains = function (self, item) + return self.items and self.items[item] == true; + end; + }; + + -- %LIST: pubsub:pubsub.example.com/node + -- TODO or the actual URI scheme? Bit overkill maybe? + -- TODO Publish items back to the service? + -- Step 1: Receiving pubsub events and storing them in the list + -- We'll start by using only the item id. + -- TODO Invent some custom schema for this? Needed for just a set of strings? + pubsubitemid = { + init = function(self, pubsub_spec, opts) + local service_addr, node = pubsub_spec:match("^pubsubitemid:([^/]*)/(.*)"); + if not service_addr then + module:log("warn", "Invalid list specification (expected 'pubsubitemid:/', got: '%s')", pubsub_spec); + return; + end + module:depends("pubsub_subscription"); + module:add_item("pubsub-subscription", { + service = service_addr; + node = node; + on_subscribed = function () + self.items = {}; + end; + on_item = function (event) + self:add(event.item.attr.id); + end; + on_retract = function (event) + self:remove(event.item.attr.id); + end; + on_purge = function () + self.items = {}; + end; + on_unsubscribed = function () + self.items = nil; + end; + on_delete= function () + self.items = nil; + end; + }); + -- TODO Initial fetch? Or should mod_pubsub_subscription do this? + end; + add = function (self, item) + if self.items then + self.items[item] = true; + end + end; + remove = function (self, item) + if self.items then + self.items[item] = nil; + end + end; + contains = function (self, item) + return self.items and self.items[item] == true; + end; + }; +}; +list_backends.https = list_backends.http; + +local normalize_functions = { + upper = string.upper, lower = string.lower; + md5 = hashes.md5, sha1 = hashes.sha1, sha256 = hashes.sha256; + prep = jid.prep, bare = jid.bare; +}; + +local function wrap_list_method(list_method, filter) + return function (self, item) + return list_method(self, filter(item)); + end +end + +local function create_list(list_backend, list_def, opts) + if not list_backends[list_backend] then + error("Unknown list type '"..list_backend.."'", 0); + end + local list = setmetatable({}, { __index = list_backends[list_backend] }); + if list.init then + list:init(list_def, opts); + end + if opts.filter then + local filters = {}; + for func_name in opts.filter:gmatch("[%w_]+") do + if func_name == "log" then + table.insert(filters, function (s) + --print("&&&&&", s); + module:log("debug", "Checking list <%s> for: %s", list_def, s); + return s; + end); + else + assert(normalize_functions[func_name], "Unknown list filter: "..func_name); + table.insert(filters, normalize_functions[func_name]); + end + end + + local filter; + local n = #filters; + if n == 1 then + filter = filters[1]; + else + function filter(s) + for i = 1, n do + s = filters[i](s or ""); + end + return s; + end + end + + list.add = wrap_list_method(list.add, filter); + list.remove = wrap_list_method(list.remove, filter); + list.contains = wrap_list_method(list.contains, filter); + end + return list; +end + +--[[ +%LIST spammers: memory (source: /etc/spammers.txt) + +%LIST spammers: memory (source: /etc/spammers.txt) + + +%LIST spammers: http://example.com/blacklist.txt +]] + +function definition_handlers.LIST(list_name, list_definition) + local list_backend = list_definition:match("^%w+"); + local opts = {}; + local opt_string = list_definition:match("^%S+%s+%((.+)%)"); + if opt_string then + for opt_k, opt_v in opt_string:gmatch("(%w+): ?([^,]+)") do + opts[opt_k] = opt_v; + end + end + return create_list(list_backend, list_definition:match("^%S+"), opts); +end + +function definition_handlers.PATTERN(name, pattern) + local ok, err = pcall(string.match, "", pattern); + if not ok then + error("Invalid pattern '"..name.."': "..err); + end + return pattern; +end + +function definition_handlers.SEARCH(name, pattern) + return pattern; +end + +return definition_handlers; diff --git a/prosody-modules/mod_firewall/marks.lib.lua b/prosody-modules/mod_firewall/marks.lib.lua new file mode 100644 index 00000000..3c9bbb06 --- /dev/null +++ b/prosody-modules/mod_firewall/marks.lib.lua @@ -0,0 +1,35 @@ +local mark_storage = module:open_store("firewall_marks"); +local mark_map_storage = module:open_store("firewall_marks", "map"); + +local user_sessions = prosody.hosts[module.host].sessions; + +module:hook("firewall/marked/user", function (event) + local user = user_sessions[event.username]; + local marks = user and user.firewall_marks; + if user and not marks then + -- Load marks from storage to cache on the user object + marks = mark_storage:get(event.username) or {}; + user.firewall_marks = marks; --luacheck: ignore 122 + end + if marks then + marks[event.mark] = event.timestamp; + end + local ok, err = mark_map_storage:set(event.username, event.mark, event.timestamp); + if not ok then + module:log("error", "Failed to mark user %q with %q: %s", event.username, event.mark, err); + end + return true; +end, -1); + +module:hook("firewall/unmarked/user", function (event) + local user = user_sessions[event.username]; + local marks = user and user.firewall_marks; + if marks then + marks[event.mark] = nil; + end + local ok, err = mark_map_storage:set(event.username, event.mark, nil); + if not ok then + module:log("error", "Failed to unmark user %q with %q: %s", event.username, event.mark, err); + end + return true; +end, -1); diff --git a/prosody-modules/mod_firewall/mod_firewall.lua b/prosody-modules/mod_firewall/mod_firewall.lua new file mode 100644 index 00000000..62cfdbd7 --- /dev/null +++ b/prosody-modules/mod_firewall/mod_firewall.lua @@ -0,0 +1,863 @@ + +local lfs = require "lfs"; +local resolve_relative_path = require "core.configmanager".resolve_relative_path; +local envload = require "util.envload".envload; +local logger = require "util.logger".init; +local it = require "util.iterators"; +local set = require "util.set"; + +local have_features, features = pcall(require, "core.features"); +features = have_features and features.available or set.new(); + +-- [definition_type] = definition_factory(param) +local definitions = module:shared("definitions"); + +-- When a definition instance has been instantiated, it lives here +-- [definition_type][definition_name] = definition_object +local active_definitions = { + ZONE = { + -- Default zone that includes all local hosts + ["$local"] = setmetatable({}, { __index = prosody.hosts }); + }; +}; + +local default_chains = { + preroute = { + type = "event"; + priority = 0.1; + "pre-message/bare", "pre-message/full", "pre-message/host"; + "pre-presence/bare", "pre-presence/full", "pre-presence/host"; + "pre-iq/bare", "pre-iq/full", "pre-iq/host"; + }; + deliver = { + type = "event"; + priority = 0.1; + "message/bare", "message/full", "message/host"; + "presence/bare", "presence/full", "presence/host"; + "iq/bare", "iq/full", "iq/host"; + }; + deliver_remote = { + type = "event"; "route/remote"; + priority = 0.1; + }; +}; + +local extra_chains = module:get_option("firewall_extra_chains", {}); + +local chains = {}; +for k,v in pairs(default_chains) do + chains[k] = v; +end +for k,v in pairs(extra_chains) do + chains[k] = v; +end + +-- Returns the input if it is safe to be used as a variable name, otherwise nil +function idsafe(name) + return name:match("^%a[%w_]*$"); +end + +local meta_funcs = { + bare = function (code) + return "jid_bare("..code..")", {"jid_bare"}; + end; + node = function (code) + return "(jid_split("..code.."))", {"jid_split"}; + end; + host = function (code) + return "(select(2, jid_split("..code..")))", {"jid_split"}; + end; + resource = function (code) + return "(select(3, jid_split("..code..")))", {"jid_split"}; + end; +}; + +-- Run quoted (%q) strings through this to allow them to contain code. e.g.: LOG=Received: $(stanza:top_tag()) +function meta(s, deps, extra) + return (s:gsub("$(%b())", function (expr) + expr = expr:gsub("\\(.)", "%1"); + return [["..tostring(]]..expr..[[).."]]; + end) + :gsub("$(%b<>)", function (expr) + expr = expr:sub(2,-2); + local default = ""; + expr = expr:gsub("||(%b\"\")$", function (default_string) + default = stripslashes(default_string:sub(2,-2)); + return ""; + end); + local func_chain = expr:match("|[%w|]+$"); + if func_chain then + expr = expr:sub(1, -1-#func_chain); + end + local code; + if expr:match("^@") then + -- Skip stanza:find() for simple attribute lookup + local attr_name = expr:sub(2); + if deps and (attr_name == "to" or attr_name == "from" or attr_name == "type") then + -- These attributes may be cached in locals + code = attr_name; + table.insert(deps, attr_name); + else + code = "stanza.attr["..("%q"):format(attr_name).."]"; + end + elseif expr:match("^%w+#$") then + code = ("stanza:get_child_text(%q)"):format(expr:sub(1, -2)); + else + code = ("stanza:find(%q)"):format(expr); + end + if func_chain then + for func_name in func_chain:gmatch("|(%w+)") do + -- to/from are already available in local variables, use those if possible + if (code == "to" or code == "from") and func_name == "bare" then + code = "bare_"..code; + table.insert(deps, code); + elseif (code == "to" or code == "from") and (func_name == "node" or func_name == "host" or func_name == "resource") then + table.insert(deps, "split_"..code); + code = code.."_"..func_name; + else + assert(meta_funcs[func_name], "unknown function: "..func_name); + local new_code, new_deps = meta_funcs[func_name](code); + code = new_code; + if new_deps and #new_deps > 0 then + assert(deps, "function not supported here: "..func_name); + for _, dep in ipairs(new_deps) do + table.insert(deps, dep); + end + end + end + end + end + return "\"..tostring("..code.." or "..("%q"):format(default)..")..\""; + end) + :gsub("$$(%a+)", extra or {}) + :gsub([[^""%.%.]], "") + :gsub([[%.%.""$]], "")); +end + +function metaq(s, ...) + return meta(("%q"):format(s), ...); +end + +local escape_chars = { + a = "\a", b = "\b", f = "\f", n = "\n", r = "\r", t = "\t", + v = "\v", ["\\"] = "\\", ["\""] = "\"", ["\'"] = "\'" +}; +function stripslashes(s) + return (s:gsub("\\(.)", escape_chars)); +end + +-- Dependency locations: +-- +-- +-- function handler() +-- +-- if then +-- +-- end +-- end + +local available_deps = { + st = { global_code = [[local st = require "util.stanza";]]}; + it = { global_code = [[local it = require "util.iterators";]]}; + it_count = { global_code = [[local it_count = it.count;]], depends = { "it" } }; + current_host = { global_code = [[local current_host = module.host;]] }; + jid_split = { + global_code = [[local jid_split = require "util.jid".split;]]; + }; + jid_bare = { + global_code = [[local jid_bare = require "util.jid".bare;]]; + }; + to = { local_code = [[local to = stanza.attr.to or jid_bare(session.full_jid);]]; depends = { "jid_bare" } }; + from = { local_code = [[local from = stanza.attr.from;]] }; + type = { local_code = [[local type = stanza.attr.type;]] }; + name = { local_code = [[local name = stanza.name;]] }; + split_to = { -- The stanza's split to address + depends = { "jid_split", "to" }; + local_code = [[local to_node, to_host, to_resource = jid_split(to);]]; + }; + split_from = { -- The stanza's split from address + depends = { "jid_split", "from" }; + local_code = [[local from_node, from_host, from_resource = jid_split(from);]]; + }; + bare_to = { depends = { "jid_bare", "to" }, local_code = "local bare_to = jid_bare(to)"}; + bare_from = { depends = { "jid_bare", "from" }, local_code = "local bare_from = jid_bare(from)"}; + group_contains = { + global_code = [[local group_contains = module:depends("groups").group_contains]]; + }; + is_admin = require"core.usermanager".is_admin and { global_code = [[local is_admin = require "core.usermanager".is_admin;]]} or nil; + get_jid_role = require "core.usermanager".get_jid_role and { global_code = [[local get_jid_role = require "core.usermanager".get_jid_role;]] } or nil; + core_post_stanza = { global_code = [[local core_post_stanza = prosody.core_post_stanza;]] }; + zone = { global_code = function (zone) + local var = zone; + if var == "$local" then + var = "_local"; -- See #1090 + else + assert(idsafe(var), "Invalid zone name: "..zone); + end + return ("local zone_%s = zones[%q] or {};"):format(var, zone); + end }; + date_time = { global_code = [[local os_date = os.date]]; local_code = [[local current_date_time = os_date("*t");]] }; + time = { local_code = function (what) + local defs = {}; + for field in what:gmatch("%a+") do + table.insert(defs, ("local current_%s = current_date_time.%s;"):format(field, field)); + end + return table.concat(defs, " "); + end, depends = { "date_time" }; }; + timestamp = { global_code = [[local get_time = require "socket".gettime;]]; local_code = [[local current_timestamp = get_time();]]; }; + globalthrottle = { + global_code = function (throttle) + assert(idsafe(throttle), "Invalid rate limit name: "..throttle); + assert(active_definitions.RATE[throttle], "Unknown rate limit: "..throttle); + return ("local global_throttle_%s = rates.%s:single();"):format(throttle, throttle); + end; + }; + multithrottle = { + global_code = function (throttle) + assert(pcall(require, "util.cache"), "Using LIMIT with 'on' requires Prosody 0.10 or higher"); + assert(idsafe(throttle), "Invalid rate limit name: "..throttle); + assert(active_definitions.RATE[throttle], "Unknown rate limit: "..throttle); + return ("local multi_throttle_%s = rates.%s:multi();"):format(throttle, throttle); + end; + }; + full_sessions = { + global_code = [[local full_sessions = prosody.full_sessions;]]; + }; + rostermanager = { + global_code = [[local rostermanager = require "core.rostermanager";]]; + }; + roster_entry = { + local_code = [[local roster_entry = (to_node and rostermanager.load_roster(to_node, to_host) or {})[bare_from];]]; + depends = { "rostermanager", "split_to", "bare_from" }; + }; + list = { global_code = function (list) + assert(idsafe(list), "Invalid list name: "..list); + assert(active_definitions.LIST[list], "Unknown list: "..list); + return ("local list_%s = lists[%q];"):format(list, list); + end + }; + search = { + local_code = function (search_name) + local search_path = assert(active_definitions.SEARCH[search_name], "Undefined search path: "..search_name); + return ("local search_%s = tostring(stanza:find(%q) or \"\")"):format(search_name, search_path); + end; + }; + pattern = { + local_code = function (pattern_name) + local pattern = assert(active_definitions.PATTERN[pattern_name], "Undefined pattern: "..pattern_name); + return ("local pattern_%s = %q"):format(pattern_name, pattern); + end; + }; + tokens = { + local_code = function (search_and_pattern) + local search_name, pattern_name = search_and_pattern:match("^([^%-]+)-(.+)$"); + local code = ([[local tokens_%s_%s = {}; + if search_%s then + for s in search_%s:gmatch(pattern_%s) do + tokens_%s_%s[s] = true; + end + end + ]]):format(search_name, pattern_name, search_name, search_name, pattern_name, search_name, pattern_name); + return code, { "search:"..search_name, "pattern:"..pattern_name }; + end; + }; + sender_role = { + local_code = [[local sender_role = get_jid_role(bare_from, current_host)]]; + depends = { "bare_from", "current_host", "get_jid_role" }; + }; + recipient_role = { + local_code = [[local recipient_role = get_jid_role(bare_to, current_host)]]; + depends = { "bare_to", "current_host", "get_jid_role" }; + }; + scan_list = { + global_code = [[local function scan_list(list, items) for item in pairs(items) do if list:contains(item) then return true; end end end]]; + }; + iplib = { + global_code = [[local iplib = require "util.ip";]]; + }; + geoip_country = { + global_code = [[ +local geoip_country = require "geoip.country"; +local geov4 = geoip_country.open(module:get_option_string("geoip_ipv4_country", "/usr/share/GeoIP/GeoIP.dat")); +local geov6 = geoip_country.open(module:get_option_string("geoip_ipv6_country", "/usr/share/GeoIP/GeoIPv6.dat")); +local function get_geoip(ips, what) + if not ips then + return "--"; + end + local ip = iplib.new_ip(ips); + if not ip then + return "--"; + end + if ip.proto == "IPv6" and geov6 then + local geoinfo = geoinfo:query_by_addr6(ip.addr); + if geoinfo then + return geoinfo[what or "code"]; + end + elseif ip.proto == "IPv4" and geov4 then + local geoinfo = geoinfo:query_by_addr(ip.addr); + if geoinfo then + return geoinfo[what or "code"]; + end + end + return "--"; +end + ]]; + depends = { + "iplib" + } + }; + new_short_id = { + global_code = [[local new_short_id = require "util.id".short;]]; + }; + new_medium_id = { + global_code = [[local new_medium_id = require "util.id".medium;]]; + }; + new_long_id = { + global_code = [[local new_long_id = require "util.id".long;]]; + }; + + trace = { + global_code = [[local trace_init = module:require("trace").init;]]; + }; +}; + +local function include_dep(dependency, code) + local dep, dep_param = dependency:match("^([^:]+):?(.*)$"); + local dep_info = available_deps[dep]; + if not dep_info then + module:log("error", "Dependency not found: %s", dep); + return; + end + if code.included_deps[dependency] ~= nil then + if code.included_deps[dependency] ~= true then + module:log("error", "Circular dependency on %s", dep); + end + return; + end + code.included_deps[dependency] = false; -- Pending flag (used to detect circular references) + for _, dep_dep in ipairs(dep_info.depends or {}) do + include_dep(dep_dep, code); + end + if dep_info.global_code then + if dep_param ~= "" then + local global_code, deps = dep_info.global_code(dep_param); + if deps then + for _, dep_dep in ipairs(deps) do + include_dep(dep_dep, code); + end + end + table.insert(code.global_header, global_code); + else + table.insert(code.global_header, dep_info.global_code); + end + end + if dep_info.local_code then + if dep_param ~= "" then + local local_code, deps = dep_info.local_code(dep_param); + if deps then + for _, dep_dep in ipairs(deps) do + include_dep(dep_dep, code); + end + end + table.insert(code, "\n\t\t-- "..dep.."\n\t\t"..local_code.."\n"); + else + table.insert(code, "\n\t\t-- "..dep.."\n\t\t"..dep_info.local_code.."\n"); + end + end + code.included_deps[dependency] = true; +end + +local definition_handlers = module:require("definitions"); +local condition_handlers = module:require("conditions"); +local action_handlers = module:require("actions"); + +if module:get_option_boolean("firewall_experimental_user_marks", true) then + module:require"marks"; +end + +local function new_rule(ruleset, chain, line_no) + assert(chain, "no chain specified"); + local rule = { conditions = {}, actions = {}, deps = {}, line_no = line_no }; + table.insert(ruleset[chain], rule); + return rule; +end + +local function parse_firewall_rules(filename) + local line_no = 0; + + local function errmsg(err) + return "Error compiling "..filename.." on line "..line_no..": "..err; + end + + local metadata = { debug = {} }; + local ruleset = { + deliver = {}; + }; + + local chain = "deliver"; -- Default chain + local rule; + + local file, err = io.open(filename); + if not file then return nil, err; end + + local state; -- nil -> "rules" -> "actions" -> nil -> ... + + local line_hold; + for line in file:lines() do + line = line:match("^%s*(.-)%s*$"); + if line_hold and line:sub(-1,-1) ~= "\\" then + line = line_hold..line; + line_hold = nil; + elseif line:sub(-1,-1) == "\\" then + line_hold = (line_hold or "")..line:sub(1,-2); + end + line_no = line_no + 1; + + if line_hold or line:find("^[#;]") then -- luacheck: ignore 542 + -- No action; comment or partial line + elseif line == "" then + if state == "rules" then + return nil, ("Expected an action on line %d for preceding criteria") + :format(line_no); + end + state = nil; + elseif not(state) and line:sub(1, 2) == "::" then + chain = line:gsub("^::%s*", ""); + local chain_info = chains[chain]; + if not chain_info then + if chain:match("^user/") then + chains[chain] = { type = "event", priority = 1, pass_return = false }; + else + return nil, errmsg("Unknown chain: "..chain); + end + elseif chain_info.type ~= "event" then + return nil, errmsg("Only event chains supported at the moment"); + end + ruleset[chain] = ruleset[chain] or {}; + elseif not(state) and line:sub(1, 2) == "@@" then + local k, v = line:match("^@@%s*([^%s=]+)%s*=%s*(.+)$"); + if not k then + return nil, errmsg("Unable to parse metadata assignment (expected '@@ key = value')"); + end + metadata[k] = v; + elseif not(state) and line:sub(1,1) == "%" then -- Definition (zone, limit, etc.) + local what, name = line:match("^%%%s*([%w_]+) +([^ :]+)"); + if not definition_handlers[what] then + return nil, errmsg("Definition of unknown object: "..what); + elseif not name or not idsafe(name) then + return nil, errmsg("Invalid "..what.." name"); + end + + local val = line:match(": ?(.*)$"); + if not val and line:find(":<") then -- Read from file + local fn = line:match(":< ?(.-)%s*$"); + if not fn then + return nil, errmsg("Unable to parse filename"); + end + local f, err = io.open(fn); + if not f then return nil, errmsg(err); end + val = f:read("*a"):gsub("\r?\n", " "):gsub("%s+$", ""); + end + if not val then + return nil, errmsg("No value given for definition"); + end + val = stripslashes(val); + local ok, ret = pcall(definition_handlers[what], name, val); + if not ok then + return nil, errmsg(ret); + end + + if not active_definitions[what] then + active_definitions[what] = {}; + end + active_definitions[what][name] = ret; + elseif line:find("^[%w_ ]+[%.=]") then + -- Action + if state == nil then + -- This is a standalone action with no conditions + rule = new_rule(ruleset, chain); + end + state = "actions"; + -- Action handlers? + local action = line:match("^[%w_ ]+"):upper():gsub(" ", "_"); + if not action_handlers[action] then + return nil, ("Unknown action on line %d: %s"):format(line_no, action or ""); + end + table.insert(rule.actions, "-- "..line) + local ok, action_string, action_deps = pcall(action_handlers[action], line:match("=(.+)$")); + if not ok then + return nil, errmsg(action_string); + end + table.insert(rule.actions, action_string); + for _, dep in ipairs(action_deps or {}) do + table.insert(rule.deps, dep); + end + elseif state == "actions" then -- state is actions but action pattern did not match + state = nil; -- Awaiting next rule, etc. + table.insert(ruleset[chain], rule); -- FIXME: Is this a bug? Rule should have already been inserted by new_rule()? + rule = nil; + else + -- Condition + if not state then -- Starting a new rule block? + state = "rules"; + rule = new_rule(ruleset, chain, line_no); + end + -- Check standard modifiers for the condition (e.g. NOT) + local negated; + local condition = line:match("^[^:=%.?]*"); + if condition:find("%f[%w]NOT%f[^%w]") then + local s, e = condition:match("%f[%w]()NOT()%f[^%w]"); + condition = (condition:sub(1,s-1)..condition:sub(e+1, -1)):match("^%s*(.-)%s*$"); + negated = true; + end + condition = condition:gsub(" ", "_"); + if not condition_handlers[condition] then + return nil, ("Unknown condition on line %d: %s"):format(line_no, (condition:gsub("_", " "))); + end + -- Get the code for this condition + local ok, condition_code, condition_deps = pcall(condition_handlers[condition], line:match(":%s?(.+)$")); + if not ok then + return nil, errmsg(condition_code); + end + if negated then condition_code = "not("..condition_code..")"; end + table.insert(rule.conditions, condition_code); + for _, dep in ipairs(condition_deps or {}) do + table.insert(rule.deps, dep); + end + end + end + return ruleset, metadata; +end + +local function process_firewall_rules(ruleset, metadata) + -- Compile ruleset and return complete code + + local chain_handlers = {}; + + -- Loop through the chains in the parsed ruleset (e.g. incoming, outgoing) + for chain_name, rules in pairs(ruleset) do + local code = { included_deps = {}, global_header = {} }; + local condition_uses = {}; + -- This inner loop assumes chain is an event-based, not a filter-based + -- chain (filter-based will be added later) + for _, rule in ipairs(rules) do + for _, condition in ipairs(rule.conditions) do + if condition:find("^not%(.+%)$") then + condition = condition:match("^not%((.+)%)$"); + end + condition_uses[condition] = (condition_uses[condition] or 0) + 1; + end + end + + if metadata.trace then + include_dep("trace", code); + table.insert(code, ("local trace = trace_init(%q, %q);"):format(metadata.filename, chain_name)) + end + + local condition_cache, n_conditions = {}, 0; + for rule_n, rule in ipairs(rules) do + for _, dep in ipairs(rule.deps) do + include_dep(dep, code); + end + table.insert(code, "\n\t\t"); + local rule_code; + if #rule.conditions > 0 then + for i, condition in ipairs(rule.conditions) do + local negated = condition:match("^not%(.+%)$"); + if negated then + condition = condition:match("^not%((.+)%)$"); + end + if condition_uses[condition] > 1 then + local name = condition_cache[condition]; + if not name then + n_conditions = n_conditions + 1; + name = "condition"..n_conditions; + condition_cache[condition] = name; + table.insert(code, "local "..name.." = "..condition..";\n\t\t"); + end + rule.conditions[i] = (negated and "not(" or "")..name..(negated and ")" or ""); + else + rule.conditions[i] = (negated and "not(" or "(")..condition..")"; + end + + if metadata.trace then + -- Wrap each condition in a tracer + rule.conditions[i] = ("trace(%d, %d, %s)"):format(rule_n, i, rule.conditions[i]); + end + end + + if metadata.trace then + -- Trace overall action + table.insert(rule.actions, 1, ("trace(%d, nil, true)"):format(rule_n)); + table.insert(rule.actions, ("else trace(%d, nil, false)"):format(rule_n)); + end + + rule_code = "if "..table.concat(rule.conditions, " and ").." then\n\t\t\t" + ..table.concat(rule.actions, "\n\t\t\t") + .."\n\t\tend\n"; + else + rule_code = table.concat(rule.actions, "\n\t\t"); + end + table.insert(code, rule_code); + end + + for name in pairs(definition_handlers) do + table.insert(code.global_header, 1, "local "..name:lower().."s = definitions."..name..";"); + end + + local code_string = "return function (definitions, fire_event, log, module, pass_return)\n\t" + ..table.concat(code.global_header, "\n\t") + .."\n\tlocal db = require 'util.debug';\n\n\t" + .."return function (event)\n\t\t" + .."local stanza, session = event.stanza, event.origin;\n" + ..table.concat(code, "") + .."\n\tend;\nend"; + + chain_handlers[chain_name] = code_string; + end + + return chain_handlers; +end + +local function compile_firewall_rules(filename) + local ruleset, metadata = parse_firewall_rules(filename); + if not ruleset then return nil, metadata; end + local chain_handlers = process_firewall_rules(ruleset, metadata); + return chain_handlers; +end + +-- Compile handler code into a factory that produces a valid event handler. Factory accepts +-- a value to be returned on PASS +local function compile_handler(code_string, filename) + -- Prepare event handler function + local chunk, err = envload(code_string, "="..filename, _G); + if not chunk then + return nil, "Error compiling (probably a compiler bug, please report): "..err; + end + local function fire_event(name, data) + return module:fire_event(name, data); + end + local init_ok, initialized_chunk = pcall(chunk); + if not init_ok then + return nil, "Error initializing compiled rules: "..initialized_chunk; + end + return function (pass_return) + return initialized_chunk(active_definitions, fire_event, logger(filename), module, pass_return); -- Returns event handler with upvalues + end +end + +local function resolve_script_path(script_path) + local relative_to = prosody.paths.config; + if script_path:match("^module:") then + relative_to = module:get_directory(); + script_path = script_path:match("^module:(.+)$"); + end + return resolve_relative_path(relative_to, script_path); +end + +-- [filename] = { last_modified = ..., events_hooked = { [name] = handler } } +local loaded_scripts = {}; + +function load_script(script) + script = resolve_script_path(script); + local last_modified = (lfs.attributes(script) or {}).modification or os.time(); + if loaded_scripts[script] then + if loaded_scripts[script].last_modified == last_modified then + return; -- Already loaded, and source file hasn't changed + end + module:log("debug", "Reloading %s", script); + -- Already loaded, but the source file has changed + -- unload it now, and we'll load the new version below + unload_script(script, true); + end + local chain_functions, err = compile_firewall_rules(script); + + if not chain_functions then + module:log("error", "Error compiling %s: %s", script, err or "unknown error"); + return; + end + + -- Loop through the chains in the script, and for each chain attach the compiled code to the + -- relevant events, keeping track in events_hooked so we can cleanly unload later + local events_hooked = {}; + for chain, handler_code in pairs(chain_functions) do + local new_handler, err = compile_handler(handler_code, "mod_firewall::"..chain); + if not new_handler then + module:log("error", "Compilation error for %s: %s", script, err); + else + local chain_definition = chains[chain]; + if chain_definition and chain_definition.type == "event" then + local handler = new_handler(chain_definition.pass_return); + for _, event_name in ipairs(chain_definition) do + events_hooked[event_name] = handler; + module:hook(event_name, handler, chain_definition.priority); + end + elseif not chain:sub(1, 5) == "user/" then + module:log("warn", "Unknown chain %q", chain); + end + local event_name, handler = "firewall/chains/"..chain, new_handler(false); + events_hooked[event_name] = handler; + module:hook(event_name, handler); + end + end + loaded_scripts[script] = { last_modified = last_modified, events_hooked = events_hooked }; + module:log("debug", "Loaded %s", script); +end + +--COMPAT w/0.9 (no module:unhook()!) +local function module_unhook(event, handler) + return module:unhook_object_event((hosts[module.host] or prosody).events, event, handler); +end + +function unload_script(script, is_reload) + script = resolve_script_path(script); + local script_info = loaded_scripts[script]; + if not script_info then + return; -- Script not loaded + end + local events_hooked = script_info.events_hooked; + for event_name, event_handler in pairs(events_hooked) do + module_unhook(event_name, event_handler); + events_hooked[event_name] = nil; + end + loaded_scripts[script] = nil; + if not is_reload then + module:log("debug", "Unloaded %s", script); + end +end + +-- Given a set of scripts (e.g. from config) figure out which ones need to +-- be loaded, which are already loaded but need unloading, and which to reload +function load_unload_scripts(script_list) + local wanted_scripts = script_list / resolve_script_path; + local currently_loaded = set.new(it.to_array(it.keys(loaded_scripts))); + local scripts_to_unload = currently_loaded - wanted_scripts; + for script in wanted_scripts do + -- If the script is already loaded, this is fine - it will + -- reload the script for us if the file has changed + load_script(script); + end + for script in scripts_to_unload do + unload_script(script); + end +end + +function module.load() + if not prosody.arg then return end -- Don't run in prosodyctl + local firewall_scripts = module:get_option_set("firewall_scripts", {}); + load_unload_scripts(firewall_scripts); + -- Replace contents of definitions table (shared) with active definitions + for k in it.keys(definitions) do definitions[k] = nil; end + for k,v in pairs(active_definitions) do definitions[k] = v; end +end + +function module.save() + return { active_definitions = active_definitions, loaded_scripts = loaded_scripts }; +end + +function module.restore(state) + active_definitions = state.active_definitions; + loaded_scripts = state.loaded_scripts; +end + +module:hook_global("config-reloaded", function () + load_unload_scripts(module:get_option_set("firewall_scripts", {})); +end); + +function module.command(arg) + if not arg[1] or arg[1] == "--help" then + require"util.prosodyctl".show_usage([[mod_firewall ]], [[Compile files with firewall rules to Lua code]]); + return 1; + end + local verbose = arg[1] == "-v"; + if verbose then table.remove(arg, 1); end + + if arg[1] == "test" then + table.remove(arg, 1); + return module:require("test")(arg); + end + + local serialize = require "util.serialization".serialize; + if verbose then + print("local logger = require \"util.logger\".init;"); + print(); + print("local function fire_event(name, data)\n\tmodule:fire_event(name, data)\nend"); + print(); + end + + for _, filename in ipairs(arg) do + filename = resolve_script_path(filename); + print("do -- File "..filename); + local chain_functions = assert(compile_firewall_rules(filename)); + if verbose then + print(); + print("local active_definitions = "..serialize(active_definitions)..";"); + print(); + end + local c = 0; + for chain, handler_code in pairs(chain_functions) do + c = c + 1; + print("---- Chain "..chain:gsub("_", " ")); + local chain_func_name = "chain_"..tostring(c).."_"..chain:gsub("%p", "_"); + if not verbose then + print(("%s = %s;"):format(chain_func_name, handler_code:sub(8))); + else + + print(("local %s = (%s)(active_definitions, fire_event, logger(%q));"):format(chain_func_name, handler_code:sub(8), filename)); + print(); + + local chain_definition = chains[chain]; + if chain_definition and chain_definition.type == "event" then + for _, event_name in ipairs(chain_definition) do + print(("module:hook(%q, %s, %d);"):format(event_name, chain_func_name, chain_definition.priority or 0)); + end + end + print(("module:hook(%q, %s, %d);"):format("firewall/chains/"..chain, chain_func_name, chain_definition.priority or 0)); + end + + print("---- End of chain "..chain); + print(); + end + print("end -- End of file "..filename); + end +end + + +-- Console + +local console_env = module:shared("/*/admin_shell/env"); + +console_env.firewall = {}; + +function console_env.firewall:mark(user_jid, mark_name) + local username, host = jid.split(user_jid); + if not username or not hosts[host] then + return nil, "Invalid JID supplied"; + elseif not idsafe(mark_name) then + return nil, "Invalid characters in mark name"; + end + if not module:context(host):fire_event("firewall/marked/user", { + username = session.username; + mark = mark_name; + timestamp = os.time(); + }) then + return nil, "Mark not set - is mod_firewall loaded on that host?"; + end + return true, "User marked"; +end + +function console_env.firewall:unmark(jid, mark_name) + local username, host = jid.split(user_jid); + if not username or not hosts[host] then + return nil, "Invalid JID supplied"; + elseif not idsafe(mark_name) then + return nil, "Invalid characters in mark name"; + end + if not module:context(host):fire_event("firewall/unmarked/user", { + username = session.username; + mark = mark_name; + }) then + return nil, "Mark not removed - is mod_firewall loaded on that host?"; + end + return true, "User unmarked"; +end diff --git a/prosody-modules/mod_firewall/scripts/jabberspam-simple-blocklist.pfw b/prosody-modules/mod_firewall/scripts/jabberspam-simple-blocklist.pfw new file mode 100644 index 00000000..bbc1c8f8 --- /dev/null +++ b/prosody-modules/mod_firewall/scripts/jabberspam-simple-blocklist.pfw @@ -0,0 +1,17 @@ +# This is a simple ruleset to block all traffic from servers +# on the JabberSPAM blocklist. Even traffic from existing user +# contacts will be blocked. +# +# Example config (make sure "firewall" is in modules_enabled): +# +# firewall_scripts = { "module:scripts/jabberspam-simple-blocklist.pfw" } +# +# For a more advanced ruleset, consider using spam-blocking.pfw +# and spam-blocklists.pfw. + +%LIST blocklist: https://cdn.jsdelivr.net/gh/jabberspam/blacklist/blacklist.txt + +::deliver + +CHECK LIST: blocklist contains $<@from|host> +BOUNCE=policy-violation (Your server is blocked due to spam) diff --git a/prosody-modules/mod_firewall/scripts/spam-blocking.pfw b/prosody-modules/mod_firewall/scripts/spam-blocking.pfw new file mode 100644 index 00000000..ed6d8e30 --- /dev/null +++ b/prosody-modules/mod_firewall/scripts/spam-blocking.pfw @@ -0,0 +1,182 @@ +#### Anti-spam ruleset ########################################### +# +# This script provides some foundational anti-spam rules. It aims +# to PASS stanzas that are definitely not spam, and DROP stanzas +# that are very likely spam. +# +# It does not do any form of content filtering, +# but this can be implemented by other scripts and +# modules as desired using the chains documented below. +# +# +# The following chains are available as extension +# points: +# +# ::user/spam_check_custom +# Apply additional rules to all stanzas before they are checked. +# Mainly useful to PASS stanzas that you do not want to be +# filtered. +# +# ::user/spam_check_message_custom +# Apply additional rules to messages from strangers, aiming to +# PASS stanzas that are not spam and jump to ::user/spam_reject +# for stanzas that are considered spam. +# +# ::user/spam_check_message_content_custom +# Apply additional rules to messages that may be spam, based on +# message content rules. These may contain more intensive rules, +# so are executed after all other checks. Rules should jump to +# ::user/spam_reject if a message is considered spam. +# +# ::user/spam_check_presence_custom +# Apply additional rules to presence that may be spam. +# +# ::user/spam_check_subscription_request_custom +# Apply additional rules to subscription requests. +# +# ::user/spam_handle_unknown_custom +# Override default handling of stanzas that weren't explicitly +# passed or rejected by the anti-spam checks. +# +# ::user/spam_reject_custom +# Override default handling of stanzas that have +# been recognised as spam (default is to bounce +# a policy-violation error). +# +################################################################## + +#### Entry point for all incoming stanzas ######################## +::deliver + +# Override this if you want to prevent certain stanzas going through +# the normal spam_check chain +JUMP_CHAIN=user/spam_check_custom + +# Run the default spam_check chain +JUMP_CHAIN=user/spam_check + +################################################################## + +#### General spam-checking rules (all stanzas) ################### +::user/spam_check + +# Pass stanzas that a user sends to their own account +TO SELF? +PASS. + +# Pass stanzas that are addressed to a valid full JID +TO FULL JID? +PASS. + +# Pass stanzas from contacts +SUBSCRIBED? +PASS. + +# Run extra rules that apply to messages only +KIND: message +JUMP CHAIN=user/spam_check_message + +# Run extra rules that apply to presence stanzas only +KIND: presence +JUMP CHAIN=user/spam_check_presence + +JUMP CHAIN=user/spam_handle_unknown + +# Default is to allow, override this with +# the 'user/spam_handle_unknown' chain +PASS. + +#### Rules for messages ########################################## +::user/spam_check_message + +JUMP CHAIN=user/spam_check_message_custom + +# Type 'groupchat' messages addressed to an offline full JID are harmless, +# and should be routed normally to handle MUC 'ghosts' correctly +TO: <*>@<*>/<*> +TYPE: groupchat +PASS. + +# Mediated MUC invitations are naturally from 'strangers' and have special +# handling. We lean towards accepting them, unless overridden by custom rules. +NOT FROM FULL JID? +INSPECT: {http://jabber.org/protocol/muc#user}x/invite +JUMP CHAIN=user/spam_check_muc_invite + +# Non-chat message types often generate pop-ups in clients, +# so we won't accept them from strangers +NOT TYPE: chat +JUMP CHAIN=user/spam_reject + +JUMP CHAIN=user/spam_check_message_content + +# This chain can be used by other scripts +# and modules that analyze message content +JUMP CHAIN=user/spam_check_message_content_custom + +################################################################## + +#### Rules for presence stanzas ################################## +::user/spam_check_presence + +JUMP CHAIN=user/spam_check_presence_custom + +# Presence to offline full JIDs is harmless, and should be routed +# normally to handle MUC 'ghosts' correctly +TO: <*>@<*>/<*> +PASS. + +# These may be received if rosters get out of sync and are harmless +# because they will not be routed to the client unless necessary +TYPE: unsubscribe|unsubscribed +PASS. + +# We don't want to receive presence from random strangers, +# but still allow subscription requests +NOT TYPE: subscribe|subscribed +DROP. + +# This chain can be used by other scripts +# and modules to filter subscription requests +JUMP CHAIN=user/spam_check_subscription_request + +JUMP CHAIN=user/spam_check_subscription_request_custom + +################################################################## + +#### Rules for MUC invitations ################################### + +::user/spam_check_muc_invite + +# This chain can be used to inspect the invitation and determine +# the appropriate action. Otherwise, we proceed with the default +# action below. +JUMP CHAIN=user/spam_check_muc_invite_custom + +# Allow mediated MUC invitations by default +PASS. + +#### Stanzas reaching this chain will be rejected ################ +::user/spam_reject + +# This chain can be used by other scripts +# and modules to override the default behaviour +# when rejecting spam stanzas +JUMP CHAIN=user/spam_reject_custom + +LOG=Rejecting suspected spam: $(stanza:top_tag()) +BOUNCE=policy-violation + +################################################################## + +#### Stanzas that may be spam, but we're not sure either way ##### +::user/spam_handle_unknown + +# This chain can be used by other scripts +# and modules to apply additional checks, or to +# override the default behaviour +JUMP CHAIN=user/spam_handle_unknown_custom + +#LOG=[debug] Spam check allowing: $(stanza:top_tag()) + +################################################################## diff --git a/prosody-modules/mod_firewall/scripts/spam-blocklists.pfw b/prosody-modules/mod_firewall/scripts/spam-blocklists.pfw new file mode 100644 index 00000000..e6f94b99 --- /dev/null +++ b/prosody-modules/mod_firewall/scripts/spam-blocklists.pfw @@ -0,0 +1,20 @@ +# This script depends on spam-blocking.pfw also being loaded +# Any traffic that is not explicitly blocked or allowed by other +# rules will be checked against the JabberSPAM server blocklist + +%LIST blocklist: https://cdn.jsdelivr.net/gh/jabberspam/blacklist/blacklist.txt + +::user/spam_handle_unknown_custom + +CHECK LIST: blocklist contains $<@from|host> +BOUNCE=policy-violation (Your server is blocked due to spam) + +::user/spam_check_muc_invite_custom + +# Check the server we received the invitation from +CHECK LIST: blocklist contains $<@from|host> +BOUNCE=policy-violation (Your server is blocked due to spam) + +# Check the inviter's JID against the blocklist, too +CHECK LIST: blocklist contains $<{http://jabber.org/protocol/muc#user}x/invite@from|host> +BOUNCE=policy-violation (Your server is blocked due to spam) diff --git a/prosody-modules/mod_firewall/scripts/spam-strip-xhtml.pfw b/prosody-modules/mod_firewall/scripts/spam-strip-xhtml.pfw new file mode 100644 index 00000000..38ca00f5 --- /dev/null +++ b/prosody-modules/mod_firewall/scripts/spam-strip-xhtml.pfw @@ -0,0 +1,5 @@ +# Strip XHTML-IM from messages received from strangers + +::user/spam_check_message_custom + +STRIP=html http://jabber.org/protocol/xhtml-im diff --git a/prosody-modules/mod_firewall/test.lib.lua b/prosody-modules/mod_firewall/test.lib.lua new file mode 100644 index 00000000..a72b0215 --- /dev/null +++ b/prosody-modules/mod_firewall/test.lib.lua @@ -0,0 +1,75 @@ +-- luacheck: globals load_unload_scripts +local set = require "util.set"; +local ltn12 = require "ltn12"; + +local xmppstream = require "util.xmppstream"; + +local function stderr(...) + io.stderr:write("** ", table.concat({...}, "\t", 1, select("#", ...)), "\n"); +end + +return function (arg) + require "net.http".request = function (url, ex, cb) + stderr("Making HTTP request to "..url); + local body_table = {}; + local ok, response_status, response_headers = require "ssl.https".request({ + url = url; + headers = ex.headers; + method = ex.body and "POST" or "GET"; + sink = ltn12.sink.table(body_table); + source = ex.body and ltn12.source.string(ex.body) or nil; + }); + stderr("HTTP response "..response_status); + cb(table.concat(body_table), response_status, { headers = response_headers }); + return true; + end; + + local stats_dropped, stats_passed = 0, 0; + + load_unload_scripts(set.new(arg)); + local stream_callbacks = { default_ns = "jabber:client" }; + + function stream_callbacks.streamopened(session) + session.notopen = nil; + end + function stream_callbacks.streamclosed() + end + function stream_callbacks.error(session, error_name, error_message) -- luacheck: ignore 212/session + stderr("Fatal error parsing XML stream: "..error_name..": "..tostring(error_message)) + assert(false); + end + function stream_callbacks.handlestanza(session, stanza) + if not module:fire_event("firewall/chains/deliver", { origin = session, stanza = stanza }) then + stats_passed = stats_passed + 1; + print(stanza); + print(""); + else + stats_dropped = stats_dropped + 1; + end + end + + local session = { notopen = true }; + function session.send(stanza) + stderr("Reply:", "\n"..tostring(stanza).."\n"); + end + local stream = xmppstream.new(session, stream_callbacks); + stream:feed(""); + local line_count = 0; + for line in io.lines() do + line_count = line_count + 1; + local ok, err = stream:feed(line.."\n"); + if not ok then + stderr("Fatal XML parse error on line "..line_count..": "..err); + return 1; + end + end + + stderr("Summary"); + stderr("-------"); + stderr(""); + stderr(stats_dropped + stats_passed, "processed"); + stderr(stats_passed, "passed"); + stderr(stats_dropped, "dropped"); + stderr(line_count, "input lines"); + stderr(""); +end diff --git a/prosody-modules/mod_muc_anonymize_moderation_actions/README.markdown b/prosody-modules/mod_muc_anonymize_moderation_actions/README.markdown new file mode 100644 index 00000000..62a1b323 --- /dev/null +++ b/prosody-modules/mod_muc_anonymize_moderation_actions/README.markdown @@ -0,0 +1,32 @@ + +# mod_muc_anonymize_moderation_actions + +This modules allows to anonymize affiliation and role changes in MUC rooms. + +Enabling this module on a MUC Virtualhost will add a settings in the roomconfig form. +When the feature is enabled, when a moderator changes the role or affiliation of an occupant (kick, ban, ...) their name will be removed from the broadcasted message, to not disclose who did the moderation action. + +This is particularly usefull to prevent some revenge when a moderator bans someone. + +This module is under AGPL-3.0 license. + +It was tested on Prosody 0.12.x. + +## Configuration + +Just enable the module on your MUC VirtualHost. +The feature will be accessible throught the room configuration form. + +You can tweak the position of the settings in the MUC configuration form using `anonymize_moderation_actions_form_position`. +This value will be passed as priority for the "muc-config-form" hook, so you can move field up by increasing the value, or down by decreasing the value. + +By default, the field will be between muc#roomconfig_changesubject and muc#roomconfig_moderatedroom (default value is `78`). + +``` lua +VirtualHost "muc.example.com" + modules_enabled = { "muc_anonymize_moderation_actions" } + anonymize_moderation_actions_form_position = 96 +``` diff --git a/prosody-modules/mod_muc_anonymize_moderation_actions/mod_muc_anonymize_moderation_actions.lua b/prosody-modules/mod_muc_anonymize_moderation_actions/mod_muc_anonymize_moderation_actions.lua new file mode 100644 index 00000000..89a41cc7 --- /dev/null +++ b/prosody-modules/mod_muc_anonymize_moderation_actions/mod_muc_anonymize_moderation_actions.lua @@ -0,0 +1,76 @@ +-- mod_muc_anonymize_moderation_actions +-- +-- SPDX-FileCopyrightText: 2024 John Livingston +-- SPDX-License-Identifier: AGPL-3.0-only + +-- form_position: the position in the room config form (this value will be passed as priority for the "muc-config-form" hook). +-- By default, field will be between muc#roomconfig_changesubject and muc#roomconfig_moderatedroom +local form_position = module:get_option_number("anonymize_moderation_actions_form_position") or 80-2; + +local function get_anonymize_moderation_actions(room) + return room._data.anonymize_moderation_actions or false; +end + +local function set_anonymize_moderation_actions(room, anonymize_moderation_actions) + anonymize_moderation_actions = anonymize_moderation_actions and true or nil; + if get_anonymize_moderation_actions(room) == anonymize_moderation_actions then return false; end + room._data.anonymize_moderation_actions = anonymize_moderation_actions; + return true; +end + +-- Config form declaration +local function add_form_option(event) + table.insert(event.form, { + name = "muc#roomconfig_anonymize_moderation_actions"; + type = "boolean"; + label = "Anonymize moderation actions"; + desc = "When this is enabled, moderation actions will be anonymized, to avoid disclosing who is banning/kicking/… occupants."; + value = get_anonymize_moderation_actions(event.room); + }); +end + +local function config_submitted(event) + set_anonymize_moderation_actions(event.room, event.value); +end + +local function remove_actor(event) + if (event.room and get_anonymize_moderation_actions(event.room)) then + event.actor = nil; + end +end + +local function remove_moderate_actor(event) + local room, announcement, tombstone = event.room, event.announcement, event.tombstone; + if not get_anonymize_moderation_actions(room) then + return; + end + + local moderated = announcement:find("{urn:xmpp:fasten:0}apply-to/{urn:xmpp:message-moderate:0}moderated"); + if moderated then + module:log("debug", "We must anonymize the moderation announcement for stanza %s", event.stanza_id); + -- FIXME: XEP-0245 has changed. + -- urn:xmpp:message-moderate:0 requires a "by" attribute + -- urn:xmpp:message-moderate:1 do not require the "by" attribute + -- So, for now, settings the room jid, as we only implement urn:xmpp:message-moderate:0. + moderated.attr.by = room.jid; + moderated:remove_children("occupant-id", "urn:xmpp:occupant-id:0"); + end + + if tombstone then + local moderated = tombstone:get_child("moderated", "urn:xmpp:message-moderate:0"); + if moderated then + module:log("debug", "We must anonymize the moderation tombstone for stanza %s", event.stanza_id); + -- FIXME: XEP-0245 has changed. + -- urn:xmpp:message-moderate:0 requires a "by" attribute + -- urn:xmpp:message-moderate:1 do not require the "by" attribute + -- So, for now, settings the room jid, as we only implement urn:xmpp:message-moderate:0. + moderated.attr.by = room.jid; + moderated:remove_children("occupant-id", "urn:xmpp:occupant-id:0"); + end + end +end + +module:hook("muc-config-submitted/muc#roomconfig_anonymize_moderation_actions", config_submitted); +module:hook("muc-config-form", add_form_option, form_position); +module:hook("muc-broadcast-presence", remove_actor); +module:hook("muc-moderate-message", remove_moderate_actor); diff --git a/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua b/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua index 1a217671..771498ad 100644 --- a/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua +++ b/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua @@ -8,6 +8,7 @@ -- * "slow_mode_duration" -- * "mute_anonymous" -- * "moderation_delay" +-- * "anonymize_moderation_actions" -- These options are introduced in the Peertube livechat plugin. -- -- The "slow_mode_duration" comes with mod_muc_slow_mode. @@ -132,6 +133,9 @@ local function apply_config(room, settings) -- (and thus we don't need to broadcast changes) room._data.livechat_muc_terms = config.livechat_muc_terms; end + if (type(config.anonymize_moderation_actions) == "boolean") then + room._data.anonymize_moderation_actions = config.anonymize_moderation_actions; + end elseif config ~= nil then module:log("error", "Invalid config returned from API for %s: %q", room.jid, config); return nil, "format", { field = "config" }; diff --git a/prosody-modules/mod_muc_mam_search/README.markdown b/prosody-modules/mod_muc_mam_search/README.markdown new file mode 100644 index 00000000..0ca73b79 --- /dev/null +++ b/prosody-modules/mod_muc_mam_search/README.markdown @@ -0,0 +1,23 @@ + +# mod_muc_mam_search + +With this module you can make some advanced search in MAM (MUC Archive Management - XEP-0313). + +This module is part of peertube-plugin-livechat, and is under AGPL-3.0.-only license. +This module can work on any Prosody server (version >= 0.12.x). +This module is still experimental. + +## Configuration + +Just enable the module on your MUC component. + +The feature will be announced using Service Discovery (XEP-0030). + +TODO: document the Disco namespace. + +You can then query archives using query similar to those for XEP-0313. + +TODO: document available queries. diff --git a/prosody-modules/mod_muc_mam_search/archive.lib.lua b/prosody-modules/mod_muc_mam_search/archive.lib.lua new file mode 100644 index 00000000..d26be1e6 --- /dev/null +++ b/prosody-modules/mod_muc_mam_search/archive.lib.lua @@ -0,0 +1,25 @@ +-- SPDX-FileCopyrightText: 2024 John Livingston +-- SPDX-License-Identifier: AGPL-3.0-only + +-- FIXME: these imports are copied from mod_muc_mam, we should avoid that. +local log_all_rooms = module:get_option_boolean("muc_log_all_rooms", false); +local log_by_default = module:get_option_boolean("muc_log_by_default", true); + +-- FIXME: this function is copied from mod_muc_mam. We should not do so, and use directly the original function. +local function archiving_enabled(room) + if log_all_rooms then + module:log("debug", "Archiving all rooms"); + return true; + end + local enabled = room._data.archiving; + if enabled == nil then + module:log("debug", "Default is %s (for %s)", log_by_default, room.jid); + return log_by_default; + end + module:log("debug", "Logging in room %s is %s", room.jid, enabled); + return enabled; +end + +return { + archiving_enabled = archiving_enabled; +}; diff --git a/prosody-modules/mod_muc_mam_search/filter.lib.lua b/prosody-modules/mod_muc_mam_search/filter.lib.lua new file mode 100644 index 00000000..e202c54f --- /dev/null +++ b/prosody-modules/mod_muc_mam_search/filter.lib.lua @@ -0,0 +1,24 @@ +-- SPDX-FileCopyrightText: 2024 John Livingston +-- SPDX-License-Identifier: AGPL-3.0-only + +-- Perform the search criteria. +-- Returns true if the item match. +-- Note: there is a logical OR between search_from and search_occupant_id +local function item_match(id, item, search_from, search_occupant_id) + if (search_from ~= nil) then + if (search_from == item.attr.from) then + return true; + end + end + if (search_occupant_id ~= nil) then + local occupant_id = item:get_child("occupant-id", "urn:xmpp:occupant-id:0"); + if (occupant_id and occupant_id.attr.id == search_occupant_id) then + return true; + end + end + return false; +end + +return { + item_match = item_match; +}; diff --git a/prosody-modules/mod_muc_mam_search/mod_muc_mam_search.lua b/prosody-modules/mod_muc_mam_search/mod_muc_mam_search.lua new file mode 100644 index 00000000..6a92c2aa --- /dev/null +++ b/prosody-modules/mod_muc_mam_search/mod_muc_mam_search.lua @@ -0,0 +1,154 @@ +-- mod_muc_mam_search +-- +-- SPDX-FileCopyrightText: 2024 John Livingston +-- SPDX-License-Identifier: AGPL-3.0-only +-- + +local archiving_enabled = module:require("archive").archiving_enabled; +local item_match = module:require("filter").item_match; +local jid_split = require "util.jid".split; +local jid_bare = require "util.jid".bare; +local st = require "util.stanza"; +local datetime = require"util.datetime"; +local dataform = require "util.dataforms".new; +local get_form_type = require "util.dataforms".get_type; + +local mod_muc = module:depends"muc"; +local get_room_from_jid = mod_muc.get_room_from_jid; +local muc_log_archive = module:open_store("muc_log", "archive"); + +local xmlns_mam = "urn:xmpp:mam:2"; +local xmlns_mam_search = "urn:xmpp:mam:2#x-search"; +local xmlns_delay = "urn:xmpp:delay"; +local xmlns_forward = "urn:xmpp:forward:0"; + +module:hook("muc-disco#info", function(event) + if archiving_enabled(event.room) then + event.reply:tag("feature", {var=xmlns_mam_search}):up(); + end +end); + +local query_form = dataform { + { name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam_search }; + { name = "from"; type = "jid-single" }; + { name = "occupant_id"; type = "text-single" }; +}; + +-- Serve form +module:hook("iq-get/bare/"..xmlns_mam_search..":query", function(event) + local origin, stanza = event.origin, event.stanza; + origin.send(st.reply(stanza):query(xmlns_mam_search):add_child(query_form:form())); + return true; +end); + +-- Handle archive queries +module:hook("iq-set/bare/"..xmlns_mam_search..":query", function(event) + local origin, stanza = event.origin, event.stanza; + local room_jid = stanza.attr.to; + local room_node = jid_split(room_jid); + local orig_from = stanza.attr.from; + local query = stanza.tags[1]; + + local room = get_room_from_jid(room_jid); + if not room then + origin.send(st.error_reply(stanza, "cancel", "item-not-found")) + return true; + end + local from = jid_bare(orig_from); + + -- Must be room admin or owner. + local from_affiliation = room:get_affiliation(from); + if (from_affiliation ~= "owner" and from_affiliation ~= "admin") then + origin.send(st.error_reply(stanza, "auth", "forbidden")) + return true; + end + + local qid = query.attr.queryid; + + -- Search query parameters + local search_from; + local search_occupant_id; + local form = query:get_child("x", "jabber:x:data"); + if form then + local form_type, err = get_form_type(form); + if not form_type then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid dataform: "..err)); + return true; + elseif form_type ~= xmlns_mam_search then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam_search.."'")); + return true; + end + form, err = query_form:data(form); + if err then + origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); + return true; + end + + search_from = form["from"]; + search_occupant_id = form["occupant_id"]; + else + module:log("debug", "Missing query form, forbidden.") + origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing dataform")); + return true; + end + + -- TODO: handle RSM (pagination)? + module:log("debug", "Archive query by %s id=%s", from, qid); + + -- Load all the data! + local data, err = muc_log_archive:find(room_node, { + start = nil; ["end"] = nil; + with = "message tag, containing the original senders JID, unless the room makes this public. + -- but we only allow this feature to owner and admin, so we don't need to remove this. + + item.attr.to = nil; + item.attr.xmlns = "jabber:client"; + fwd_st:add_child(item); + + origin.send(fwd_st); + end + end + + origin.send(st.reply(stanza) + -- The result uses xmlns_mam and not xmlns_mam_search, so that the frontend handles this in the same way than xmlns_mam. + :tag("fin", { xmlns = xmlns_mam, complete = "true" })); + + -- That's all folks! + module:log("debug", "Archive query id=%s completed, %d items returned", qid or stanza.attr.id, count); + return true; +end); diff --git a/prosody-modules/mod_muc_moderation/README.markdown b/prosody-modules/mod_muc_moderation/README.markdown index 6c914443..4abb1f66 100644 --- a/prosody-modules/mod_muc_moderation/README.markdown +++ b/prosody-modules/mod_muc_moderation/README.markdown @@ -1,7 +1,9 @@ - +summary: Let moderators remove spam and abuse messages +--- + # Introduction This module implements [XEP-0425: Message Moderation]. @@ -16,7 +18,7 @@ role in the channel / group chat. Example [MUC component][doc:chatrooms] configuration: ``` {.lua} -VirtualHost "channels.example.com" "muc" +Component "channels.example.com" "muc" modules_enabled = { "muc_mam", "muc_moderation", @@ -25,20 +27,19 @@ modules_enabled = { # Compatibility -- Should work with Prosody 0.11.x and later. -- Tested with trunk rev `52c6dfa04dba`. -- Message tombstones requires a compatible storage module implementing - a new message replacement API. +- Basic functionality with Prosody 0.11.x and later +- Full functionality with Prosody 0.12.x and `internal` or `sql` + storage^[Replacing moderated messages with tombstones requires new storage API methods.] +- Works with [mod_storage_xmlarchive] ## Clients -- Tested with [Converse.js](https://conversejs.org/) - [v6.0.1](https://github.com/conversejs/converse.js/releases/tag/v6.0.1) +- [Converse.js](https://conversejs.org/) +- [Gajim](https://dev.gajim.org/gajim/gajim/-/issues/10107) +- [clix](https://code.zash.se/clix/rev/6c1953fbe0fa) ### Feature requests -- [Conv](https://github.com/iNPUTmice/Conversations/issues/3722)[ersa](https://github.com/iNPUTmice/Conversations/issues/3920)[tions](https://github.com/iNPUTmice/Conversations/issues/4227) -- [Dino](https://github.com/dino/dino/issues/1133) -- [Gajim](https://dev.gajim.org/gajim/gajim/-/issues/10107) -- [Poezio](https://lab.louiz.org/poezio/poezio/-/issues/3543) -- [Profanity](https://github.com/profanity-im/profanity/issues/1336) +- [Conversations](https://codeberg.org/iNPUTmice/Conversations/issues/20) +- [Dino](https://github.com/dino/dino/issues/1133) +- [Profanity](https://github.com/profanity-im/profanity/issues/1336) diff --git a/prosody-modules/mod_muc_moderation/mod_muc_moderation.lua b/prosody-modules/mod_muc_moderation/mod_muc_moderation.lua index 318175be..1e794841 100644 --- a/prosody-modules/mod_muc_moderation/mod_muc_moderation.lua +++ b/prosody-modules/mod_muc_moderation/mod_muc_moderation.lua @@ -27,6 +27,7 @@ end -- Namespaces local xmlns_fasten = "urn:xmpp:fasten:0"; local xmlns_moderate = "urn:xmpp:message-moderate:0"; +local xmlns_occupant_id = "urn:xmpp:occupant-id:0"; local xmlns_retract = "urn:xmpp:message-retract:0"; -- Discovering support @@ -34,36 +35,17 @@ module:hook("muc-disco#info", function (event) event.reply:tag("feature", { var = xmlns_moderate }):up(); end); --- Main handling -module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) - local stanza, origin = event.stanza, event.origin; +-- TODO error registry, requires Prosody 0.12+ - -- Collect info we need - local apply_to = stanza.tags[1]; - local moderate_tag = apply_to:get_child("moderate", xmlns_moderate); - if not moderate_tag then return end -- some other kind of fastening? - - local reason = moderate_tag:get_child_text("reason"); - local retract = moderate_tag:get_child("retract", xmlns_retract); - - local room_jid = stanza.attr.to; +-- moderate : function (string, string, string, boolean, string) : boolean, enum, enum, string +local function moderate(actor, room_jid, stanza_id, retract, reason) local room_node = jid.split(room_jid); local room = mod_muc.get_room_from_jid(room_jid); - local stanza_id = apply_to.attr.id; - - -- Permissions - local actor = stanza.attr.from; + -- Permissions is based on role, which is a property of a current occupant, + -- so check if the actor is an occupant, otherwise if they have a reserved + -- nickname that can be used to retrieve the role. local actor_nick = room:get_occupant_jid(actor); - local affiliation = room:get_affiliation(actor); - -- Retrieve their current role, iff they are in the room, otherwise what they - -- would have based on affiliation. - local role = room:get_role(actor_nick) or room:get_default_role(affiliation); - if valid_roles[role or "none"] < valid_roles.moderator then - origin.send(st.error_reply(stanza, "auth", "forbidden", "You need a role of at least 'moderator'")); - return true; - end - if not actor_nick then local reserved_nickname = room:get_affiliation_data(jid.bare(actor), "reserved_nickname"); if reserved_nickname then @@ -71,6 +53,14 @@ module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) end end + -- Retrieve their current role, iff they are in the room, otherwise what they + -- would have based on affiliation. + local affiliation = room:get_affiliation(actor); + local role = room:get_role(actor_nick) or room:get_default_role(affiliation); + if valid_roles[role or "none"] < valid_roles.moderator then + return false, "auth", "forbidden", "You need a role of at least 'moderator'"; + end + -- Original stanza to base tombstone on local original, err; if muc_log_archive.get then @@ -84,20 +74,26 @@ module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) end end end + if not original then if err == "item-not-found" then - origin.send(st.error_reply(stanza, "modify", "item-not-found")); + return false, "modify", "item-not-found"; else - origin.send(st.error_reply(stanza, "wait", "internal-server-error")); + return false, "wait", "internal-server-error"; end - return true; end + local actor_occupant = room:get_occupant_by_real_jid(actor) or room:new_occupant(jid.bare(actor), actor_nick); local announcement = st.message({ from = room_jid, type = "groupchat", id = id.medium(), }) :tag("apply-to", { xmlns = xmlns_fasten, id = stanza_id }) :tag("moderated", { xmlns = xmlns_moderate, by = actor_nick }) + if room.get_occupant_id then + -- This isn't a regular broadcast message going through the events occupant_id.lib hooks so we do this here + announcement:add_child(st.stanza("occupant-id", { xmlns = xmlns_occupant_id; id = room:get_occupant_id(actor_occupant) })); + end + if retract then announcement:tag("retract", { xmlns = xmlns_retract }):up(); end @@ -106,8 +102,16 @@ module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) announcement:text_tag("reason", reason); end + local moderated_occupant_id = original:get_child("occupant-id", xmlns_occupant_id); + if room.get_occupant_id and moderated_occupant_id then + announcement:add_direct_child(moderated_occupant_id); + end + + announcement:reset(); + + local tombstone = nil; if muc_log_archive.set and retract then - local tombstone = st.message({ from = original.attr.from, type = "groupchat", id = original.attr.id }) + tombstone = st.message({ from = original.attr.from, type = "groupchat", id = original.attr.id }) :tag("moderated", { xmlns = xmlns_moderate, by = actor_nick }) :tag("retracted", { xmlns = xmlns_retract, stamp = dt.datetime() }):up(); @@ -115,10 +119,39 @@ module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) tombstone:text_tag("reason", reason); end + if room.get_occupant_id then + if actor_occupant then + tombstone:add_child(st.stanza("occupant-id", { xmlns = xmlns_occupant_id; id = room:get_occupant_id(actor_occupant) })); + end + + if moderated_occupant_id then + -- Copy occupant id from moderated message + tombstone:add_direct_child(moderated_occupant_id); + end + end + tombstone:reset(); + end + + -- fire an event, that can be used to cancel the moderation, or modify stanzas. + local event = { + room = room; + announcement = announcement; + tombstone = tombstone; + stanza_id = stanza_id; + retract = retract; + reason = reason; + actor = actor; + actor_nick = actor_nick; + }; + if module:fire_event("muc-moderate-message", event) then + -- TODO: allow to change the error message? + return false, "wait", "internal-server-error"; + end + + if tombstone then local was_replaced = muc_log_archive:set(room_node, stanza_id, tombstone); if not was_replaced then - origin.send(st.error_reply(stanza, "wait", "internal-server-error")); - return true; + return false, "wait", "internal-server-error"; end end @@ -126,6 +159,32 @@ module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) module:log("info", "Message with id '%s' in room %s moderated by %s, reason: %s", stanza_id, room_jid, actor, reason); room:broadcast_message(announcement); + return true; +end + +-- Main handling +module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) + local stanza, origin = event.stanza, event.origin; + + local actor = stanza.attr.from; + local room_jid = stanza.attr.to; + + -- Collect info we need + local apply_to = stanza.tags[1]; + local moderate_tag = apply_to:get_child("moderate", xmlns_moderate); + if not moderate_tag then return end -- some other kind of fastening? + + local reason = moderate_tag:get_child_text("reason"); + local retract = moderate_tag:get_child("retract", xmlns_retract); + + local stanza_id = apply_to.attr.id; + + local ok, error_type, error_condition, error_text = moderate(actor, room_jid, stanza_id, retract, reason); + if not ok then + origin.send(st.error_reply(stanza, error_type, error_condition, error_text)); + return true; + end + origin.send(st.reply(stanza)); return true; end); diff --git a/prosody-modules/mod_muc_moderation_delay/README.md b/prosody-modules/mod_muc_moderation_delay/README.markdown similarity index 93% rename from prosody-modules/mod_muc_moderation_delay/README.md rename to prosody-modules/mod_muc_moderation_delay/README.markdown index 7c831885..2b1d7812 100644 --- a/prosody-modules/mod_muc_moderation_delay/README.md +++ b/prosody-modules/mod_muc_moderation_delay/README.markdown @@ -6,8 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only With this module, you can apply a delay to groupchat messages delivery, so that room moderators can moderate them before other participants receives them. -This module is part of peertube-plugin-livechat, and is under the same LICENSE. +This module is part of peertube-plugin-livechat, and is under AGPL-3.0.-only license. This module can work on any Prosody server (version >= 0.12.x). +This module is still experimental. ## Configuration diff --git a/prosody-modules/mod_muc_poll/README.md b/prosody-modules/mod_muc_poll/README.md index 072a19a2..be61bb41 100644 --- a/prosody-modules/mod_muc_poll/README.md +++ b/prosody-modules/mod_muc_poll/README.md @@ -13,16 +13,16 @@ There will probably be a XEP proposal for this module behaviour. When done, this ## Configuration Just enable the module on your MUC component. -All above configurations are optional. +All following configurations are optional. -## poll_groupchat_votes_priority +### poll_groupchat_votes_priority The priority for the hook that will take into account votes. You can change this, if you have some specific hook that should be done after/before counting votes (slow mode, firewall, ...). Default: 40 (Prosody checks visitor role with priority of 50, we want this to be after). -## Strings +### Strings You can change some defaults strings, if you want for example to localize the poll messages. Here are the existing strings and default values: diff --git a/prosody-modules/mod_muc_slow_mode/README.md b/prosody-modules/mod_muc_slow_mode/README.markdown similarity index 100% rename from prosody-modules/mod_muc_slow_mode/README.md rename to prosody-modules/mod_muc_slow_mode/README.markdown diff --git a/prosody-modules/mod_pubsub_peertubelivechat/README.md b/prosody-modules/mod_pubsub_peertubelivechat/README.md index b0525942..62d143cc 100644 --- a/prosody-modules/mod_pubsub_peertubelivechat/README.md +++ b/prosody-modules/mod_pubsub_peertubelivechat/README.md @@ -7,13 +7,16 @@ SPDX-License-Identifier: AGPL-3.0-only This module is a custom module that provide some pubsub services associated to a MUC room. This module is entended to be used in the peertube-plugin-livechat project. -For each MUC room, there will be an associated pubsub node. -This node in only accessible by the ROOM admin/owner. +For each MUC room, there will be a associated pubsub nodes. +These nodes are only accessible by the ROOM admins/owners. -This node can contains various objects: +Here are a description of existing nodes, and objects they can contain: -* task lists -* tasks +* livechat-tasks: + * task lists + * tasks +* livechat-notes: + * notes * ... (more to come) These objects are meant te be shared between admin/owner. diff --git a/prosody-modules/mod_pubsub_peertubelivechat/mod_pubsub_peertubelivechat.lua b/prosody-modules/mod_pubsub_peertubelivechat/mod_pubsub_peertubelivechat.lua index 95465bf1..95afee23 100644 --- a/prosody-modules/mod_pubsub_peertubelivechat/mod_pubsub_peertubelivechat.lua +++ b/prosody-modules/mod_pubsub_peertubelivechat/mod_pubsub_peertubelivechat.lua @@ -15,6 +15,7 @@ -- Implemented nodes: -- * livechat-tasks: contains tasklist and task items, specific to livechat plugin. +-- * livechat-notes: contains notes, specific to livechat plugin. -- There are some other tricks in this module: -- * unsubscribing users that have left the room (the front-end will subscribe again when needed) @@ -39,16 +40,14 @@ local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event"; local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; local xmlns_tasklist = "urn:peertube-plugin-livechat:tasklist"; -local xmlns_task = "urn:peertube-plugin-livechat:task" +local xmlns_task = "urn:peertube-plugin-livechat:task"; +local xmlns_note = "urn:peertube-plugin-livechat:note"; local lib_pubsub = module:require "pubsub"; local mod_muc = module:depends"muc"; local get_room_from_jid = mod_muc.get_room_from_jid; -local muc_util = module:require "muc/util"; -local valid_roles = muc_util.valid_roles; - -- room_jid => object passed to module:add_items() local mep_service_items = {}; @@ -389,4 +388,5 @@ end); module:hook("muc-disco#info", function (event) event.reply:tag("feature", { var = xmlns_task }):up(); event.reply:tag("feature", { var = xmlns_tasklist }):up(); + event.reply:tag("feature", { var = xmlns_note }):up(); end); diff --git a/server/lib/configuration/channel/sanitize.ts b/server/lib/configuration/channel/sanitize.ts index 3aa45480..8a72f9ed 100644 --- a/server/lib/configuration/channel/sanitize.ts +++ b/server/lib/configuration/channel/sanitize.ts @@ -38,6 +38,7 @@ async function sanitizeChannelConfigurationOptions ( const moderationData = data.moderation ?? {} // comes with livechat 10.3.0 moderationData.delay ??= 0 + moderationData.anonymize ??= false // comes with livechat 11.0.0 // mute not present in livechat <= 10.2.0 const mute = data.mute ?? {} @@ -73,7 +74,8 @@ async function sanitizeChannelConfigurationOptions ( anonymous: _readBoolean(mute, 'anonymous') }, moderation: { - delay: _readInteger(moderationData, 'delay', 0, 60) + delay: _readInteger(moderationData, 'delay', 0, 60), + anonymize: _readBoolean(moderationData, 'anonymize') } } if (terms !== undefined) { diff --git a/server/lib/configuration/channel/storage.ts b/server/lib/configuration/channel/storage.ts index d5dbdf90..d9ecc1fe 100644 --- a/server/lib/configuration/channel/storage.ts +++ b/server/lib/configuration/channel/storage.ts @@ -54,7 +54,8 @@ function getDefaultChannelConfigurationOptions (_options: RegisterServerOptions) anonymous: false }, moderation: { - delay: 0 + delay: 0, + anonymize: false }, terms: undefined } diff --git a/server/lib/external-auth/oidc.ts b/server/lib/external-auth/oidc.ts index a694d00e..b2780450 100644 --- a/server/lib/external-auth/oidc.ts +++ b/server/lib/external-auth/oidc.ts @@ -87,8 +87,8 @@ class ExternalAuthOIDC { private readonly redirectUrl: string private readonly connectUrl: string private readonly externalVirtualhost: string - private readonly avatarsDir: string - private readonly avatarsFiles: string[] + private readonly avatarsDir: string | undefined + private readonly avatarsFiles: string[] | undefined private readonly encryptionOptions = { algorithm: 'aes256' as string, @@ -129,8 +129,8 @@ class ExternalAuthOIDC { connectUrl: string redirectUrl: string externalVirtualhost: string - avatarsDir: string - avatarsFiles: string[] + avatarsDir?: string + avatarsFiles?: string[] }) { this.logger = { debug: (s) => params.logger.debug('[ExternalAuthOIDC] ' + s), @@ -591,8 +591,8 @@ class ExternalAuthOIDC { */ private async getRandomAvatar (): Promise { try { - if (!this.avatarsDir || !this.avatarsFiles.length) { - throw new Error('Seems there is no default avatars') + if (!this.avatarsDir || !this.avatarsFiles?.length) { + return undefined } const file = this.avatarsFiles[Math.floor(Math.random() * this.avatarsFiles.length)] diff --git a/server/lib/firewall/config.ts b/server/lib/firewall/config.ts new file mode 100644 index 00000000..e29eaa88 --- /dev/null +++ b/server/lib/firewall/config.ts @@ -0,0 +1,210 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { RegisterServerOptions } from '@peertube/peertube-types' +import type { AdminFirewallConfiguration } from '../../../shared/lib/types' +import * as path from 'path' +import * as fs from 'fs' +import { + firewallNameRegexp, maxFirewallFileSize, maxFirewallFiles, maxFirewallNameLength +} from '../../../shared/lib/admin-firewall' + +/** + * Indicates if the firewall configuration can be changed in the Peertube web interface. + * Sys admins can disable this feature by creating a special file in the plugin folder. + * @param options Peertube server options + */ +export async function canEditFirewallConfig (options: RegisterServerOptions): Promise { + const peertubeHelpers = options.peertubeHelpers + const logger = peertubeHelpers.logger + if (!peertubeHelpers.plugin) { + return false + } + + const filepath = path.resolve(peertubeHelpers.plugin.getDataDirectoryPath(), 'disable_mod_firewall_editing') + try { + // Testing if file exist by reading it. + await fs.promises.readFile(filepath) + return false + } catch (err: any) { + if (('code' in err) && err.code === 'ENOENT') { + // File does not exist + return true + } + logger.error(err) + // Here it is safer to disable the editing... + return false + } +} + +/** + * Returns the list of mod_firewall configuration files. + * @param options: Peertube server options. + * @param dir the path to the directory containing these configuration files. + * @param includeDisabled if true, disabled files are included in the results. + */ +export async function listModFirewallFiles ( + options: RegisterServerOptions, + dir: string, + includeDisabled?: boolean +): Promise { + try { + const files = (await fs.promises.readdir(dir, { withFileTypes: true })).filter(file => { + if (!file.isFile()) { + return false + } + + if ( + file.name.endsWith('.pfw') && + // we only load valid names, to avoid having files that could not be edited from frontend + firewallNameRegexp.test(file.name.substring(0, file.name.length - 4)) + ) { + return true + } + + if ( + includeDisabled && + file.name.endsWith('.pfw.disabled') && + firewallNameRegexp.test(file.name.substring(0, file.name.length - 13)) + ) { + return true + } + + return false + }) + + return files.map(f => path.join(dir, f.name)).sort() + } catch (err) { + // should be that the directory does not exists + return [] + } +} + +/** + * Returns the modFirewall configuration. + * @param options Peertube server options + * @param dir the path to the directory containing these configuration files. + * @throws will throw an error if it can't read any of the configuration file. + */ +export async function getModFirewallConfig ( + options: RegisterServerOptions, + dir: string +): Promise { + const filePaths = await listModFirewallFiles(options, dir, true) + + const files = [] + for (const filePath of filePaths) { + const content = (await fs.promises.readFile(filePath)).toString() + const name = path.basename(filePath).replace(/\.pfw(\.disabled)?$/, '') + files.push({ + name, + content, + enabled: !filePath.endsWith('.disabled') + }) + } + + const enabled = (await options.settingsManager.getSetting('prosody-firewall-enabled')) === true + + return { + enabled, + files + } +} + +/** + * Sanitize any data received from the frontend, to store in modFirewall configuration. + * Throws an exception if data is invalid. + * @param options Peertube server options + * @param data Incoming data + */ +export async function sanitizeModFirewallConfig ( + options: RegisterServerOptions, + data: any +): Promise { + if (typeof data !== 'object') { + throw new Error('Invalid data type') + } + if (!Array.isArray(data.files)) { + throw new Error('Invalid data.files') + } + + if (data.files.length > maxFirewallFiles) { + throw new Error('Too many files') + } + + const files: AdminFirewallConfiguration['files'] = [] + for (const entry of data.files) { + if (typeof entry !== 'object') { + throw new Error('Invalid data in data.files') + } + if (typeof entry.enabled !== 'boolean') { + throw new Error('Invalid data in data.files (enabled)') + } + if (typeof entry.name !== 'string') { + throw new Error('Invalid data in data.files (name)') + } + if (typeof entry.content !== 'string') { + throw new Error('Invalid data in data.files (content)') + } + + if (entry.name.length > maxFirewallNameLength || !firewallNameRegexp.test(entry.name)) { + throw new Error('Invalid name in data.files') + } + if (entry.content.length > maxFirewallFileSize) { + throw new Error('File content too big in data.files') + } + + files.push({ + enabled: entry.enabled, + name: entry.name, + content: entry.content + }) + } + + const result = { + enabled: !!data.enabled, // this is not saved, so no need to check type. + files + } + + return result +} + +/** + * Saves the modFirewall configuration. + * FIXME: currently, if the save fails on one file, remaining files will not be saved. So there is a risk of data loss. + * @param options Peertube server options + * @param dir the path to the directory containing these configuration files. + * @param config the configuration to save + * @throws will throw an error if it can't read any of the configuration file. + */ +export async function saveModFirewallConfig ( + options: RegisterServerOptions, + dir: string, + config: AdminFirewallConfiguration +): Promise { + const logger = options.peertubeHelpers.logger + + const previousFiles = await listModFirewallFiles(options, dir, true) + + logger.debug('[mod-firewall-lib] Creating the ' + dir + ' directory.') + await fs.promises.mkdir(dir, { recursive: true }) + + const seen = new Map() + for (const f of config.files) { + const filePath = path.join( + dir, + f.name + '.pfw' + (f.enabled ? '' : '.disabled') + ) + logger.info('[mod-firewall-lib] Saving ' + filePath) + await fs.promises.writeFile(filePath, f.content) + seen.set(filePath, true) + } + + // Removing deprecated files: + for (const p of previousFiles) { + if (seen.has(p)) { continue } + logger.info('[mod-firewall-lib] Deleting deprecated file ' + p) + await fs.promises.rm(p) + } +} diff --git a/server/lib/middlewares/is-admin.ts b/server/lib/middlewares/is-admin.ts new file mode 100644 index 00000000..d220513c --- /dev/null +++ b/server/lib/middlewares/is-admin.ts @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { RegisterServerOptions } from '@peertube/peertube-types' +import type { Request, Response, NextFunction } from 'express' +import type { RequestPromiseHandler } from './async' +import { isUserAdmin } from '../helpers' + +/** + * Returns a middleware handler to check if advanced configuration is not disabled + * @param options Peertube server options + * @returns middleware function + */ +function checkUserIsAdminMiddleware (options: RegisterServerOptions): RequestPromiseHandler { + return async (req: Request, res: Response, next: NextFunction) => { + const logger = options.peertubeHelpers.logger + if (!await isUserAdmin(options, res)) { + logger.warn('Current user tries to access a page only allowed for admins, and has no right.') + res.sendStatus(403) + return + } + + logger.debug('User is admin, can access the page..') + next() + } +} + +export { + checkUserIsAdminMiddleware +} diff --git a/server/lib/migration/settings.ts b/server/lib/migration/settings.ts index b1a19d63..f73f729f 100644 --- a/server/lib/migration/settings.ts +++ b/server/lib/migration/settings.ts @@ -3,11 +3,77 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { RegisterServerOptions } from '@peertube/peertube-types' +import { pluginShortName } from '../helpers' async function migrateSettings (options: RegisterServerOptions): Promise { const logger = options.peertubeHelpers.logger logger.info('Checking if there is a migration script to launch...') // 2022-10-10: as we removed the «chat-type» settings, there is no migration needed for now. + // 2024-09-02: concord theme was removed from ConverseJS, must change if used. + await _migrateConverseTheme(options) +} + +async function _migrateConverseTheme (options: RegisterServerOptions): Promise { + const peertubeHelpers = options.peertubeHelpers + const logger = peertubeHelpers.logger + // NB: we cant use safely settingsManager.getSetting, because settings are not registered yet. + logger.info('Checking if we need to migrate converse-theme') + if (!/^[-a-z]+$/.test(pluginShortName)) { + // to prevent sql injection... be sure there is no special char here. + throw new Error(`Wrong pluginShortName '${pluginShortName}'`) + } + const [results] = await peertubeHelpers.database.query( + 'SELECT "settings" FROM "plugin"' + + ' WHERE "plugin"."name" = :pluginShortName', + { + replacements: { + pluginShortName + } + } + ) + if (!Array.isArray(results)) { + throw new Error('_migrateConverseTheme: query result is not an array.') + } + if (results.length === 0) { + logger.error('Plugin not found in database') + return + } + if (results.length > 1) { + logger.error('Multiple lines for plugin in database, dont know which one to migrate... Aborting.') + return + } + const settings = results[0].settings + if (!settings) { + logger.info('Plugin settings are empty in database, no migration needed.') + return + } + if (typeof settings !== 'object') { + logger.error('Plugin settings in database seems to be invalid json') + return + } + if (!('converse-theme' in settings)) { + logger.debug('The setting converse-theme is not here, no need to migrate.') + return + } + if (settings['converse-theme'] !== 'concord') { + logger.debug('The setting converse-theme is not set to concord, no need to migrate.') + return + } + + logger.info('The setting converse-theme is set to concord, we must replace by peertube..') + await peertubeHelpers.database.query( + 'UPDATE "plugin" ' + + ' SET "settings" = "settings" || :value ' + + ' WHERE "name" = :pluginShortName', + { + replacements: { + pluginShortName, + value: JSON.stringify({ + 'converse-theme': 'peertube' + }) + } + } + ) } export { diff --git a/server/lib/prosody/config.ts b/server/lib/prosody/config.ts index c132dfa8..25557f3b 100644 --- a/server/lib/prosody/config.ts +++ b/server/lib/prosody/config.ts @@ -18,6 +18,7 @@ import { getRemoteServerInfosDir } from '../federation/storage' import { BotConfiguration } from '../configuration/bot' import { debugMucAdmins } from '../debug' import { ExternalAuthOIDC } from '../external-auth/oidc' +import { listModFirewallFiles } from '../firewall/config' async function getWorkingDir (options: RegisterServerOptions): Promise { const peertubeHelpers = options.peertubeHelpers @@ -102,14 +103,23 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise() @@ -356,7 +368,9 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise { await initPromoteApiRouter(options, router) await initEmojisRouter(options, router) + await initAdminFirewallApiRouter(options, router) + if (isDebugMode(options)) { // Only add this route if the debug mode is enabled at time of the server launch. // Note: the isDebugMode will be tested again when the API is called. diff --git a/server/lib/routers/api/admin/firewall.ts b/server/lib/routers/api/admin/firewall.ts new file mode 100644 index 00000000..502d2738 --- /dev/null +++ b/server/lib/routers/api/admin/firewall.ts @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { RegisterServerOptions } from '@peertube/peertube-types' +import type { Router, Request, Response, NextFunction } from 'express' +import type { AdminFirewallConfiguration } from '../../../../../shared/lib/types' +import { asyncMiddleware, RequestPromiseHandler } from '../../../middlewares/async' +import { checkUserIsAdminMiddleware } from '../../../middlewares/is-admin' +import { + getModFirewallConfig, sanitizeModFirewallConfig, saveModFirewallConfig, canEditFirewallConfig +} from '../../../firewall/config' +import { getProsodyFilePaths, writeProsodyConfig } from '../../../prosody/config' +import { reloadProsody } from '../../../prosody/ctl' + +function canEditFirewallConfigMiddleware (options: RegisterServerOptions): RequestPromiseHandler { + return async (req: Request, res: Response, next: NextFunction) => { + if (!await canEditFirewallConfig(options)) { + options.peertubeHelpers.logger.info('Firewall configuration editing is disabled') + res.sendStatus(403) + return + } + next() + } +} + +async function initAdminFirewallApiRouter (options: RegisterServerOptions, router: Router): Promise { + const logger = options.peertubeHelpers.logger + + router.get('/admin/firewall', asyncMiddleware([ + checkUserIsAdminMiddleware(options), + canEditFirewallConfigMiddleware(options), + async (req: Request, res: Response, _next: NextFunction): Promise => { + try { + const prosodyPaths = await getProsodyFilePaths(options) + const result: AdminFirewallConfiguration = await getModFirewallConfig(options, prosodyPaths.modFirewallFiles) + res.status(200) + res.json(result) + } catch (err) { + options.peertubeHelpers.logger.error(err) + res.sendStatus(500) + } + } + ])) + + router.post('/admin/firewall', asyncMiddleware([ + checkUserIsAdminMiddleware(options), + canEditFirewallConfigMiddleware(options), + async (req: Request, res: Response, _next: NextFunction): Promise => { + try { + const prosodyPaths = await getProsodyFilePaths(options) + + let data: AdminFirewallConfiguration + try { + data = await sanitizeModFirewallConfig(options, req.body) + } catch (err) { + logger.error(err) + res.sendStatus(400) + return + } + + await saveModFirewallConfig(options, prosodyPaths.modFirewallFiles, data) + + logger.info('Just saved a new mod_firewall const, must rewrite Prosody configuration file, and reload Prosody.') + await writeProsodyConfig(options) + await reloadProsody(options) + + const result: AdminFirewallConfiguration = await getModFirewallConfig(options, prosodyPaths.modFirewallFiles) + res.status(200) + res.json(result) + } catch (err) { + options.peertubeHelpers.logger.error(err) + res.sendStatus(500) + } + } + ])) +} + +export { + initAdminFirewallApiRouter +} diff --git a/server/lib/routers/api/room.ts b/server/lib/routers/api/room.ts index ec2d0da8..a3efbad6 100644 --- a/server/lib/routers/api/room.ts +++ b/server/lib/routers/api/room.ts @@ -39,6 +39,7 @@ interface RoomDefaults { mute_anonymous?: boolean livechat_muc_terms?: string moderation_delay?: number + anonymize_moderation_actions?: boolean } affiliations?: Affiliations } @@ -54,7 +55,8 @@ async function _getChannelSpecificOptions ( slow_mode_duration: channelOptions.slowMode.duration, mute_anonymous: channelOptions.mute.anonymous, livechat_muc_terms: channelOptions.terms, - moderation_delay: channelOptions.moderation.delay + moderation_delay: channelOptions.moderation.delay, + anonymize_moderation_actions: channelOptions.moderation.anonymize } } diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 3b82fdde..ca45e7a9 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -11,9 +11,10 @@ import { ExternalAuthOIDC, ExternalAuthOIDCType } from './external-auth/oidc' import { Emojis } from './emojis' import { LivechatProsodyAuth } from './prosody/auth' import { loc } from './loc' +import { canEditFirewallConfig } from './firewall/config' const escapeHTML = require('escape-html') -type AvatarSet = 'sepia' | 'cat' | 'bird' | 'fenec' | 'abstract' | 'legacy' | 'nctv' +type AvatarSet = 'sepia' | 'cat' | 'bird' | 'fenec' | 'abstract' | 'legacy' | 'none' | 'nctv' async function initSettings (options: RegisterServerOptions): Promise { const { peertubeHelpers, settingsManager } = options @@ -27,7 +28,7 @@ async function initSettings (options: RegisterServerOptions): Promise { initAdvancedChannelCustomizationSettings(options) initChatBehaviourSettings(options) initThemingSettings(options) - initChatServerAdvancedSettings(options) + await initChatServerAdvancedSettings(options) await ExternalAuthOIDC.initSingletons(options) const loadOidcs = (): void => { @@ -511,7 +512,8 @@ function initThemingSettings ({ registerSetting }: RegisterServerOptions): void { value: 'bird', label: loc('avatar_set_option_bird') }, { value: 'fenec', label: loc('avatar_set_option_fenec') }, { value: 'abstract', label: loc('avatar_set_option_abstract') }, - { value: 'legacy', label: loc('avatar_set_option_legacy') } + { value: 'legacy', label: loc('avatar_set_option_legacy') }, + { value: 'none', label: loc('avatar_set_option_none') } ] as Array<{ value: AvatarSet label: string @@ -525,9 +527,9 @@ function initThemingSettings ({ registerSetting }: RegisterServerOptions): void default: 'peertube' as ConverseJSTheme, private: false, options: [ - { value: 'peertube', label: loc('peertube') }, - { value: 'default', label: loc('default') }, - { value: 'concord', label: loc('concord') } + { value: 'peertube', label: loc('converse_theme_option_peertube') }, + { value: 'default', label: loc('converse_theme_option_default') }, + { value: 'cyberpunk', label: loc('converse_theme_option_cyberpunk') } ] as Array<{value: ConverseJSTheme, label: string}>, descriptionHTML: loc('converse_theme_description') }) @@ -555,7 +557,9 @@ function initThemingSettings ({ registerSetting }: RegisterServerOptions): void * Registers settings related to the "Chat server advanded settings" section. * @param param0 server options */ -function initChatServerAdvancedSettings ({ registerSetting }: RegisterServerOptions): void { +async function initChatServerAdvancedSettings (options: RegisterServerOptions): Promise { + const { registerSetting } = options + registerSetting({ name: 'prosody-advanced', type: 'html', @@ -723,6 +727,23 @@ function initChatServerAdvancedSettings ({ registerSetting }: RegisterServerOpti private: true, descriptionHTML: loc('prosody_components_list_description') }) + + registerSetting({ + name: 'prosody-firewall-enabled', + label: loc('prosody_firewall_label'), + type: 'input-checkbox', + default: false, + private: true, + descriptionHTML: loc('prosody_firewall_description') + }) + if (await canEditFirewallConfig(options)) { + registerSetting({ + type: 'html', + name: 'prosody-firewall-configure-button', + private: true, + descriptionHTML: loc('prosody_firewall_configure_button') + }) + } } export { diff --git a/shared/lib/admin-firewall.ts b/shared/lib/admin-firewall.ts new file mode 100644 index 00000000..36d4fabf --- /dev/null +++ b/shared/lib/admin-firewall.ts @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +// Note: API request body size is limited to 100Kb (expressjs body-parser defaut limit, and Peertube nginx config). +// So we must be sure to never send more than 100Kb. +// All files are sent in one JSON object. +export const maxFirewallFileSize: number = 3 * 1024 +export const maxFirewallFiles = 20 + +export const maxFirewallNameLength = 20 +export const firewallNameRegexp = /^[a-zA-Z0-9_-]+$/ diff --git a/shared/lib/types.ts b/shared/lib/types.ts index a827efb7..9d3915da 100644 --- a/shared/lib/types.ts +++ b/shared/lib/types.ts @@ -13,7 +13,7 @@ interface ActorImage { updatedAt: Date | string } -type ConverseJSTheme = 'peertube' | 'default' | 'concord' +type ConverseJSTheme = 'peertube' | 'default' | 'cyberpunk' interface InitConverseJSParams { peertubeVideoOriginalUrl?: string @@ -109,6 +109,7 @@ interface ChannelConfigurationOptions { terms?: string // comes with Livechat 10.2.0 moderation: { // comes with Livechat 10.3.0 delay: number + anonymize: boolean // comes with Livechat 11.0.0 } } @@ -191,6 +192,17 @@ interface LivechatToken { date: number } +interface AdminFirewallConfigurationFile { + name: string + content: string + enabled: boolean +} + +interface AdminFirewallConfiguration { + enabled: boolean + files: AdminFirewallConfigurationFile[] +} + export type { ConverseJSTheme, InitConverseJSParams, @@ -211,5 +223,7 @@ export type { ChannelEmojis, ChannelEmojisConfiguration, ProsodyAuthentInfos, - LivechatToken + LivechatToken, + AdminFirewallConfiguration, + AdminFirewallConfigurationFile } diff --git a/support/documentation/config.toml b/support/documentation/config.toml index b2cda28e..089114b8 100644 --- a/support/documentation/config.toml +++ b/support/documentation/config.toml @@ -6,21 +6,20 @@ baseURL = "http://localhost:1313/peertube-plugin-livechat/" # this will be overr languageCode = "en-us" defaultContentLanguage = "en" title = "Peertube plugin livechat" -theme = 'hugo-theme-learn' +theme = 'hugo-theme-relearn' [outputs] -home = [ "HTML", "RSS", "JSON"] +home = [ "HTML", "RSS", "SEARCH"] [params] - author = "John Livingston" - description = "Peertube plugin livechat documentation" showVisitedLinks = true disableSearch = false disableLandingPageButton = false + landingPageName = " Livechat" themeVariant = "red" - landingPageURL = "/peertube-plugin-livechat/" - landingPageName = " Home" - custom_css = ["css/livechatdoc.css"] + + [params.author] + name = "John Livingston" [[menu.shortcuts]] name = " Github repository" @@ -34,187 +33,185 @@ weight = 20 [Languages] [Languages.en] + weight = 1 title = "Peertube plugin livechat documentation" languageName = "English" contentDir = "content/en" [Languages.ar] + weight = 2 languageName = "العربية" - landingPageURL = "/peertube-plugin-livechat/ar/" contentDir = "content/translations/ar" [Languages.ca] + weight = 3 languageName = "Català" - landingPageURL = "/peertube-plugin-livechat/ca/" contentDir = "content/translations/ca" [Languages.cs] + weight = 4 languageName = "Čeština" - landingPageURL = "/peertube-plugin-livechat/cs/" contentDir = "content/translations/cs" [Languages.de] + weight = 5 title = "Peertube Plugin Livechat Dokumentation" languageName = "Deutsch" - landingPageName = " Home" - landingPageURL = "/peertube-plugin-livechat/de/" contentDir = "content/translations/de" [Languages.el] + weight = 6 languageName = "ελληνικά" - landingPageURL = "/peertube-plugin-livechat/el/" contentDir = "content/translations/el" [Languages.eo] + weight = 7 languageName = "Esperanto" - landingPageURL = "/peertube-plugin-livechat/eo/" contentDir = "content/translations/eo" [Languages.es] + weight = 8 languageName = "Español" - landingPageURL = "/peertube-plugin-livechat/es/" contentDir = "content/translations/es" [Languages.eu] + weight = 9 languageName = "Euskara" - landingPageURL = "/peertube-plugin-livechat/eu/" contentDir = "content/translations/eu" [Languages.fa] + weight = 10 languageName = "فارسی" - landingPageURL = "/peertube-plugin-livechat/fa/" contentDir = "content/translations/fa" [Languages.fi] + weight = 11 languageName = "Suomi" - landingPageURL = "/peertube-plugin-livechat/fi/" contentDir = "content/translations/fi" [Languages.fr] + weight = 12 title = "Documentation plugin Peertube livechat" languageName = "Français" - landingPageName = " Accueil" - landingPageURL = "/peertube-plugin-livechat/fr/" contentDir = "content/translations/fr" [Languages.gd] + weight = 13 languageName = "Gàidhlig" - landingPageURL = "/peertube-plugin-livechat/gd/" contentDir = "content/translations/gd" [Languages.gl] + weight = 14 languageName = "Galego" - landingPageURL = "/peertube-plugin-livechat/gl/" contentDir = "content/translations/gl" [Languages.hr] + weight = 15 languageName = "Hrvatski" - landingPageURL = "/peertube-plugin-livechat/hr/" contentDir = "content/translations/hr" [Languages.hu] + weight = 16 languageName = "Magyar" - landingPageURL = "/peertube-plugin-livechat/hu/" contentDir = "content/translations/hu" [Languages.is] + weight = 17 languageName = "Íslenska" - landingPageURL = "/peertube-plugin-livechat/is/" contentDir = "content/translations/is" [Languages.it] + weight = 18 languageName = "Italiano" - landingPageURL = "/peertube-plugin-livechat/it/" contentDir = "content/translations/it" [Languages.ja] + weight = 19 title = "PeerTube ライブチャットプラグイン" languageName = "日本語バージョン" - landingPageName = " Home" - landingPageURL = "/peertube-plugin-livechat/ja/" contentDir = "content/translations/ja" [Languages.kab] + weight = 20 languageName = "Taqbaylit" - landingPageURL = "/peertube-plugin-livechat/ka/" contentDir = "content/translations/kab" [Languages.nb] + weight = 21 languageName = "Norsk bokmål" - landingPageURL = "/peertube-plugin-livechat/nb/" contentDir = "content/translations/nb" [Languages.nl] + weight = 22 languageName = "Nederlands" - landingPageURL = "/peertube-plugin-livechat/nl/" contentDir = "content/translations/nl" [Languages.nn] + weight = 23 languageName = "Norsk nynorsk" - landingPageURL = "/peertube-plugin-livechat/nn/" contentDir = "content/translations/nn" [Languages.oc] + weight = 24 languageName = "Occitan" - landingPageURL = "/peertube-plugin-livechat/oc/" contentDir = "content/translations/oc" [Languages.pl] + weight = 25 languageName = "Polski" - landingPageURL = "/peertube-plugin-livechat/pl/" contentDir = "content/translations/pl" [Languages.pt] + weight = 26 languageName = "uguês" - landingPageURL = "/peertube-plugin-livechat/pt/" contentDir = "content/translations/pt" [Languages.ru] + weight = 27 languageName = "Pусский" - landingPageURL = "/peertube-plugin-livechat/ru/" contentDir = "content/translations/ru" [Languages.sq] + weight = 28 languageName = "Shqip" - landingPageURL = "/peertube-plugin-livechat/sq/" contentDir = "content/translations/sq" [Languages.sv] + weight = 29 languageName = "Svenska" - landingPageURL = "/peertube-plugin-livechat/sv/" contentDir = "content/translations/sv" [Languages.th] + weight = 30 languageName = "ไทย" - landingPageURL = "/peertube-plugin-livechat/th/" contentDir = "content/translations/th" [Languages.tr] + weight = 31 languageName = "Türkçe" - landingPageURL = "/peertube-plugin-livechat/tr/" contentDir = "content/translations/tr" [Languages.tok] + weight = 32 languageName = "Toki Pona" - landingPageURL = "/peertube-plugin-livechat/to/" contentDir = "content/translations/tok" [Languages.uk] + weight = 33 languageName = "украї́нська мо́ва" - landingPageURL = "/peertube-plugin-livechat/uk/" contentDir = "content/translations/uk" [Languages.vi] + weight = 34 languageName = "Tiếng Việt" - landingPageURL = "/peertube-plugin-livechat/vi/" contentDir = "content/translations/vi" [Languages.zh-Hans] + weight = 35 languageName = "简体中文(中国)" - landingPageURL = "/peertube-plugin-livechat/zh-Hans/" contentDir = "content/translations/zh-Hans" [Languages.zh-Hant] + weight = 36 languageName = "繁體中文(台灣" - landingPageURL = "/peertube-plugin-livechat/zh-Hant/" contentDir = "content/translations/zh-Hant" diff --git a/support/documentation/content/en/_index.md b/support/documentation/content/en/_index.md index 6d762580..3951b333 100644 --- a/support/documentation/content/en/_index.md +++ b/support/documentation/content/en/_index.md @@ -1,11 +1,14 @@ -# PeerTube plugin livechat +--- +title: "Peertube livechat" +description: "Peertube plugin livechat documentation" +--- {{% notice tip %}} You can use the language selector in the left menu to view this documentation in different languages. Some translations are missing or incomplete. In this case, you'll see the English version of the text. {{% /notice %}} -![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px "Chat screenshot") Welcome the **Peertube Livechat Plugin** documentation. diff --git a/support/documentation/content/en/contributing/document/_index.md b/support/documentation/content/en/contributing/document/_index.md index 69cb336a..1bc429f8 100644 --- a/support/documentation/content/en/contributing/document/_index.md +++ b/support/documentation/content/en/contributing/document/_index.md @@ -17,7 +17,10 @@ The documentation source code is in the `support/documentation/content` folder. The documentation is generated using [Hugo](https://gohugo.io/). You have to install it on your computer if you want to preview your work. -The used theme is [hugo-theme-learn](https://learn.netlify.app/). +The minimum required version for Hugo is 0.121.0. +It was tested using version 0.132.2. + +The used theme is [hugo-theme-relearn](https://mcshelby.github.io/hugo-theme-relearn). You should read its documentation before starting editing the documentation. When a new plugin version is released, or when documentation is updated, plugin maintainers will merge the `main` branch to the `documentation` branch. diff --git a/support/documentation/content/en/documentation/admin/advanced/_index.md b/support/documentation/content/en/documentation/admin/advanced/_index.md index 81290c44..b9856d83 100644 --- a/support/documentation/content/en/documentation/admin/advanced/_index.md +++ b/support/documentation/content/en/documentation/admin/advanced/_index.md @@ -1,7 +1,7 @@ --- title: "Advanced usage" description: "Some advanced features" -weight: 20 +weight: 40 chapter: false --- diff --git a/support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md b/support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md index d8774472..7dbdbae0 100644 --- a/support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md +++ b/support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -113,7 +113,7 @@ You must then place these certificates in a folder that will be accessible to th {{% notice tip %}} If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing "xxx" with the arguments you wish to pass to -prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx` +prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx` {{% /notice %}} The plugin will check once a day to see if any files have been modified in this folder, and reload Prosody if necessary. diff --git a/support/documentation/content/en/documentation/admin/external_auth.md b/support/documentation/content/en/documentation/admin/external_auth.md index c6c3ca42..8bc6424f 100644 --- a/support/documentation/content/en/documentation/admin/external_auth.md +++ b/support/documentation/content/en/documentation/admin/external_auth.md @@ -14,9 +14,9 @@ Such "external account users" will be easier to moderate than anonymous accounts This also allows user to join the chat without creating Peertube account (in case your instance has closed registration for example, or without waiting for account approval). -![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a "{{% livechat_label login_using_external_account %}}" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px "{{% livechat_label login_using_external_account %}} button") -![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px) +![Screenshot of a dialog with an "OpenID Connect" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px "External login dialog - OpenID Connect") This page will describe available authentication methods. @@ -49,7 +49,7 @@ You will now have to fill some settings. This is the button label in the following screenshot: -![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px) +![Screenshot of a dialog with an "OpenID Connect" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px "External login dialog - OpenID Connect") For now, it is not possible to localize this label. diff --git a/support/documentation/content/en/documentation/admin/mod_firewall.md b/support/documentation/content/en/documentation/admin/mod_firewall.md new file mode 100644 index 00000000..404e3c00 --- /dev/null +++ b/support/documentation/content/en/documentation/admin/mod_firewall.md @@ -0,0 +1,55 @@ +--- +title: "Prosody mod_firewall" +description: "Advanced firewall rules for the Prosody server" +weight: 30 +chapter: false +--- + +{{% notice info %}} +This feature comes with the livechat plugin version 11.0.0. +{{% /notice %}} + +You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server. + +Doing so, Peertube admins will be able to define advanced firewall rules. + +{{% notice warning %}} +These rules could be used to run arbitrary code on the server. +If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). +This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. +You can still use mod_firewall by editing files directly on the server. +{{% /notice %}} + +## Edit rules + +First, you must enable the feature in the [plugin settings](/peertube-plugin-livechat/documentation/admin/settings). + +Just bellow the settings, you will find a "Configure mod_firewall" button. +This button will open a configuration page. + +![Screenshot of the "{{% livechat_label prosody_firewall_configuration %}}" form.](/peertube-plugin-livechat/images/mod_firewall.png?classes=shadow,border&height=400px "{{% livechat_label prosody_firewall_configuration %}}") + +Here you can add several configuration files. + +You can enable/disable each files. + +Files will be loaded in the alphabetical order. +You can use a number as prefix to easily choose the order. + +{{% notice info %}} +You can also edit these firewall rules directly on the server, in the `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` directory. +File names must only contains alphanumerical characters, underscores and hyphens. +The extension must be `.pfw`, or `.pfw.disabled` if you want to disable a file. +Please be sure that the peertube system user has write access to these files, else the web editing interface will fail. +Once you have edited these files, you must reload prosody. +This can be done by saving the plugin settings, or saving the mod_firewall configuration in the web interface, or by restarting Peertube. +{{% /notice %}} + +When you save the configuration, the server will automatically reload it, and your rules will apply immediatly. +You can check that there is no parsing error in the Prosody error log. +To do so, you can read the `plugins/data/peertube-plugin-livechat/prosody/prosody.err` file, or use the [diagnostic tool](/peertube-plugin-livechat/documentation/installation/troubleshooting/) that will show last Prosody errors. + +## Examples + +Don't hesitate to share your rules. +To do so, you can for example edit this [page](/peertube-plugin-livechat/contributing/document/#write-documentation). diff --git a/support/documentation/content/en/documentation/admin/settings.md b/support/documentation/content/en/documentation/admin/settings.md index b6167453..a778bd15 100644 --- a/support/documentation/content/en/documentation/admin/settings.md +++ b/support/documentation/content/en/documentation/admin/settings.md @@ -120,27 +120,27 @@ You can choose from several different sets the default avatars that will be used {{% livechat_label avatar_set_option_sepia %}}: [David Revoy's Peertube avatar generator](https://www.peppercarrot.com/extras/html/2023_peertube-generator/), [CC-By](https://creativecommons.org/licenses/by/4.0/) license -![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px) +![Sepia avatar example](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px "Sepia") {{% livechat_label avatar_set_option_cat %}}: [David Revoy's cat avatar generator](https://www.peppercarrot.com/extras/html/2016_cat-generator/), [CC-By](https://creativecommons.org/licenses/by/4.0/) license -![Cats](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px) +![Cats avatar example](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px "Cats") {{% livechat_label avatar_set_option_bird %}}: [David Revoy's bird avatar generator](https://www.peppercarrot.com/extras/html/2019_bird-generator/), [CC-By](https://creativecommons.org/licenses/by/4.0/) license -![Birds](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px) +![Birds avatar example](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px "Birds") {{% livechat_label avatar_set_option_fenec %}}: [David Revoy's fenec/mobilizon avatar generator](https://www.peppercarrot.com/extras/html/2020_mobilizon-generator/), [CC-By](https://creativecommons.org/licenses/by/4.0/) license -![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px) +![Fenecs avatar example](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px "Fenecs") {{% livechat_label avatar_set_option_abstract %}}: [David Revoy's Abstract avatar generator](https://www.peppercarrot.com/extras/html/2017_abstract-generator/index.php), [CC-By](https://creativecommons.org/licenses/by/4.0/) license -![Abstracts](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px) +![Abstracts avatar example](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px "Abtracts") {{% livechat_label avatar_set_option_legacy %}}: Based on [David Revoy' work](https://www.davidrevoy.com), [AGPL-v3](https://www.gnu.org/licenses/agpl-3.0.en.html) license -![Legacy](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px) +![Legacy avatar example](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px "Legacy") If you can't see the change immediatly, it could be because of your browser cache. Just clear your browser session storage, or restart it. @@ -150,7 +150,7 @@ You can choose which theme to use for ConverseJS: - Peertube theme: this is a special theme, made especially for peertube's integration. - Default ConverseJS theme: this is the default ConverseJS theme. -- ConverseJS concord theme: this is a theme provided by ConverseJS. +- ConverseJS cyberpunk theme: this is a theme provided by ConverseJS. ### {{% livechat_label autocolors_label %}} @@ -254,3 +254,9 @@ More informations on Prosody external components [here](https://prosody.im/doc/c #### {{% livechat_label prosody_components_list_label %}} {{% livechat_label prosody_components_list_description %}} + +### {{% livechat_label prosody_firewall_label %}} + +You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server. + +For more information, please check [the documentation](/peertube-plugin-livechat/documentation/admin/mod_firewall/). diff --git a/support/documentation/content/en/documentation/installation/troubleshooting.md b/support/documentation/content/en/documentation/installation/troubleshooting.md index cbf8527a..348a878e 100644 --- a/support/documentation/content/en/documentation/installation/troubleshooting.md +++ b/support/documentation/content/en/documentation/installation/troubleshooting.md @@ -15,11 +15,11 @@ If the chat does not work, there is a diagnostic tool in the plugin's settings p Open the plugin settings, and click on the "launch diagnostic" button. -![Launch diagnostic](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px) +![Screenshot of the plugin's settings page, with a "launch diagnostic" button.](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px "Launch diagnostic") If there is any error in the diagnostic page, you can search in this page for a solution, or refer to the [Bug tracking documentation page](/peertube-plugin-livechat/issues/) if you can't find any response. -![Diagnostic result](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px) +![Screenshot of the diagnostic result page. This gives a lot of information, with status for different test suites.](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px "Diagnostic result") ## Chat does not load diff --git a/support/documentation/content/en/documentation/user/obs.md b/support/documentation/content/en/documentation/user/obs.md index e08f19b0..ec0560ab 100644 --- a/support/documentation/content/en/documentation/user/obs.md +++ b/support/documentation/content/en/documentation/user/obs.md @@ -12,18 +12,18 @@ In the current page, you will find some advices to handle your live chats using You can easily include the chat in your video stream. -![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px "Embeding the chat in a live stream") You can use the "{{% livechat_label share_chat_link %}}" feature to generate an URL to your chat. This button should be near the chat if you are the video owner (unless it was desactivated by your server admins). Check the "{{% livechat_label read_only %}}" checkbox in the modal. -![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, where the "{{% livechat_label read_only %}}" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px "Share link popup") Then use this link as a "web browser source" in OBS. -![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px) +![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px "Embeding the chat in OBS") You can use the "{{% livechat_label transparent_background %}}" option to have a transparent background in OBS. If you want to customize the background transparency, you can add this CSS in your OBS browser source's settings: @@ -56,17 +56,17 @@ The livechat plugin offers a way to create long term token that can identify you To do so, just use the "{{% livechat_label share_chat_link %}}" feature, and open the "{{% livechat_label share_chat_dock %}}" tab. From there, you can create a new token using the "+" button. -![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, on the "{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable."](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px "Share link popup - dock tab") Then, copy the url, and use the "Docks / Custom browser docks" menu from your OBS to add a dock with this URL. -![OBS - Dock menu](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px) +![Screenshot of the OBS Dock menu, with a "Custom Browser Docks" entry.](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px "OBS - Dock menu") -![OBS - Dock dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px) +![Screenshot of the OBS Custom Browser Docks dialog, with a new dock called "My chat".](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px "OBS - Dock dialog") Once you have done, you will have a new dock connected to the chat with your account. -![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px) +![Screenshot of OBS with a new dock including the chat. The user is logged in with their Peertube account, and can chat directly from OBS.](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px "OBS - Dock") {{% notice tip %}} Tokens are valid to join any chat room. You don't have to generate separate tokens for each of your rooms. diff --git a/support/documentation/content/en/documentation/user/streamers/basics.md b/support/documentation/content/en/documentation/user/streamers/basics.md index 3114f059..79f42c24 100644 --- a/support/documentation/content/en/documentation/user/streamers/basics.md +++ b/support/documentation/content/en/documentation/user/streamers/basics.md @@ -14,12 +14,12 @@ Information in this section are only true in the default case. When you create or modify a Peertube live, there is a "plugin settings" tab: -![New live](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px) +![Screenshot of the Peertube new live form.](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px "New live") In the "plugin settings" tab, there is a "{{% livechat_label use_chat %}}" checkbox. Just check or uncheck it to enable or disable the chat associated to your video. -![Activate the chat](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px) +![Screenshot of the form, with a "{{% livechat_label use_chat %}}" checkbox.](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px "Activate the chat") {{% notice tip %}} There can be other settings in this tab, depending on plugins installed on your Peertube instance. @@ -39,7 +39,7 @@ This url can be shared. The "{{% livechat_label share_chat_embed %}}" tab provide some links to embed the chat in websites, or in your live stream. -![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, where the "{{% livechat_label read_only %}}" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px "Share link popup") You can customize some options: @@ -49,19 +49,19 @@ You can customize some options: For more information on the "{{% livechat_label share_chat_dock %}}" tab, check the [OBS documentation](/peertube-plugin-livechat/documentation/user/obs). -![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, on the "{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable."](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px "Share link popup - dock tab") In the "{{% livechat_label web %}}" tab, the provided url opens the chat in the Peertube interface. You can share this link to other users to invite them to join the chat. -![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, on the "{{% livechat_label web %}} tab. There is a url you can copy.](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px "Share link popup - web tab") The "{{% livechat_label share_chat_link %}}" popup can also contain a "{{% livechat_label connect_using_xmpp %}}" tab. This will only be available if your instance's administators have enabled an correctly configured this option. Using this option, you can provide a link to join the chat using any [XMPP client software](https://en.wikipedia.org/wiki/XMPP#Clients). Using such softwares can for example facilitate moderation actions. -![Share link popup - xmpp tab](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, on the "{{% livechat_label connect_using_xmpp %}}" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px "{{% livechat_label connect_using_xmpp %}}") ## Moderation @@ -80,11 +80,11 @@ User joining will see messages posted before their arrival. You can change the persistence behaviour. [Open the chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), and click on "Configure". -![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px) +![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px "Chat menu") There are several options that can be changed. -![Configure chat room](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px) +![Screenshot of the chat configuration form.](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px "Configure chat room") You can for example set the default and maximum number of messages to return to 0, so that new incomers won't see any previously sent message. diff --git a/support/documentation/content/en/documentation/user/streamers/bot/_index.md b/support/documentation/content/en/documentation/user/streamers/bot/_index.md index ffd81d45..d81031d3 100644 --- a/support/documentation/content/en/documentation/user/streamers/bot/_index.md +++ b/support/documentation/content/en/documentation/user/streamers/bot/_index.md @@ -12,7 +12,7 @@ This feature comes with the livechat plugin version 8.0.0, and can be disabled b You can enable a chat bot on your chatrooms. The bot configuration is made channel per channel, and will apply to all related videos' chatrooms. -![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px) +![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px "Channel configuration") To access this page, check the [channel configuration documentation](/peertube-plugin-livechat/documentation/user/streamers/channel). diff --git a/support/documentation/content/en/documentation/user/streamers/bot/commands.md b/support/documentation/content/en/documentation/user/streamers/bot/commands.md index e80d8bb7..c9a2f773 100644 --- a/support/documentation/content/en/documentation/user/streamers/bot/commands.md +++ b/support/documentation/content/en/documentation/user/streamers/bot/commands.md @@ -7,7 +7,7 @@ chapter: false {{% livechat_label livechat_configuration_channel_command_desc %}} -![Commands configuration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px) +![Screenshot of the channel options page, with some fields to configure the bot commands.](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px "Commands configuration") You can setup several commands. @@ -15,6 +15,6 @@ You can setup several commands. {{% livechat_label livechat_configuration_channel_command_cmd_desc %}} -## {{% livechat_label livechat_configuration_channel_quote_delay_label %}} +## {{% livechat_label livechat_configuration_channel_command_message_label %}} -{{% livechat_label livechat_configuration_channel_quote_delay_desc %}} +{{% livechat_label livechat_configuration_channel_command_message_desc %}} diff --git a/support/documentation/content/en/documentation/user/streamers/bot/forbidden_words.md b/support/documentation/content/en/documentation/user/streamers/bot/forbidden_words.md index b40a875b..b8fd0ff1 100644 --- a/support/documentation/content/en/documentation/user/streamers/bot/forbidden_words.md +++ b/support/documentation/content/en/documentation/user/streamers/bot/forbidden_words.md @@ -7,9 +7,9 @@ chapter: false {{% livechat_label livechat_configuration_channel_forbidden_words_desc %}} -![Forbidden words configuration](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px) +![Screenshot of the channel options page, with several fields to configure the forbidden words.](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px "Forbidden words configuration") -![Deleted message](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px) +![Screenshot of a chat message that was deleted, with the following reason: "No url allowed".](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px "Deleted message") You can fill several "{{% livechat_label livechat_configuration_channel_forbidden_words_label %}}" fields. When a user sends a message that match the configured criteria, the message will automatically be deleted. @@ -35,6 +35,10 @@ See the [contribution guide](/peertube-plugin-livechat/contributing/) for more i These words are case insensitive. {{% /notice %}} +{{% notice tip %}} +You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them. +{{% /notice %}} + {{% notice warning %}} This features is still experimental. There might be some issues with non-latin alphabets. diff --git a/support/documentation/content/en/documentation/user/streamers/bot/quotes.md b/support/documentation/content/en/documentation/user/streamers/bot/quotes.md index f41ee149..6c384b62 100644 --- a/support/documentation/content/en/documentation/user/streamers/bot/quotes.md +++ b/support/documentation/content/en/documentation/user/streamers/bot/quotes.md @@ -11,7 +11,7 @@ chapter: false If there is no user in the chatroom, the bot won't send any message. {{% /notice %}} -![Timers configuration](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px) +![Screenshot of the channel options page, with some fields to configure a new timer.](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px "Timers configuration") ## {{% livechat_label livechat_configuration_channel_quote_label %}} diff --git a/support/documentation/content/en/documentation/user/streamers/channel.md b/support/documentation/content/en/documentation/user/streamers/channel.md index a36079f7..3a4d5ed8 100644 --- a/support/documentation/content/en/documentation/user/streamers/channel.md +++ b/support/documentation/content/en/documentation/user/streamers/channel.md @@ -11,12 +11,12 @@ This feature comes with the livechat plugin version 8.0.0, and can be disabled b In the Peertube left menu, there is a "{{% livechat_label menu_configuration_label %}}" entry: -![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px) +![Screenshot of the chatrooms configuration page. The page list the user's channels.](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px "Chatrooms menu") This "{{% livechat_label menu_configuration_label %}}" link takes you to a list of your channels. By clicking on a channel, you will then be able to setup some options for your channels: -![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px) +![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px "Channel configuration") Here you can configure: diff --git a/support/documentation/content/en/documentation/user/streamers/emojis.md b/support/documentation/content/en/documentation/user/streamers/emojis.md index ed9336a0..f0119060 100644 --- a/support/documentation/content/en/documentation/user/streamers/emojis.md +++ b/support/documentation/content/en/documentation/user/streamers/emojis.md @@ -15,11 +15,11 @@ Streamers can add custom emojis to their channels. On the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel), open the "{{% livechat_label livechat_configuration_channel_emojis_title %}}" tab: -![Channel configuration / Channel emojis configuration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px) +![Screenshot of the emoji configuration page. There is a form where you can add new emojis.](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px "Channel configuration / Channel emojis configuration") {{% livechat_label livechat_configuration_channel_emojis_desc %}} -![Channel configuration / Channel emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px) +![Screenshot of a chat session, with messages containing custom emojis. The emoji picker is open, and shows custom emojis.](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px "Channel configuration / Channel emojis") {{% livechat_label livechat_emojis_shortname_desc %}} diff --git a/support/documentation/content/en/documentation/user/streamers/moderation.md b/support/documentation/content/en/documentation/user/streamers/moderation.md index eb3c0e9a..7881553a 100644 --- a/support/documentation/content/en/documentation/user/streamers/moderation.md +++ b/support/documentation/content/en/documentation/user/streamers/moderation.md @@ -23,7 +23,7 @@ Check [the chat bot documentation](/peertube-plugin-livechat/documentation/user/ You can access room settings and moderation tools using the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers) at the top of the chat. -![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px) +![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px "Chat menu") {{% notice tip %}} The video owner will be owner of the chat room. @@ -50,11 +50,11 @@ You can prevent anonymous users to send messages. In such case, only registered To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the "configure" menu. In the form, you will find a "{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}" checkbox. -![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px) +![Screenshot of the room configuration form. There is a "{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}" checkbox.](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px "Room configuration / Mute anonymous users") Anonymous users won't have the message field, and will see following prompt: "{{% livechat_label muted_anonymous_message %}}" -![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px) +![Screenshot of a chat session. The current user has no message field. There is a message: "{{% livechat_label muted_anonymous_message %}}"](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px "Room configuration / Muted anonymous users") When this feature is enabled, anonymous users will be assigned the "visitor" role. You can change their role to "participant" if you want to allow some of them to talk. @@ -73,6 +73,47 @@ This section is still incomplete. You can promote users as moderators, if you need some help. +## {{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}} + +{{% notice info %}} +This feature comes with the livechat plugin version 11.0.0. +{{% /notice %}} + +It is possible to anonymize moderation actions, to avoid disclosing who is banning/kicking/… occupants. + +To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the "configure" menu. +In the form, you will find a "{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}" checkbox. + +You can choose to enable or disable this feature for new chatrooms on the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel). + +## Participant message history search + +{{% notice info %}} +This feature comes with the livechat plugin version 11.0.0. +{{% /notice %}} + +As a room admin or owner, you can search all messages sent by a given participant. + +To do so, you have several ways: + +* using the "{{% livechat_label search_occupant_message %}}" action in the dropdown menu besides participants in the sidebar +* using the "{{% livechat_label search_occupant_message %}}" action in the dropdown menu besides chat messages + +![Screenshot of a chat session. The moderator has open the message menu, and there is a "{{% livechat_label search_occupant_message %}}" button.](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px "Message history search") + +{{% notice tip %}} +To have more space and better readability, open the chat in full-page mode. +{{% /notice %}} + +In the search results, there are several informations that are shown at the right of the participant nickname: + +* if the current nickname is different than the nickname when the participant has sent the message, the original nickname will be shown +* you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant +* you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant + +The search result will also include all messages related to participants who had the same nickname. +You can differenciate them by comparing [JID](https://xmpp.org/extensions/xep-0029.html) and [occupant-id](https://xmpp.org/extensions/xep-0421.html). + ## Delete room content You can delete old rooms: join the room, and use the menu on the top to destroy the room. diff --git a/support/documentation/content/en/documentation/user/streamers/moderation_delay.md b/support/documentation/content/en/documentation/user/streamers/moderation_delay.md index 3033b5cc..3c6718c9 100644 --- a/support/documentation/content/en/documentation/user/streamers/moderation_delay.md +++ b/support/documentation/content/en/documentation/user/streamers/moderation_delay.md @@ -22,7 +22,7 @@ Please note that messages sent by moderators will also be delayed, to avoid them On the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel), you can set the "{{% livechat_label moderation_delay %}}" option: -![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px) +![Screenshot of the channel option form, with a field to configure the moderation delay.](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px "Channel configuration / Moderation delay") This value will apply as a default value for all your channel's chatrooms. @@ -39,8 +39,12 @@ Currently, this feature has one known bug: users that join the chat will get all However, messages sent after they joined will be delayed correctly. {{% /notice %}} +{{% notice tip %}} +You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them. +{{% /notice %}} + ## In the chat As a moderator, you will see the remaining time (in seconds) before the message is broadcasted, just besides the message datetime. -![Moderation delay timer](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border) +![Screenshot of a chat message. A timer is displayed next to the message datetime. The timer is in seconds.](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border "Moderation delay timer") diff --git a/support/documentation/content/en/documentation/user/streamers/moderation_notes.md b/support/documentation/content/en/documentation/user/streamers/moderation_notes.md new file mode 100644 index 00000000..87cf394e --- /dev/null +++ b/support/documentation/content/en/documentation/user/streamers/moderation_notes.md @@ -0,0 +1,105 @@ +--- +title: "Moderation notes" +description: "Plugin peertube-plugin-livechat moderation notes" +weight: 355 +chapter: false +--- + +{{% notice info %}} +This feature comes with the livechat plugin version 11.0.0. +{{% /notice %}} + +## Introduction + +The livechat plugin includes a Moderator Notes Application: you can write some notes, that could be associated to chat participants. +Every room's admins have access to these notes, so they can edit them collaboratively. + +You can for example use this Application to: + +* share some notes between moderators +* take notes about participants that were kicked or caused troubles +* ... + +## Using the Moderator Notes Application + +### Opening the Moderator Notes Application + +To open the Moderator Notes Application, there is a "{{% livechat_label "moderator_notes" %}}" button in the top chat menu: + +![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a "{{% livechat_label "moderator_notes" %}}" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_video.png?classes=shadow,border&height=200px "Opening the Moderator Notes Application") + +![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a "{{% livechat_label "moderator_notes" %}}" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px "Opening the Moderator Notes Application") + +Clicking this button will toggle the Application display: + +![Screenshot of a Peertube video, with the chat on the right. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px "Moderator Notes Application") + +![Screenshot of a Peertube chat, fullscreen. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px "Moderator Notes Application") + +{{% notice tip %}} +To have more space and better readability, open the chat in full-page mode. +{{% /notice %}} + +### Access rights + +Every room's admins have access to this Application (read and write access). + +When you promote someone as room admin or owner, they gets instant access to this Application. +When you remove admin or owner rights to someone, they instantly lose access to this Application. + +### Scope + +Notes are only available in the room in which you have created them. + +Chatrooms can be releated to video or channel. +If you want to keep notes from one video to another, please consider using rooms associated to channels. + +{{% notice warning %}} +Currently the video vs channel rooms is an instance-wide settings. +Only Peertube admins can change it, and it applies to all chatrooms. +In the future, this choice will be added in your channel's options. +{{% /notice %}} + +### Notes + +#### Create/Edit Notes + +You can use the plus button on the top to create a new note. +You can also edit existing notes using the edit button, or delate any note. + +{{% notice tip %}} +All modification are instantly visible in all your browser tabs, and for all room's admins. +{{% /notice %}} + +You can create a note associated to a participant in several ways: + +* using the "{{% livechat_label moderator_note_create_for_participant %}}" action in the dropdown menu besides participants in the sidebar +* using the "{{% livechat_label moderator_note_create_for_participant %}}" action in the dropdown menu besides chat messages + +When a note is associated to a participant, you will see their nickname and avatar on the top of the note. + +#### Notes filtering + +You can filter notes to find all notes related to a given participant in several ways: + +* click on the "{{% livechat_label moderator_note_search_for_participant %}}" button that is available on notes to find all notes related to the same participant +* click on the "{{% livechat_label moderator_note_search_for_participant %}}" button in the dropdown menu besides participants in the sidebar +* click on the "{{% livechat_label moderator_note_search_for_participant %}}" button in the dropdown menu besides chat messages + +You can remove the filter by clicking on the close button. + +![Screenshot of the note application, with a filter enabled for user "Mike". The only notes that are shown are the notes for the Mike user.](/peertube-plugin-livechat/images/moderation_notes_filters.png?classes=shadow,border&height=200px "Moderator Notes Application - filtering") + +When you filters notes on a participant, there are several informations that are shown at the right of the participant nickname: + +* if the current nickname is different than the nickname when you created the note, the original nickname will be shown +* you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant +* you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant + +The search result will also include all notes related to participants who had the same nickname. +So you can also take note for anonymous users (who don't have any consistent JID or occupant-id). +You can differenciate them by comparing JID and occupant-id. + +#### Sorting notes + +You can sort notes simply using drag & drop. diff --git a/support/documentation/content/en/documentation/user/streamers/polls.md b/support/documentation/content/en/documentation/user/streamers/polls.md index d737a90d..1f6f172f 100644 --- a/support/documentation/content/en/documentation/user/streamers/polls.md +++ b/support/documentation/content/en/documentation/user/streamers/polls.md @@ -13,7 +13,7 @@ This feature comes with the livechat plugin version 10.2.0. You can create a new poll by using the "{{% livechat_label new_poll %}}" action in the chat top menu: -![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px) +![Screenshot of a "{{% livechat_label new_poll %}}" form. The form contains several fields: question, duration, choices, …](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px "Poll form") {{% notice warning %}} This poll feature should not be considered as a reliable voting system. @@ -54,7 +54,7 @@ When the polls starts, a first message will be sent in the chat, from the accoun A banner will also appear to show the poll, and will be updated regularly with the current votes. -![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px) +![Screenshot of a chat session. In the chat, there is a message with the poll question, and the different choices. There is also a banner on the top of the chat, where you can see the question, and the number of votes for each answers.](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px "Poll start") Viewers can then vote by clicking on their choice, or by sending message like "!1" in the chat. @@ -63,7 +63,7 @@ Votes counts will be updated regularly in the banner. Viewers can change their vote at any time, just by making a new choice. Their precedent choice will be replaced by the new one. -![Poll votes](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px) +![Screenshot of a chat session, with an ongoing poll. The current user has just voted by sending "!1".](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px "Poll votes") {{% notice tip %}} Anonymous viewers can only vote once they have choosen their nickname. @@ -79,7 +79,7 @@ But they will see the message in the chat and will be able to vote by sending me When the poll ends, a new message will be sent in the chat, with the results. -![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px) +![Screenshot of a chat session, with poll that has ended. The banner no more accept new votes. There is a message in the chat with the poll results. For each choice, there is the number of votes, and the percentage of the total it represents.](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px "Poll end") {{% notice info %}} The only way to get old polls results is to search for the poll end message in the chat. diff --git a/support/documentation/content/en/documentation/user/streamers/slow_mode.md b/support/documentation/content/en/documentation/user/streamers/slow_mode.md index 8dd43462..577cdc7d 100644 --- a/support/documentation/content/en/documentation/user/streamers/slow_mode.md +++ b/support/documentation/content/en/documentation/user/streamers/slow_mode.md @@ -26,7 +26,7 @@ This limitation does not apply to moderators. On the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel), you can set the slow mode option: -![Channel configuration / Slow Mode](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px) +![Screenshot of the channel options form, with a slow mode field.](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px "Channel configuration / Slow Mode") This value will apply as a default value for all your channel's chatrooms. @@ -40,7 +40,7 @@ To modify the value for an already existing room, just open the room "configurat If the slow mode is enabled, users will be informed by a message. -![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px) +![Screenshot of a chat session. There is a banner on the bottom of the chat, indicating that the slow mode is enabled, and that users can send a message every 2 seconds.](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px "Slow mode infobox") When they send a message, the input field will be disabled for X seconds (where X is the slow mode duration). diff --git a/support/documentation/content/en/documentation/user/streamers/tasks.md b/support/documentation/content/en/documentation/user/streamers/tasks.md index 0c09b75b..05f538fc 100644 --- a/support/documentation/content/en/documentation/user/streamers/tasks.md +++ b/support/documentation/content/en/documentation/user/streamers/tasks.md @@ -26,15 +26,15 @@ You can for example use the Task Application to: To open the Task Application, there is a "{{% livechat_label "tasks" %}}" button in the top chat menu: -![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a "{{% livechat_label tasks %}}" button.](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px "Opening the Task Application") -![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a "{{% livechat_label tasks %}}" button.](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px "Opening the Task Application") Clicking this button will toggle the Task Application display: -![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video, with the chat on the right. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px "Task Application") -![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube chat, fullscreen. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px "Task Application") {{% notice tip %}} To have more space and better readability, open the chat in full-page mode. @@ -56,7 +56,7 @@ You can also edit existing task lists using the edit button, or delete any task Task lists are sorted alphabetically. -![Task lists](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px) +![Screenshot of a chat session, with the Task application. There are several task lists.](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px "Task lists") {{% notice tip %}} All modification are instantly visible in all your browser tabs, and for all room's admins. @@ -69,9 +69,9 @@ All modification are instantly visible in all your browser tabs, and for all roo You can create a task using the button on the right of task lists. This opens a form with two fields: a mandatory task name, and an optional description. -![Task form](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px) +![Screenshot of the task application. Under the first task list, there is a form to create a new task.](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px "Task form") -![Task created](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px) +![Screenshot of the task application. Under the first task list, a new task was created.](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px "Task created") #### Edit tasks @@ -79,15 +79,15 @@ Tasks can be edited by using the edit button on the right. Tasks can be marked complete (or uncomplete) by clicking directly on the checkbox in the list. -![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px) +![Screenshot of the task application. Under task lists, there are several tasks. Some of them are checked, other not.](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px "Tasks") #### Sorting tasks / change task list You can sort tasks, or move tasks from one list to another, simply using drag & drop. -![Drag and drop to sort](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px) +![Screenshot of the task application. There is a task that is dragged over another.](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px "Drag and drop to sort") -![Drag and drop to move to another list](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px) +![Screenshot of the task application. There is a task that is dragged over another task list.](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px "Drag and drop to move to another list") #### Create a task from a chat message @@ -95,10 +95,10 @@ You can create a task from a message in a chat, using the "{{% livechat_label "t This will open a dialog box where you can choose which task list you want to add the task into. The task name will be the user nickname, and the task description the message content. -![Create task from message](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px) +![Screenshot of a chat session. The menu besides a message is open, with a button to create a new task.](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px "Create task from message") -![Choose the task list](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px) +![Screenshot of a dialog, where you can choose in which task list you want to add the new task.](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px "Choose the task list") -![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px) +![Screenshot of the task application. A new task was added in the "chat questions" task list, with the user's nickname, and the message as content.](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px "Task created") Using this feature, for example, you can ask your moderators to highlight all chat questions, so you can see them at a glance during your livestream, and check them as answered. diff --git a/support/documentation/content/en/documentation/user/streamers/terms.md b/support/documentation/content/en/documentation/user/streamers/terms.md index bf190c04..04ed3835 100644 --- a/support/documentation/content/en/documentation/user/streamers/terms.md +++ b/support/documentation/content/en/documentation/user/streamers/terms.md @@ -16,7 +16,7 @@ These terms will be shown to all users joining the chat. To configure the terms & conditions, go to the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel): -![Channel configuration / Terms](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px) +![Screenshot of the channel options form, with a field to configure your terms and conditions.](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px "Channel configuration / Terms") URL in the message will be clickable. You can also do some styling: [Message Styling](https://xmpp.org/extensions/xep-0393.html). @@ -25,7 +25,7 @@ You can also do some styling: [Message Styling](https://xmpp.org/extensions/xep- When joining the chat, viewers will see the terms: -![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px) +![Screenshot of a chat session. On the top of the chat, there are terms and conditions for both the server and the channel.](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px "Terms") {{% notice info %}} Peertube instance's admin can also set global terms & conditions. diff --git a/support/documentation/content/en/documentation/user/viewers.md b/support/documentation/content/en/documentation/user/viewers.md index d9bbe70e..d7130b4a 100644 --- a/support/documentation/content/en/documentation/user/viewers.md +++ b/support/documentation/content/en/documentation/user/viewers.md @@ -9,7 +9,7 @@ chapter: false When you are watching a Peertube video that has the chat activated, you will see the chat next to the video: -![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px "Chat screenshot") There are two slightly different use cases, depending on wether or not you have an account on the Peertube instance. See bellow for more informations. @@ -23,11 +23,11 @@ This feature can be disabled by the instance's adminitrators. If you are not logged in on the Peertube instance where you are watching the video, you will automatically join the chat. You will be assigned a random nickname (something like "Anonymous 12345"). -![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px) +![Screenshot of a chat. In the participant list, there is John Livingston, and an anonymous account using "Anonymous 212873" nickname.](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px "Chat with an anonymous user") Before being able to speak in the chat room, you have to enter a nickname in the field on the bottom of the window. -![Joining chat when not connected](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px) +![Screenshot of the chat. The current user is not logged in, and must choose a nickname before being able to write in the chat.](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px "Joining chat when not connected") #### Log in using an external authentication provider @@ -39,9 +39,9 @@ The Peertube instance can configure external authentication providers (Mastodon In such case, you will see a "{{% livechat_label login_using_external_account %}}" button, that will open a dialog modal. In this dialog modal, there will be some buttons to connect using a remote account. -![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a "{{% livechat_label login_using_external_account %}}" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px "{{% livechat_label login_using_external_account %}} button") -![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px) +![Screenshot of a dialog with an "OpenID Connect" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px "External login dialog - OpenID Connect") Once you signed in the remote account, and have granted access, your nickname and avatar (if available) will be automatically fetched. No other data will be stored. @@ -68,9 +68,9 @@ This button will open a dialog where you can enter your Peertube instance URL. Once you entered it, it will check if the livechat plugin is available on the remote instance, and if the video is available. If it is the case, you will be redirected to the video on the remote instance. -![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a "{{% livechat_label login_using_external_account %}}" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px "{{% livechat_label login_using_external_account %}} button") -![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label login_using_external_account %}}" dialog. There is a field where you can enter a Peertube url.](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px "External login dialog") ## Chatting @@ -91,7 +91,7 @@ You can also click on a nickname in the participants list to insert it in the me To see the list of participants, just open the right menu: -![Participants list](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px) +![Screenshot of a chat session, with on the right the list of participants.](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px "Participants list") You can see that some participants have special rights (moderator, owner, ...). @@ -101,14 +101,14 @@ There is a dropdown menu on the top of the chat, with some advanced features. This is especially useful for [moderation features](/peertube-plugin-livechat/documentation/user/streamers/moderation). Available features depends on your access level. -![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px) +![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px "Chat menu") ## Opening full screen On top of the chat, there is a button to open the chat in fullscreen. This will open a new browser tab with the following content: -![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px) +![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px "Fullscreen chat screenshot") It can be easier to chat using a full browser tab. diff --git a/support/documentation/content/en/documentation/user/xmpp_clients.md b/support/documentation/content/en/documentation/user/xmpp_clients.md index b04c6382..19d923e2 100644 --- a/support/documentation/content/en/documentation/user/xmpp_clients.md +++ b/support/documentation/content/en/documentation/user/xmpp_clients.md @@ -29,7 +29,7 @@ chats using any XMPP account. To get the address of the room you want to join, you can use the "share chat" button that is located above the chat: -![Share button](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px) +![Screenshot of the chat, with a share button on the top.](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px "Share button") {{% notice info %}} By default, the share button is only visible to the owner of the video, @@ -37,9 +37,9 @@ and the admins/moderators of the instance. However, admins can decide to display this button for everyone. {{% /notice %}} -Then, choose "Connect using XMPP": +Then, choose "{{% livechat_label connect_using_xmpp %}}": -![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, on the "{{% livechat_label connect_using_xmpp %}}" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px "{{% livechat_label connect_using_xmpp %}}") Then you just have to click on "open" or copy/paste the address of the chat room into your XMPP client (using the "join a room" feature). diff --git a/support/documentation/content/en/images/message_search.png b/support/documentation/content/en/images/message_search.png new file mode 100644 index 00000000..8177be9b Binary files /dev/null and b/support/documentation/content/en/images/message_search.png differ diff --git a/support/documentation/content/en/images/mod_firewall.png b/support/documentation/content/en/images/mod_firewall.png new file mode 100644 index 00000000..9c6e9952 Binary files /dev/null and b/support/documentation/content/en/images/mod_firewall.png differ diff --git a/support/documentation/content/en/images/moderation_notes_filters.png b/support/documentation/content/en/images/moderation_notes_filters.png new file mode 100644 index 00000000..1eed5de4 Binary files /dev/null and b/support/documentation/content/en/images/moderation_notes_filters.png differ diff --git a/support/documentation/content/en/images/moderation_notes_open_app_fullpage.png b/support/documentation/content/en/images/moderation_notes_open_app_fullpage.png new file mode 100644 index 00000000..4a461645 Binary files /dev/null and b/support/documentation/content/en/images/moderation_notes_open_app_fullpage.png differ diff --git a/support/documentation/content/en/images/moderation_notes_open_app_video.png b/support/documentation/content/en/images/moderation_notes_open_app_video.png new file mode 100644 index 00000000..18e2b819 Binary files /dev/null and b/support/documentation/content/en/images/moderation_notes_open_app_video.png differ diff --git a/support/documentation/content/en/images/moderator_notes_app_fullpage_1.png b/support/documentation/content/en/images/moderator_notes_app_fullpage_1.png new file mode 100644 index 00000000..3f691d80 Binary files /dev/null and b/support/documentation/content/en/images/moderator_notes_app_fullpage_1.png differ diff --git a/support/documentation/content/en/images/moderator_notes_app_video_1.png b/support/documentation/content/en/images/moderator_notes_app_video_1.png new file mode 100644 index 00000000..3ec9d5ad Binary files /dev/null and b/support/documentation/content/en/images/moderator_notes_app_video_1.png differ diff --git a/support/documentation/content/en/intro/_index.md b/support/documentation/content/en/intro/_index.md index 3fa3ca94..87500d8e 100644 --- a/support/documentation/content/en/intro/_index.md +++ b/support/documentation/content/en/intro/_index.md @@ -13,14 +13,14 @@ By default, once you have installed the plugin on your Peertube instance, a chat On the following screenshot, you can see a classic Peertube video page, with a chat room on the right (click on the picture to view it full screen): -![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px "Chat screenshot") The chat room will be accessible for all viewers, even those who don't have an account on your instance. Those "anonymous" users just have to choose a nickname before they can begin talking in the chat. By default, the chat is displayed next to the video. But you can open it in another browser tab, using the button on top of it : -![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px) +![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px "Fullscreen chat screenshot") {{% notice tip %}} You can test the livechat plugin with this [demo page](https://www.yiny.org/w/399a8d13-d4cf-4ef2-b843-98530a8ccbae). @@ -31,7 +31,7 @@ You can test the livechat plugin with this [demo page](https://www.yiny.org/w/39 As a Peertube administrator, you can setup this plugin on your instance simply by using the Peertube plugin marketplace included in the administration interface. Search for "livechat", then click "install": that's it! -![Livechat installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px) +![Screenshot of Peertube plugins admin page. The search fields contains "livechat", and the search results show the livechat plugin.](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px "Livechat installation") ## Livechat capabilities @@ -76,11 +76,11 @@ This is for example useful for replays. In the following screenshot, you can see a live replay, where the chat content is embeded on bottom of the video: -![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px "Embeding the chat in a live stream") In the following screenshot, you can see an OBS setup, where the chat is included as a source in the current scene (background color can be changed, and can be transparent): -![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px) +![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px "Embeding the chat in OBS") ## Other usages diff --git a/support/documentation/content/en/technical/moderation_notes.md b/support/documentation/content/en/technical/moderation_notes.md new file mode 100644 index 00000000..d775583d --- /dev/null +++ b/support/documentation/content/en/technical/moderation_notes.md @@ -0,0 +1,86 @@ +--- +title: "Moderator notes overview" +description: "Moderator Notes Application technical overview" +weight: 75 +chapter: false +livechatnotranslation: true +--- + +The livechat plugin includes a [Moderation Notes Application](/peertube-plugin-livechat/documentation/user/streamers/moderation_notes). +The present document describes how this is implemented. + +## Basics + +This features relies on [XEP-0060: Publish-Subscribe](https://xmpp.org/extensions/xep-0060.html). +This XEP provide a way to store and retrieve items, and to receive push notifications when an item is created/deleted/modified. + +There is a Prosody Module, [mod_pubsub_peertubelivechat](https://github.com/JohnXLivingston/peertube-plugin-livechat/tree/main/prosody-modules/mod_pubsub_peertubelivechat), to implement some specific use of the pubsub mechanism. + +This module is also used for [Tasks](/peertube-plugin-livechat/technical/tasks/). + +We use the [JID+NodeID addressing](https://xmpp.org/extensions/xep-0060.html#addressing-jidnode) to specify some nodes related to each MUC room. +The JID is the MUC room JID, the NodeID is functionnality we want to address. + +This modules implement the "livechat-notes" node, to handle moderator notes. + +The "livechat-notes" node contains one type of objects: Note (XML Namespaces: `urn:peertube-plugin-livechat:note`). + +On the front-end, we have the [livechat-converse-notes](https://github.com/JohnXLivingston/peertube-plugin-livechat/tree/main/conversejs/custom/plugins/notes) plugin for ConverseJS. + +## Workflow / Unsubscribing + +This is basically the same as for [Tasks](/peertube-plugin-livechat/technical/tasks/). + +## Items + +Here we describes the content of note items. + +* Item tag: `note` +* XML Namespace: `urn:peertube-plugin-livechat:note` +* item attributes: + * `order`: the order of the note in the note list +* item childs: + * `description`: the text content of the note + * `note-about`: an optional tag, if the note is associated to a participant + +The `note-about` tag, if present, has following structure: + +* Item tag: `note-about` +* XML Namespace: none +* item attributes: + * `jid`: the JID of the occupant + * `nick` the nick of the occupant, at time of note creation +* item childs: + * `occupant-id`: see [XEP-0421](https://xmpp.org/extensions/xep-0421.html). + +Example: + +```xml + + + + + + Some text. + + + + + + + + +``` diff --git a/support/documentation/content/en/technical/tasks.md b/support/documentation/content/en/technical/tasks.md index e4b38879..adef52b4 100644 --- a/support/documentation/content/en/technical/tasks.md +++ b/support/documentation/content/en/technical/tasks.md @@ -16,10 +16,12 @@ This XEP provide a way to store and retrieve items, and to receive push notifica There is a Prosody Module, [mod_pubsub_peertubelivechat](https://github.com/JohnXLivingston/peertube-plugin-livechat/tree/main/prosody-modules/mod_pubsub_peertubelivechat), to implement some specific use of the pubsub mechanism. +This module is also used for [Moderator Notes](/peertube-plugin-livechat/technical/moderation_notes/). + We use the [JID+NodeID addressing](https://xmpp.org/extensions/xep-0060.html#addressing-jidnode) to specify some nodes related to each MUC room. The JID is the MUC room JID, the NodeID is functionnality we want to address. -For now, this modules only implement one such node: "livechat-tasks", to handle tasks and task lists. -But the module code anticipates futur uses. + +This modules implement the "livechat-tasks" node, to handle tasks and task lists. The "livechat-tasks" node contains two type of objects: Task and TaskList (XML Namespaces: `urn:peertube-plugin-livechat:tasklist` and `urn:peertube-plugin-livechat:task`). Tasks have an attribute containing their task list id. diff --git a/support/documentation/static/css/livechatdoc.css b/support/documentation/layouts/partials/custom-header.html similarity index 56% rename from support/documentation/static/css/livechatdoc.css rename to support/documentation/layouts/partials/custom-header.html index 3850d345..37f5085d 100644 --- a/support/documentation/static/css/livechatdoc.css +++ b/support/documentation/layouts/partials/custom-header.html @@ -1,16 +1,13 @@ -/* - * SPDX-FileCopyrightText: 2024 John Livingston - * - * SPDX-License-Identifier: AGPL-3.0-only - */ + \ No newline at end of file diff --git a/support/documentation/layouts/partials/custom-header.html.license b/support/documentation/layouts/partials/custom-header.html.license new file mode 100644 index 00000000..b253ad42 --- /dev/null +++ b/support/documentation/layouts/partials/custom-header.html.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 John Livingston + +SPDX-License-Identifier: AGPL-3.0-only diff --git a/support/documentation/layouts/partials/logo.html b/support/documentation/layouts/partials/logo.html index a72f60c6..df524763 100644 --- a/support/documentation/layouts/partials/logo.html +++ b/support/documentation/layouts/partials/logo.html @@ -1,5 +1,6 @@ - + \n" -"Language-Team: Arabic \n" +"Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" -"X-Generator: Weblate 5.3.1\n" +"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : (n%100>=3 " +"&& n%100<=10) ? 3 : n%100>=11 ? 4 : 5);\n" +"X-Generator: Weblate 5.7\n" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/contact/_index.md @@ -91,22 +93,22 @@ msgstr "" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "Git" -msgstr "" +msgstr "جيت" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "NodeJS" -msgstr "" +msgstr "NodeJS" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "NPM" -msgstr "" +msgstr "NPM" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "Typescript" -msgstr "" +msgstr "Typescript" #. type: Plain text #: support/documentation/content/en/contributing/develop/_index.md @@ -116,7 +118,7 @@ msgstr "" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "`git`" -msgstr "" +msgstr "`git`" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md @@ -131,7 +133,7 @@ msgstr "" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "`build-essential`" -msgstr "" +msgstr "`build-essential`" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md @@ -146,7 +148,7 @@ msgstr "" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "`reuse`" -msgstr "" +msgstr "`reuse`" #. type: Plain text #: support/documentation/content/en/contributing/develop/_index.md @@ -430,7 +432,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md -msgid "The used theme is [hugo-theme-learn](https://learn.netlify.app/). You should read its documentation before starting editing the documentation." +msgid "The minimum required version for Hugo is 0.121.0. It was tested using version 0.132.2." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/contributing/document/_index.md +msgid "The used theme is [hugo-theme-relearn](https://mcshelby.github.io/hugo-theme-relearn). You should read its documentation before starting editing the documentation." msgstr "" #. type: Plain text @@ -479,7 +486,7 @@ msgstr "" #: support/documentation/content/en/contributing/document/_index.md #, no-wrap msgid "Preview" -msgstr "" +msgstr "معاينة" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md @@ -539,7 +546,7 @@ msgstr "" #: support/documentation/content/en/contributing/document/_index.md #, no-wrap msgid "Write documentation" -msgstr "" +msgstr "كتابة الدليل" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md @@ -596,7 +603,7 @@ msgstr "" #: support/documentation/content/en/contributing/document/_index.md #, no-wrap msgid "Publication" -msgstr "" +msgstr "النشر" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md @@ -651,7 +658,7 @@ msgstr "" #: support/documentation/content/en/contributing/translate/_index.md #, no-wrap msgid "How to" -msgstr "" +msgstr "كيف ذلك" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/translate/_index.md @@ -690,10 +697,9 @@ msgstr "" #. type: Title ## #: support/documentation/content/en/contributing/translate/_index.md -#, fuzzy, no-wrap -#| msgid "Translations" +#, no-wrap msgid "ConverseJS translations" -msgstr "الترجمات" +msgstr "ترجمات ConverseJS" #. type: Plain text #: support/documentation/content/en/contributing/translate/_index.md @@ -804,7 +810,7 @@ msgstr "" #: support/documentation/content/en/contributing/translate/_index.md #, no-wrap msgid "Documentation translation" -msgstr "" +msgstr "ترجمة الدليل" #. type: Plain text #: support/documentation/content/en/contributing/translate/_index.md @@ -892,7 +898,7 @@ msgstr "" #: support/documentation/content/en/credits/_index.md #, no-wrap msgid "Credits" -msgstr "" +msgstr "المُساهِمون في المشروع" #. type: Plain text #: support/documentation/content/en/credits/_index.md @@ -943,13 +949,13 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/_index.md #, no-wrap msgid "Some advanced features" -msgstr "" +msgstr "بعض الميزات المتقدمة" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/admin/advanced/_index.md #, no-wrap msgid "Advanced usage" -msgstr "" +msgstr "استخدام متقدم" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/admin/advanced/matterbridge.md @@ -961,7 +967,7 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/matterbridge.md #, no-wrap msgid "Using Matterbridge" -msgstr "" +msgstr "استخدم Matterbridge" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/matterbridge.md @@ -1067,7 +1073,7 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/matterbridge.md #, no-wrap msgid "Configurating Matterbridge" -msgstr "" +msgstr "ضبط Matterbridge" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/matterbridge.md @@ -1183,7 +1189,7 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md #, no-wrap msgid "Plugin settings" -msgstr "" +msgstr "إعدادات الملحق" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -1225,7 +1231,7 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md #, no-wrap msgid "DNS" -msgstr "" +msgstr "نظام أسماء النطاقات" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -1312,7 +1318,7 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md #, no-wrap msgid "Using trusted certificates" -msgstr "" +msgstr "استخدام شهادات موثوق فيها" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -1342,7 +1348,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md -msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx`" +msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx`" msgstr "" #. type: Plain text @@ -1663,7 +1669,7 @@ msgstr "" #: support/documentation/content/en/documentation/installation/troubleshooting.md #, no-wrap msgid "Troubleshooting" -msgstr "" +msgstr "في حال مشكلة" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -1681,7 +1687,7 @@ msgstr "" #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap msgid "External Authentication" -msgstr "" +msgstr "استيثاق خارجي" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md @@ -1706,13 +1712,13 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a \"{{% livechat_label login_using_external_account %}}\" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px \"{{% livechat_label login_using_external_account %}} button\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog with an \"OpenID Connect\" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px \"External login dialog - OpenID Connect\")" msgstr "" #. type: Plain text @@ -1839,7 +1845,7 @@ msgstr "" #: build/documentation/pot_in/documentation/admin/external_auth.md #, no-wrap msgid "More to come" -msgstr "" +msgstr "المزيد لاحقا…" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md @@ -1854,10 +1860,101 @@ msgstr "" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/admin/_index.md -#, fuzzy, no-wrap -#| msgid "Documentation" +#, no-wrap msgid "Admin documentation" -msgstr "المستندات" +msgstr "مستندات الإدارة" + +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Advanced firewall rules for the Prosody server" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Prosody mod_firewall" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "This feature comes with the livechat plugin version 11.0.0." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Doing so, Peertube admins will be able to define advanced firewall rules." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "These rules could be used to run arbitrary code on the server. If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. You can still use mod_firewall by editing files directly on the server." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Edit rules" +msgstr "تعديل القواعد" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "First, you must enable the feature in the [plugin settings](/peertube-plugin-livechat/documentation/admin/settings)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Just bellow the settings, you will find a \"Configure mod_firewall\" button. This button will open a configuration page." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "![Screenshot of the \"{{% livechat_label prosody_firewall_configuration %}}\" form.](/peertube-plugin-livechat/images/mod_firewall.png?classes=shadow,border&height=400px \"{{% livechat_label prosody_firewall_configuration %}}\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Here you can add several configuration files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can enable/disable each files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Files will be loaded in the alphabetical order. You can use a number as prefix to easily choose the order." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can also edit these firewall rules directly on the server, in the `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` directory. File names must only contains alphanumerical characters, underscores and hyphens. The extension must be `.pfw`, or `.pfw.disabled` if you want to disable a file. Please be sure that the peertube system user has write access to these files, else the web editing interface will fail. Once you have edited these files, you must reload prosody. This can be done by saving the plugin settings, or saving the mod_firewall configuration in the web interface, or by restarting Peertube." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "When you save the configuration, the server will automatically reload it, and your rules will apply immediatly. You can check that there is no parsing error in the Prosody error log. To do so, you can read the `plugins/data/peertube-plugin-livechat/prosody/prosody.err` file, or use the [diagnostic tool](/peertube-plugin-livechat/documentation/installation/troubleshooting/) that will show last Prosody errors." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Examples" +msgstr "أمثلة" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Don't hesitate to share your rules. To do so, you can for example edit this [page](/peertube-plugin-livechat/contributing/document/#write-documentation)." +msgstr "" #. type: Yaml Front Matter Hash Value: description #: build/documentation/pot_in/documentation/admin/settings.md @@ -1869,7 +1966,7 @@ msgstr "" #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap msgid "Settings" -msgstr "" +msgstr "الإعدادات" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -1896,7 +1993,7 @@ msgstr "" #: support/documentation/content/en/intro/_index.md #, no-wrap msgid "Federation" -msgstr "" +msgstr "الفديرالية" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -1905,10 +2002,9 @@ msgstr "" #. type: Title ## #: build/documentation/pot_in/documentation/admin/settings.md -#, fuzzy, no-wrap -#| msgid "Documentation" +#, no-wrap msgid "Authentication" -msgstr "المستندات" +msgstr "الإستيثاق" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -1929,7 +2025,7 @@ msgstr "" #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap msgid "Channel advanced configuration" -msgstr "" +msgstr "ضبط متقدم للقناة" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -1985,7 +2081,7 @@ msgstr "" #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap msgid "Theming" -msgstr "" +msgstr "تخصيص المظهر" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -1999,7 +2095,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px)" +msgid "![Sepia avatar example](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px \"Sepia\")" msgstr "" #. type: Plain text @@ -2009,7 +2105,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Cats](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px)" +msgid "![Cats avatar example](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px \"Cats\")" msgstr "" #. type: Plain text @@ -2019,7 +2115,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Birds](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px)" +msgid "![Birds avatar example](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px \"Birds\")" msgstr "" #. type: Plain text @@ -2029,7 +2125,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px)" +msgid "![Fenecs avatar example](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px \"Fenecs\")" msgstr "" #. type: Plain text @@ -2039,7 +2135,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Abstracts](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px)" +msgid "![Abstracts avatar example](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px \"Abtracts\")" msgstr "" #. type: Plain text @@ -2049,7 +2145,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Legacy](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px)" +msgid "![Legacy avatar example](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px \"Legacy\")" msgstr "" #. type: Plain text @@ -2074,7 +2170,7 @@ msgstr "" #. type: Bullet: '- ' #: build/documentation/pot_in/documentation/admin/settings.md -msgid "ConverseJS concord theme: this is a theme provided by ConverseJS." +msgid "ConverseJS cyberpunk theme: this is a theme provided by ConverseJS." msgstr "" #. type: Title ## @@ -2129,12 +2225,16 @@ msgstr "" msgid "More informations on Prosody external components [here](https://prosody.im/doc/components)." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "For more information, please check [the documentation](/peertube-plugin-livechat/documentation/admin/mod_firewall/)." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/_index.md -#, fuzzy, no-wrap -#| msgid "Documentation" +#, no-wrap msgid "Plugin documentation" -msgstr "المستندات" +msgstr "مستندات الملحق" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/installation/cpu_compatibility.md @@ -2265,7 +2365,7 @@ msgstr "" #: support/documentation/content/en/documentation/installation/_index.md #, no-wrap msgid "Installation guide" -msgstr "" +msgstr "دليل التثبيت" #. type: Plain text #: support/documentation/content/en/documentation/installation/_index.md @@ -2304,7 +2404,7 @@ msgstr "" #: support/documentation/content/en/documentation/installation/troubleshooting.md #, no-wrap msgid "Diagnostic tool" -msgstr "" +msgstr "أدوات الفحص" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md @@ -2318,7 +2418,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Launch diagnostic](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the plugin's settings page, with a \"launch diagnostic\" button.](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px \"Launch diagnostic\")" msgstr "" #. type: Plain text @@ -2328,7 +2428,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Diagnostic result](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the diagnostic result page. This gives a lot of information, with status for different test suites.](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px \"Diagnostic result\")" msgstr "" #. type: Title ## @@ -2363,7 +2463,7 @@ msgstr "" #: support/documentation/content/en/documentation/installation/troubleshooting.md #, no-wrap msgid "Websocket" -msgstr "" +msgstr "Websocket" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md @@ -2396,7 +2496,7 @@ msgstr "" #: support/documentation/content/en/documentation/installation/upgrade_before_6.0.0.md #, no-wrap msgid "IMPORTANT NOTE" -msgstr "" +msgstr "ملاحظة هامة" #. type: Plain text #: support/documentation/content/en/documentation/installation/upgrade_before_6.0.0.md @@ -2421,10 +2521,9 @@ msgstr "" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/_index.md -#, fuzzy, no-wrap -#| msgid "Documentation" +#, no-wrap msgid "User documentation" -msgstr "المستندات" +msgstr "دليل المستخدم" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/obs.md @@ -2457,7 +2556,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px \"Embeding the chat in a live stream\")" msgstr "" #. type: Plain text @@ -2473,7 +2572,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, where the \"{{% livechat_label read_only %}}\" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px \"Share link popup\")" msgstr "" #. type: Plain text @@ -2484,7 +2583,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px \"Embeding the chat in OBS\")" msgstr "" #. type: Plain text @@ -2542,7 +2641,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable.\"](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px \"Share link popup - dock tab\")" msgstr "" #. type: Plain text @@ -2552,12 +2651,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock menu](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Dock menu, with a \"Custom Browser Docks\" entry.](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px \"OBS - Dock menu\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Custom Browser Docks dialog, with a new dock called \"My chat\".](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px \"OBS - Dock dialog\")" msgstr "" #. type: Plain text @@ -2567,7 +2666,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of OBS with a new dock including the chat. The user is logged in with their Peertube account, and can chat directly from OBS.](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px \"OBS - Dock\")" msgstr "" #. type: Plain text @@ -2611,7 +2710,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/basics.md #, no-wrap msgid "Some basics" -msgstr "" +msgstr "بعض الأساسيات" #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2631,7 +2730,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![New live](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the Peertube new live form.](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px \"New live\")" msgstr "" #. type: Plain text @@ -2641,7 +2740,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Activate the chat](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the form, with a \"{{% livechat_label use_chat %}}\" checkbox.](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px \"Activate the chat\")" msgstr "" #. type: Plain text @@ -2664,7 +2763,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/basics.md #, no-wrap msgid "Share the chat" -msgstr "" +msgstr "إعادة نشر المحادثة" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2713,7 +2812,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label web %}} tab. There is a url you can copy.](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px \"Share link popup - web tab\")" msgstr "" #. type: Plain text @@ -2723,7 +2822,8 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - xmpp tab](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +#: support/documentation/content/en/documentation/user/xmpp_clients.md +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label connect_using_xmpp %}}\" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px \"{{% livechat_label connect_using_xmpp %}}\")" msgstr "" #. type: Title ## @@ -2732,7 +2832,7 @@ msgstr "" #: support/documentation/content/en/intro/_index.md #, no-wrap msgid "Moderation" -msgstr "" +msgstr "الإشراف" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2771,7 +2871,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/basics.md #: build/documentation/pot_in/documentation/user/streamers/moderation.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px \"Chat menu\")" msgstr "" #. type: Plain text @@ -2781,7 +2881,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Configure chat room](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat configuration form.](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px \"Configure chat room\")" msgstr "" #. type: Plain text @@ -2825,11 +2925,11 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md #, no-wrap msgid "Commands" -msgstr "" +msgstr "الأوامر" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md -msgid "![Commands configuration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with some fields to configure the bot commands.](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px \"Commands configuration\")" msgstr "" #. type: Plain text @@ -2847,16 +2947,16 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md #, no-wrap msgid "Forbidden words" +msgstr "المصطلحات الممنوعة" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md +msgid "![Screenshot of the channel options page, with several fields to configure the forbidden words.](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px \"Forbidden words configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Forbidden words configuration](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px)" -msgstr "" - -#. type: Plain text -#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Deleted message](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px)" +msgid "![Screenshot of a chat message that was deleted, with the following reason: \"No url allowed\".](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px \"Deleted message\")" msgstr "" #. type: Plain text @@ -2894,6 +2994,12 @@ msgstr "" msgid "These words are case insensitive." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md +#: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +msgid "You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them." +msgstr "" + #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md msgid "This features is still experimental. There might be some issues with non-latin alphabets. You can [open an issue](https://github.com/JohnXLivingston/peertube-plugin-livechat/issues) to report your problems." @@ -2908,14 +3014,14 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #, no-wrap msgid "Chat bot setup" -msgstr "" +msgstr "ضبط روبوت المحادثة" #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #: support/documentation/content/en/intro/_index.md #, no-wrap msgid "Chat bot" -msgstr "" +msgstr "روبوت محادثة" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md @@ -2931,7 +3037,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px \"Channel configuration\")" msgstr "" #. type: Plain text @@ -2968,7 +3074,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/quotes.md -msgid "![Timers configuration](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the channel options page, with some fields to configure a new timer.](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px \"Timers configuration\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -2980,10 +3086,9 @@ msgstr "معلومات عامة" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/streamers/channel.md -#, fuzzy, no-wrap -#| msgid "General information" +#, no-wrap msgid "Channel configuration" -msgstr "معلومات عامة" +msgstr "ضبط القناة" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md @@ -2992,7 +3097,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the chatrooms configuration page. The page list the user's channels.](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px \"Chatrooms menu\")" msgstr "" #. type: Plain text @@ -3045,13 +3150,13 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/emojis.md #, no-wrap msgid "Custom emojis" -msgstr "" +msgstr "الإيموجي المخصصة" #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/emojis.md #, no-wrap msgid "Channel emojis" -msgstr "" +msgstr "الوجوه التعبيرية الخاصة بالقناة" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md @@ -3065,12 +3170,12 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis configuration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the emoji configuration page. There is a form where you can add new emojis.](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session, with messages containing custom emojis. The emoji picker is open, and shows custom emojis.](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis\")" msgstr "" #. type: Title ### @@ -3116,7 +3221,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/_index.md #, no-wrap msgid "For streamers" -msgstr "" +msgstr "للناشرين على المباشر" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md @@ -3137,6 +3242,7 @@ msgstr "" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #: support/documentation/content/en/documentation/user/streamers/tasks.md #: support/documentation/content/en/intro/_index.md @@ -3172,7 +3278,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel option form, with a field to configure the moderation delay.](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Moderation delay\")" msgstr "" #. type: Plain text @@ -3215,7 +3321,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Moderation delay timer](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border)" +msgid "![Screenshot of a chat message. A timer is displayed next to the message datetime. The timer is in seconds.](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border \"Moderation delay timer\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -3241,7 +3347,7 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap msgid "The chat bot" -msgstr "" +msgstr "روبوت المحادثة" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3252,7 +3358,7 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap msgid "Accessing moderation tools" -msgstr "" +msgstr "الوصول إلى أدوات الإشراف" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3293,7 +3399,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the room configuration form. There is a \"{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}\" checkbox.](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px \"Room configuration / Mute anonymous users\")" msgstr "" #. type: Plain text @@ -3303,7 +3409,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. The current user has no message field. There is a message: \"{{% livechat_label muted_anonymous_message %}}\"](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px \"Room configuration / Muted anonymous users\")" msgstr "" #. type: Plain text @@ -3337,6 +3443,81 @@ msgstr "" msgid "You can promote users as moderators, if you need some help." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "It is possible to anonymize moderation actions, to avoid disclosing who is banning/kicking/… occupants." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the \"configure\" menu. In the form, you will find a \"{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}\" checkbox." +msgstr "" + +#. type: Title ## +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#, no-wrap +msgid "Participant message history search" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "As a room admin or owner, you can search all messages sent by a given participant." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To do so, you have several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "![Screenshot of a chat session. The moderator has open the message menu, and there is a \"{{% livechat_label search_occupant_message %}}\" button.](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px \"Message history search\")" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "To have more space and better readability, open the chat in full-page mode." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "In the search results, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "if the current nickname is different than the nickname when the participant has sent the message, the original nickname will be shown" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "The search result will also include all messages related to participants who had the same nickname. You can differenciate them by comparing [JID](https://xmpp.org/extensions/xep-0029.html) and [occupant-id](https://xmpp.org/extensions/xep-0421.html)." +msgstr "" + #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap @@ -3352,7 +3533,7 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap msgid "Instance moderation" -msgstr "" +msgstr "الإشراف على الخادم" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3369,6 +3550,230 @@ msgstr "" msgid "From there, you can also promote yourself as room moderator by using the \"{{% livechat_label promote %}}\" button on the right." msgstr "" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Plugin peertube-plugin-livechat moderation notes" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Moderation notes" +msgstr "ملاحظات الإشراف" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The livechat plugin includes a Moderator Notes Application: you can write some notes, that could be associated to chat participants. Every room's admins have access to these notes, so they can edit them collaboratively." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can for example use this Application to:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "share some notes between moderators" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "take notes about participants that were kicked or caused troubles" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "..." +msgstr "..." + +#. type: Title ## +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Using the Moderator Notes Application" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Opening the Moderator Notes Application" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "To open the Moderator Notes Application, there is a \"{{% livechat_label \"moderator_notes\" %}}\" button in the top chat menu:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_video.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Clicking this button will toggle the Application display:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/polls.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +#, no-wrap +msgid "Access rights" +msgstr "صلاحيات الوصول" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Every room's admins have access to this Application (read and write access)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you promote someone as room admin or owner, they gets instant access to this Application. When you remove admin or owner rights to someone, they instantly lose access to this Application." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Scope" +msgstr "النطاق" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Notes are only available in the room in which you have created them." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Chatrooms can be releated to video or channel. If you want to keep notes from one video to another, please consider using rooms associated to channels." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Currently the video vs channel rooms is an instance-wide settings. Only Peertube admins can change it, and it applies to all chatrooms. In the future, this choice will be added in your channel's options." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes" +msgstr "الملاحظات" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Create/Edit Notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can use the plus button on the top to create a new note. You can also edit existing notes using the edit button, or delate any note." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can create a note associated to a participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When a note is associated to a participant, you will see their nickname and avatar on the top of the note." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes filtering" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can filter notes to find all notes related to a given participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button that is available on notes to find all notes related to the same participant" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can remove the filter by clicking on the close button." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of the note application, with a filter enabled for user \"Mike\". The only notes that are shown are the notes for the Mike user.](/peertube-plugin-livechat/images/moderation_notes_filters.png?classes=shadow,border&height=200px \"Moderator Notes Application - filtering\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you filters notes on a participant, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "if the current nickname is different than the nickname when you created the note, the original nickname will be shown" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The search result will also include all notes related to participants who had the same nickname. So you can also take note for anonymous users (who don't have any consistent JID or occupant-id). You can differenciate them by comparing JID and occupant-id." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Sorting notes" +msgstr "ترتيب الملاحظات" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can sort notes simply using drag & drop." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/polls.md #, no-wrap @@ -3379,7 +3784,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/polls.md #, no-wrap msgid "Polls" -msgstr "" +msgstr "استطلاعات الرأي" #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/polls.md @@ -3394,7 +3799,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a \"{{% livechat_label new_poll %}}\" form. The form contains several fields: question, duration, choices, …](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px \"Poll form\")" msgstr "" #. type: Plain text @@ -3448,13 +3853,6 @@ msgstr "" msgid "If there was a previous unfinished poll, it will end and its result will be shown." msgstr "" -#. type: Title ### -#: support/documentation/content/en/documentation/user/streamers/polls.md -#: support/documentation/content/en/documentation/user/streamers/tasks.md -#, no-wrap -msgid "Access rights" -msgstr "" - #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md msgid "Every room's admins can create a new poll." @@ -3493,7 +3891,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. In the chat, there is a message with the poll question, and the different choices. There is also a banner on the top of the chat, where you can see the question, and the number of votes for each answers.](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px \"Poll start\")" msgstr "" #. type: Plain text @@ -3513,7 +3911,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll votes](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with an ongoing poll. The current user has just voted by sending \"!1\".](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px \"Poll votes\")" msgstr "" #. type: Plain text @@ -3538,7 +3936,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with poll that has ended. The banner no more accept new votes. There is a message in the chat with the poll results. For each choice, there is the number of votes, and the percentage of the total it represents.](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px \"Poll end\")" msgstr "" #. type: Plain text @@ -3556,7 +3954,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #, no-wrap msgid "Slow mode" -msgstr "" +msgstr "الوضع البطيء" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md @@ -3597,7 +3995,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #, no-wrap msgid "Slow mode option" -msgstr "" +msgstr "ميزة الوضع البطيء" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md @@ -3606,7 +4004,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Channel configuration / Slow Mode](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a slow mode field.](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Slow Mode\")" msgstr "" #. type: Plain text @@ -3633,7 +4031,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. There is a banner on the bottom of the chat, indicating that the slow mode is enabled, and that users can send a message every 2 seconds.](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px \"Slow mode infobox\")" msgstr "" #. type: Plain text @@ -3678,11 +4076,6 @@ msgstr "" msgid "highlight questions from your viewers, so you can come back to them later without forgetting to answer them" msgstr "" -#. type: Bullet: '* ' -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "..." -msgstr "" - #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap @@ -3702,12 +4095,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text @@ -3717,17 +4110,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "To have more space and better readability, open the chat in full-page mode." +msgid "![Screenshot of a Peertube chat, fullscreen. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text @@ -3744,7 +4132,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap msgid "Task lists" -msgstr "" +msgstr "قائمة المهام" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3763,25 +4151,20 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task lists](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgid "![Screenshot of a chat session, with the Task application. There are several task lists.](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px \"Task lists\")" msgstr "" #. type: Title ### #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap msgid "Tasks" -msgstr "" +msgstr "المهام" #. type: Title #### #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap msgid "Create tasks" -msgstr "" +msgstr "إنشاء مهام" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3790,19 +4173,19 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task form](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, there is a form to create a new task.](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px \"Task form\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, a new task was created.](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Title #### #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap msgid "Edit tasks" -msgstr "" +msgstr "تعديل المهام" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3816,7 +4199,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under task lists, there are several tasks. Some of them are checked, other not.](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px \"Tasks\")" msgstr "" #. type: Title #### @@ -3832,12 +4215,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to sort](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another.](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px \"Drag and drop to sort\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to move to another list](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another task list.](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px \"Drag and drop to move to another list\")" msgstr "" #. type: Title #### @@ -3853,17 +4236,17 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Create task from message](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. The menu besides a message is open, with a button to create a new task.](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px \"Create task from message\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Choose the task list](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog, where you can choose in which task list you want to add the new task.](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px \"Choose the task list\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. A new task was added in the \"chat questions\" task list, with the user's nickname, and the message as content.](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Plain text @@ -3886,10 +4269,9 @@ msgstr "" #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/terms.md -#, fuzzy, no-wrap -#| msgid "General information" +#, no-wrap msgid "Configuration" -msgstr "معلومات عامة" +msgstr "الضبط" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md @@ -3903,7 +4285,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Channel configuration / Terms](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a field to configure your terms and conditions.](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px \"Channel configuration / Terms\")" msgstr "" #. type: Plain text @@ -3915,7 +4297,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/terms.md #, no-wrap msgid "Viewers" -msgstr "" +msgstr "مشاهدون" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md @@ -3924,7 +4306,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. On the top of the chat, there are terms and conditions for both the server and the channel.](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px \"Terms\")" msgstr "" #. type: Plain text @@ -3962,7 +4344,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #, no-wrap msgid "Joining chat rooms" -msgstr "" +msgstr "الإلتحاق بغرف المحادثة" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -3973,7 +4355,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/_index.md #: support/documentation/content/en/intro/_index.md -msgid "![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px \"Chat screenshot\")" msgstr "" #. type: Plain text @@ -3994,7 +4376,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat. In the participant list, there is John Livingston, and an anonymous account using \"Anonymous 212873\" nickname.](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px \"Chat with an anonymous user\")" msgstr "" #. type: Plain text @@ -4004,7 +4386,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Joining chat when not connected](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat. The current user is not logged in, and must choose a nickname before being able to write in the chat.](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px \"Joining chat when not connected\")" msgstr "" #. type: Title #### @@ -4057,14 +4439,14 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label login_using_external_account %}}\" dialog. There is a field where you can enter a Peertube url.](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px \"External login dialog\")" msgstr "" #. type: Title ## #: support/documentation/content/en/documentation/user/viewers.md #, no-wrap msgid "Chatting" -msgstr "" +msgstr "الدردشة" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4090,7 +4472,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #, no-wrap msgid "Participants list" -msgstr "" +msgstr "قائمة المشاركين" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4099,7 +4481,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Participants list](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with on the right the list of participants.](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px \"Participants list\")" msgstr "" #. type: Plain text @@ -4132,7 +4514,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/intro/_index.md -msgid "![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px \"Fullscreen chat screenshot\")" msgstr "" #. type: Plain text @@ -4144,7 +4526,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #, no-wrap msgid "Changing nickname" -msgstr "" +msgstr "تغيير الكنية" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4190,7 +4572,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share button](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat, with a share button on the top.](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px \"Share button\")" msgstr "" #. type: Plain text @@ -4200,12 +4582,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "Then, choose \"Connect using XMPP\":" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +msgid "Then, choose \"{{% livechat_label connect_using_xmpp %}}\":" msgstr "" #. type: Plain text @@ -4213,10 +4590,17 @@ msgstr "" msgid "Then you just have to click on \"open\" or copy/paste the address of the chat room into your XMPP client (using the \"join a room\" feature)." msgstr "" -#. type: Title # +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/_index.md +#, fuzzy, no-wrap +#| msgid "Documentation" +msgid "Peertube plugin livechat documentation" +msgstr "المستندات" + +#. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/_index.md #, no-wrap -msgid "PeerTube plugin livechat" +msgid "Peertube livechat" msgstr "" #. type: Plain text @@ -4284,7 +4668,7 @@ msgstr "" #: support/documentation/content/en/intro/_index.md #, no-wrap msgid "Installation" -msgstr "" +msgstr "التثبيت" #. type: Plain text #: support/documentation/content/en/intro/_index.md @@ -4293,7 +4677,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/intro/_index.md -msgid "![Livechat installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of Peertube plugins admin page. The search fields contains \"livechat\", and the search results show the livechat plugin.](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px \"Livechat installation\")" msgstr "" #. type: Title ## @@ -4372,7 +4756,7 @@ msgstr "" #: support/documentation/content/en/intro/_index.md #, no-wrap msgid "Other usages" -msgstr "" +msgstr "استخدامات أخرى" #. type: Plain text #: support/documentation/content/en/intro/_index.md diff --git a/support/documentation/po/livechat.ca.po b/support/documentation/po/livechat.ca.po index a721daee..869b9ee8 100644 --- a/support/documentation/po/livechat.ca.po +++ b/support/documentation/po/livechat.ca.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: peertube-plugin-livechat-documentation VERSION\n" -"POT-Creation-Date: 2024-07-10 16:54+0200\n" +"POT-Creation-Date: 2024-08-30 16:24+0200\n" "PO-Revision-Date: 2023-07-17 10:52+0000\n" "Last-Translator: Anonymous \n" "Language-Team: Catalan \n" @@ -430,7 +430,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md -msgid "The used theme is [hugo-theme-learn](https://learn.netlify.app/). You should read its documentation before starting editing the documentation." +msgid "The minimum required version for Hugo is 0.121.0. It was tested using version 0.132.2." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/contributing/document/_index.md +msgid "The used theme is [hugo-theme-relearn](https://mcshelby.github.io/hugo-theme-relearn). You should read its documentation before starting editing the documentation." msgstr "" #. type: Plain text @@ -1340,7 +1345,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md -msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx`" +msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx`" msgstr "" #. type: Plain text @@ -1704,13 +1709,13 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a \"{{% livechat_label login_using_external_account %}}\" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px \"{{% livechat_label login_using_external_account %}} button\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog with an \"OpenID Connect\" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px \"External login dialog - OpenID Connect\")" msgstr "" #. type: Plain text @@ -1856,6 +1861,98 @@ msgstr "" msgid "Admin documentation" msgstr "" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Advanced firewall rules for the Prosody server" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Prosody mod_firewall" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "This feature comes with the livechat plugin version 11.0.0." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Doing so, Peertube admins will be able to define advanced firewall rules." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "These rules could be used to run arbitrary code on the server. If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. You can still use mod_firewall by editing files directly on the server." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Edit rules" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "First, you must enable the feature in the [plugin settings](/peertube-plugin-livechat/documentation/admin/settings)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Just bellow the settings, you will find a \"Configure mod_firewall\" button. This button will open a configuration page." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "![Screenshot of the \"{{% livechat_label prosody_firewall_configuration %}}\" form.](/peertube-plugin-livechat/images/mod_firewall.png?classes=shadow,border&height=400px \"{{% livechat_label prosody_firewall_configuration %}}\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Here you can add several configuration files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can enable/disable each files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Files will be loaded in the alphabetical order. You can use a number as prefix to easily choose the order." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can also edit these firewall rules directly on the server, in the `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` directory. File names must only contains alphanumerical characters, underscores and hyphens. The extension must be `.pfw`, or `.pfw.disabled` if you want to disable a file. Please be sure that the peertube system user has write access to these files, else the web editing interface will fail. Once you have edited these files, you must reload prosody. This can be done by saving the plugin settings, or saving the mod_firewall configuration in the web interface, or by restarting Peertube." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "When you save the configuration, the server will automatically reload it, and your rules will apply immediatly. You can check that there is no parsing error in the Prosody error log. To do so, you can read the `plugins/data/peertube-plugin-livechat/prosody/prosody.err` file, or use the [diagnostic tool](/peertube-plugin-livechat/documentation/installation/troubleshooting/) that will show last Prosody errors." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Examples" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Don't hesitate to share your rules. To do so, you can for example edit this [page](/peertube-plugin-livechat/contributing/document/#write-documentation)." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap @@ -1995,7 +2092,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px)" +msgid "![Sepia avatar example](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px \"Sepia\")" msgstr "" #. type: Plain text @@ -2005,7 +2102,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Cats](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px)" +msgid "![Cats avatar example](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px \"Cats\")" msgstr "" #. type: Plain text @@ -2015,7 +2112,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Birds](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px)" +msgid "![Birds avatar example](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px \"Birds\")" msgstr "" #. type: Plain text @@ -2025,7 +2122,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px)" +msgid "![Fenecs avatar example](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px \"Fenecs\")" msgstr "" #. type: Plain text @@ -2035,7 +2132,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Abstracts](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px)" +msgid "![Abstracts avatar example](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px \"Abtracts\")" msgstr "" #. type: Plain text @@ -2045,7 +2142,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Legacy](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px)" +msgid "![Legacy avatar example](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px \"Legacy\")" msgstr "" #. type: Plain text @@ -2070,7 +2167,7 @@ msgstr "" #. type: Bullet: '- ' #: build/documentation/pot_in/documentation/admin/settings.md -msgid "ConverseJS concord theme: this is a theme provided by ConverseJS." +msgid "ConverseJS cyberpunk theme: this is a theme provided by ConverseJS." msgstr "" #. type: Title ## @@ -2125,6 +2222,11 @@ msgstr "" msgid "More informations on Prosody external components [here](https://prosody.im/doc/components)." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "For more information, please check [the documentation](/peertube-plugin-livechat/documentation/admin/mod_firewall/)." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/_index.md #, no-wrap @@ -2313,7 +2415,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Launch diagnostic](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the plugin's settings page, with a \"launch diagnostic\" button.](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px \"Launch diagnostic\")" msgstr "" #. type: Plain text @@ -2323,7 +2425,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Diagnostic result](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the diagnostic result page. This gives a lot of information, with status for different test suites.](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px \"Diagnostic result\")" msgstr "" #. type: Title ## @@ -2451,7 +2553,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px \"Embeding the chat in a live stream\")" msgstr "" #. type: Plain text @@ -2467,7 +2569,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, where the \"{{% livechat_label read_only %}}\" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px \"Share link popup\")" msgstr "" #. type: Plain text @@ -2478,7 +2580,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px \"Embeding the chat in OBS\")" msgstr "" #. type: Plain text @@ -2536,7 +2638,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable.\"](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px \"Share link popup - dock tab\")" msgstr "" #. type: Plain text @@ -2546,12 +2648,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock menu](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Dock menu, with a \"Custom Browser Docks\" entry.](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px \"OBS - Dock menu\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Custom Browser Docks dialog, with a new dock called \"My chat\".](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px \"OBS - Dock dialog\")" msgstr "" #. type: Plain text @@ -2561,7 +2663,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of OBS with a new dock including the chat. The user is logged in with their Peertube account, and can chat directly from OBS.](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px \"OBS - Dock\")" msgstr "" #. type: Plain text @@ -2625,7 +2727,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![New live](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the Peertube new live form.](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px \"New live\")" msgstr "" #. type: Plain text @@ -2635,7 +2737,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Activate the chat](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the form, with a \"{{% livechat_label use_chat %}}\" checkbox.](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px \"Activate the chat\")" msgstr "" #. type: Plain text @@ -2707,7 +2809,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label web %}} tab. There is a url you can copy.](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px \"Share link popup - web tab\")" msgstr "" #. type: Plain text @@ -2717,7 +2819,8 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - xmpp tab](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +#: support/documentation/content/en/documentation/user/xmpp_clients.md +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label connect_using_xmpp %}}\" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px \"{{% livechat_label connect_using_xmpp %}}\")" msgstr "" #. type: Title ## @@ -2765,7 +2868,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/basics.md #: build/documentation/pot_in/documentation/user/streamers/moderation.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px \"Chat menu\")" msgstr "" #. type: Plain text @@ -2775,7 +2878,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Configure chat room](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat configuration form.](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px \"Configure chat room\")" msgstr "" #. type: Plain text @@ -2823,7 +2926,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md -msgid "![Commands configuration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with some fields to configure the bot commands.](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px \"Commands configuration\")" msgstr "" #. type: Plain text @@ -2845,12 +2948,12 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Forbidden words configuration](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with several fields to configure the forbidden words.](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px \"Forbidden words configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Deleted message](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px)" +msgid "![Screenshot of a chat message that was deleted, with the following reason: \"No url allowed\".](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px \"Deleted message\")" msgstr "" #. type: Plain text @@ -2888,6 +2991,12 @@ msgstr "" msgid "These words are case insensitive." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md +#: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +msgid "You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them." +msgstr "" + #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md msgid "This features is still experimental. There might be some issues with non-latin alphabets. You can [open an issue](https://github.com/JohnXLivingston/peertube-plugin-livechat/issues) to report your problems." @@ -2925,7 +3034,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px \"Channel configuration\")" msgstr "" #. type: Plain text @@ -2962,7 +3071,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/quotes.md -msgid "![Timers configuration](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the channel options page, with some fields to configure a new timer.](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px \"Timers configuration\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -2984,7 +3093,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the chatrooms configuration page. The page list the user's channels.](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px \"Chatrooms menu\")" msgstr "" #. type: Plain text @@ -3057,12 +3166,12 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis configuration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the emoji configuration page. There is a form where you can add new emojis.](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session, with messages containing custom emojis. The emoji picker is open, and shows custom emojis.](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis\")" msgstr "" #. type: Title ### @@ -3129,6 +3238,7 @@ msgstr "" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #: support/documentation/content/en/documentation/user/streamers/tasks.md #: support/documentation/content/en/intro/_index.md @@ -3164,7 +3274,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel option form, with a field to configure the moderation delay.](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Moderation delay\")" msgstr "" #. type: Plain text @@ -3207,7 +3317,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Moderation delay timer](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border)" +msgid "![Screenshot of a chat message. A timer is displayed next to the message datetime. The timer is in seconds.](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border \"Moderation delay timer\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -3285,7 +3395,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the room configuration form. There is a \"{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}\" checkbox.](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px \"Room configuration / Mute anonymous users\")" msgstr "" #. type: Plain text @@ -3295,7 +3405,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. The current user has no message field. There is a message: \"{{% livechat_label muted_anonymous_message %}}\"](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px \"Room configuration / Muted anonymous users\")" msgstr "" #. type: Plain text @@ -3329,6 +3439,81 @@ msgstr "" msgid "You can promote users as moderators, if you need some help." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "It is possible to anonymize moderation actions, to avoid disclosing who is banning/kicking/… occupants." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the \"configure\" menu. In the form, you will find a \"{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}\" checkbox." +msgstr "" + +#. type: Title ## +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#, no-wrap +msgid "Participant message history search" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "As a room admin or owner, you can search all messages sent by a given participant." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To do so, you have several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "![Screenshot of a chat session. The moderator has open the message menu, and there is a \"{{% livechat_label search_occupant_message %}}\" button.](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px \"Message history search\")" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "To have more space and better readability, open the chat in full-page mode." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "In the search results, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "if the current nickname is different than the nickname when the participant has sent the message, the original nickname will be shown" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "The search result will also include all messages related to participants who had the same nickname. You can differenciate them by comparing [JID](https://xmpp.org/extensions/xep-0029.html) and [occupant-id](https://xmpp.org/extensions/xep-0421.html)." +msgstr "" + #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap @@ -3361,6 +3546,230 @@ msgstr "" msgid "From there, you can also promote yourself as room moderator by using the \"{{% livechat_label promote %}}\" button on the right." msgstr "" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Plugin peertube-plugin-livechat moderation notes" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Moderation notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The livechat plugin includes a Moderator Notes Application: you can write some notes, that could be associated to chat participants. Every room's admins have access to these notes, so they can edit them collaboratively." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can for example use this Application to:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "share some notes between moderators" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "take notes about participants that were kicked or caused troubles" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "..." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Using the Moderator Notes Application" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Opening the Moderator Notes Application" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "To open the Moderator Notes Application, there is a \"{{% livechat_label \"moderator_notes\" %}}\" button in the top chat menu:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_video.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Clicking this button will toggle the Application display:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/polls.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +#, no-wrap +msgid "Access rights" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Every room's admins have access to this Application (read and write access)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you promote someone as room admin or owner, they gets instant access to this Application. When you remove admin or owner rights to someone, they instantly lose access to this Application." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Scope" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Notes are only available in the room in which you have created them." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Chatrooms can be releated to video or channel. If you want to keep notes from one video to another, please consider using rooms associated to channels." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Currently the video vs channel rooms is an instance-wide settings. Only Peertube admins can change it, and it applies to all chatrooms. In the future, this choice will be added in your channel's options." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes" +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Create/Edit Notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can use the plus button on the top to create a new note. You can also edit existing notes using the edit button, or delate any note." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can create a note associated to a participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When a note is associated to a participant, you will see their nickname and avatar on the top of the note." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes filtering" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can filter notes to find all notes related to a given participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button that is available on notes to find all notes related to the same participant" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can remove the filter by clicking on the close button." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of the note application, with a filter enabled for user \"Mike\". The only notes that are shown are the notes for the Mike user.](/peertube-plugin-livechat/images/moderation_notes_filters.png?classes=shadow,border&height=200px \"Moderator Notes Application - filtering\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you filters notes on a participant, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "if the current nickname is different than the nickname when you created the note, the original nickname will be shown" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The search result will also include all notes related to participants who had the same nickname. So you can also take note for anonymous users (who don't have any consistent JID or occupant-id). You can differenciate them by comparing JID and occupant-id." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Sorting notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can sort notes simply using drag & drop." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/polls.md #, no-wrap @@ -3386,7 +3795,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a \"{{% livechat_label new_poll %}}\" form. The form contains several fields: question, duration, choices, …](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px \"Poll form\")" msgstr "" #. type: Plain text @@ -3440,13 +3849,6 @@ msgstr "" msgid "If there was a previous unfinished poll, it will end and its result will be shown." msgstr "" -#. type: Title ### -#: support/documentation/content/en/documentation/user/streamers/polls.md -#: support/documentation/content/en/documentation/user/streamers/tasks.md -#, no-wrap -msgid "Access rights" -msgstr "" - #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md msgid "Every room's admins can create a new poll." @@ -3485,7 +3887,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. In the chat, there is a message with the poll question, and the different choices. There is also a banner on the top of the chat, where you can see the question, and the number of votes for each answers.](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px \"Poll start\")" msgstr "" #. type: Plain text @@ -3505,7 +3907,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll votes](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with an ongoing poll. The current user has just voted by sending \"!1\".](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px \"Poll votes\")" msgstr "" #. type: Plain text @@ -3530,7 +3932,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with poll that has ended. The banner no more accept new votes. There is a message in the chat with the poll results. For each choice, there is the number of votes, and the percentage of the total it represents.](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px \"Poll end\")" msgstr "" #. type: Plain text @@ -3598,7 +4000,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Channel configuration / Slow Mode](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a slow mode field.](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Slow Mode\")" msgstr "" #. type: Plain text @@ -3625,7 +4027,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. There is a banner on the bottom of the chat, indicating that the slow mode is enabled, and that users can send a message every 2 seconds.](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px \"Slow mode infobox\")" msgstr "" #. type: Plain text @@ -3670,11 +4072,6 @@ msgstr "" msgid "highlight questions from your viewers, so you can come back to them later without forgetting to answer them" msgstr "" -#. type: Bullet: '* ' -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "..." -msgstr "" - #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap @@ -3694,12 +4091,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text @@ -3709,17 +4106,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "To have more space and better readability, open the chat in full-page mode." +msgid "![Screenshot of a Peertube chat, fullscreen. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text @@ -3755,12 +4147,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task lists](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgid "![Screenshot of a chat session, with the Task application. There are several task lists.](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px \"Task lists\")" msgstr "" #. type: Title ### @@ -3782,12 +4169,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task form](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, there is a form to create a new task.](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px \"Task form\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, a new task was created.](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Title #### @@ -3808,7 +4195,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under task lists, there are several tasks. Some of them are checked, other not.](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px \"Tasks\")" msgstr "" #. type: Title #### @@ -3824,12 +4211,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to sort](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another.](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px \"Drag and drop to sort\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to move to another list](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another task list.](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px \"Drag and drop to move to another list\")" msgstr "" #. type: Title #### @@ -3845,17 +4232,17 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Create task from message](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. The menu besides a message is open, with a button to create a new task.](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px \"Create task from message\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Choose the task list](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog, where you can choose in which task list you want to add the new task.](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px \"Choose the task list\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. A new task was added in the \"chat questions\" task list, with the user's nickname, and the message as content.](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Plain text @@ -3893,7 +4280,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Channel configuration / Terms](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a field to configure your terms and conditions.](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px \"Channel configuration / Terms\")" msgstr "" #. type: Plain text @@ -3914,7 +4301,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. On the top of the chat, there are terms and conditions for both the server and the channel.](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px \"Terms\")" msgstr "" #. type: Plain text @@ -3963,7 +4350,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/_index.md #: support/documentation/content/en/intro/_index.md -msgid "![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px \"Chat screenshot\")" msgstr "" #. type: Plain text @@ -3984,7 +4371,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat. In the participant list, there is John Livingston, and an anonymous account using \"Anonymous 212873\" nickname.](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px \"Chat with an anonymous user\")" msgstr "" #. type: Plain text @@ -3994,7 +4381,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Joining chat when not connected](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat. The current user is not logged in, and must choose a nickname before being able to write in the chat.](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px \"Joining chat when not connected\")" msgstr "" #. type: Title #### @@ -4047,7 +4434,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label login_using_external_account %}}\" dialog. There is a field where you can enter a Peertube url.](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px \"External login dialog\")" msgstr "" #. type: Title ## @@ -4089,7 +4476,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Participants list](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with on the right the list of participants.](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px \"Participants list\")" msgstr "" #. type: Plain text @@ -4122,7 +4509,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/intro/_index.md -msgid "![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px \"Fullscreen chat screenshot\")" msgstr "" #. type: Plain text @@ -4180,7 +4567,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share button](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat, with a share button on the top.](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px \"Share button\")" msgstr "" #. type: Plain text @@ -4190,12 +4577,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "Then, choose \"Connect using XMPP\":" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +msgid "Then, choose \"{{% livechat_label connect_using_xmpp %}}\":" msgstr "" #. type: Plain text @@ -4203,10 +4585,16 @@ msgstr "" msgid "Then you just have to click on \"open\" or copy/paste the address of the chat room into your XMPP client (using the \"join a room\" feature)." msgstr "" -#. type: Title # +#. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/_index.md #, no-wrap -msgid "PeerTube plugin livechat" +msgid "Peertube plugin livechat documentation" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/_index.md +#, no-wrap +msgid "Peertube livechat" msgstr "" #. type: Plain text @@ -4283,7 +4671,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/intro/_index.md -msgid "![Livechat installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of Peertube plugins admin page. The search fields contains \"livechat\", and the search results show the livechat plugin.](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px \"Livechat installation\")" msgstr "" #. type: Title ## diff --git a/support/documentation/po/livechat.cs.po b/support/documentation/po/livechat.cs.po index 55c06eb1..50a6cd72 100644 --- a/support/documentation/po/livechat.cs.po +++ b/support/documentation/po/livechat.cs.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: peertube-plugin-livechat-documentation VERSION\n" -"POT-Creation-Date: 2024-07-10 16:54+0200\n" +"POT-Creation-Date: 2024-08-30 16:24+0200\n" "PO-Revision-Date: 2023-07-17 10:52+0000\n" "Last-Translator: Anonymous \n" "Language-Team: Czech \n" @@ -430,7 +430,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md -msgid "The used theme is [hugo-theme-learn](https://learn.netlify.app/). You should read its documentation before starting editing the documentation." +msgid "The minimum required version for Hugo is 0.121.0. It was tested using version 0.132.2." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/contributing/document/_index.md +msgid "The used theme is [hugo-theme-relearn](https://mcshelby.github.io/hugo-theme-relearn). You should read its documentation before starting editing the documentation." msgstr "" #. type: Plain text @@ -1340,7 +1345,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md -msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx`" +msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx`" msgstr "" #. type: Plain text @@ -1704,13 +1709,13 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a \"{{% livechat_label login_using_external_account %}}\" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px \"{{% livechat_label login_using_external_account %}} button\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog with an \"OpenID Connect\" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px \"External login dialog - OpenID Connect\")" msgstr "" #. type: Plain text @@ -1856,6 +1861,98 @@ msgstr "" msgid "Admin documentation" msgstr "" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Advanced firewall rules for the Prosody server" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Prosody mod_firewall" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "This feature comes with the livechat plugin version 11.0.0." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Doing so, Peertube admins will be able to define advanced firewall rules." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "These rules could be used to run arbitrary code on the server. If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. You can still use mod_firewall by editing files directly on the server." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Edit rules" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "First, you must enable the feature in the [plugin settings](/peertube-plugin-livechat/documentation/admin/settings)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Just bellow the settings, you will find a \"Configure mod_firewall\" button. This button will open a configuration page." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "![Screenshot of the \"{{% livechat_label prosody_firewall_configuration %}}\" form.](/peertube-plugin-livechat/images/mod_firewall.png?classes=shadow,border&height=400px \"{{% livechat_label prosody_firewall_configuration %}}\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Here you can add several configuration files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can enable/disable each files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Files will be loaded in the alphabetical order. You can use a number as prefix to easily choose the order." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can also edit these firewall rules directly on the server, in the `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` directory. File names must only contains alphanumerical characters, underscores and hyphens. The extension must be `.pfw`, or `.pfw.disabled` if you want to disable a file. Please be sure that the peertube system user has write access to these files, else the web editing interface will fail. Once you have edited these files, you must reload prosody. This can be done by saving the plugin settings, or saving the mod_firewall configuration in the web interface, or by restarting Peertube." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "When you save the configuration, the server will automatically reload it, and your rules will apply immediatly. You can check that there is no parsing error in the Prosody error log. To do so, you can read the `plugins/data/peertube-plugin-livechat/prosody/prosody.err` file, or use the [diagnostic tool](/peertube-plugin-livechat/documentation/installation/troubleshooting/) that will show last Prosody errors." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Examples" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Don't hesitate to share your rules. To do so, you can for example edit this [page](/peertube-plugin-livechat/contributing/document/#write-documentation)." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap @@ -1995,7 +2092,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px)" +msgid "![Sepia avatar example](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px \"Sepia\")" msgstr "" #. type: Plain text @@ -2005,7 +2102,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Cats](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px)" +msgid "![Cats avatar example](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px \"Cats\")" msgstr "" #. type: Plain text @@ -2015,7 +2112,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Birds](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px)" +msgid "![Birds avatar example](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px \"Birds\")" msgstr "" #. type: Plain text @@ -2025,7 +2122,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px)" +msgid "![Fenecs avatar example](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px \"Fenecs\")" msgstr "" #. type: Plain text @@ -2035,7 +2132,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Abstracts](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px)" +msgid "![Abstracts avatar example](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px \"Abtracts\")" msgstr "" #. type: Plain text @@ -2045,7 +2142,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Legacy](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px)" +msgid "![Legacy avatar example](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px \"Legacy\")" msgstr "" #. type: Plain text @@ -2070,7 +2167,7 @@ msgstr "" #. type: Bullet: '- ' #: build/documentation/pot_in/documentation/admin/settings.md -msgid "ConverseJS concord theme: this is a theme provided by ConverseJS." +msgid "ConverseJS cyberpunk theme: this is a theme provided by ConverseJS." msgstr "" #. type: Title ## @@ -2125,6 +2222,11 @@ msgstr "" msgid "More informations on Prosody external components [here](https://prosody.im/doc/components)." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "For more information, please check [the documentation](/peertube-plugin-livechat/documentation/admin/mod_firewall/)." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/_index.md #, no-wrap @@ -2313,7 +2415,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Launch diagnostic](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the plugin's settings page, with a \"launch diagnostic\" button.](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px \"Launch diagnostic\")" msgstr "" #. type: Plain text @@ -2323,7 +2425,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Diagnostic result](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the diagnostic result page. This gives a lot of information, with status for different test suites.](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px \"Diagnostic result\")" msgstr "" #. type: Title ## @@ -2451,7 +2553,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px \"Embeding the chat in a live stream\")" msgstr "" #. type: Plain text @@ -2467,7 +2569,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, where the \"{{% livechat_label read_only %}}\" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px \"Share link popup\")" msgstr "" #. type: Plain text @@ -2478,7 +2580,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px \"Embeding the chat in OBS\")" msgstr "" #. type: Plain text @@ -2536,7 +2638,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable.\"](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px \"Share link popup - dock tab\")" msgstr "" #. type: Plain text @@ -2546,12 +2648,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock menu](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Dock menu, with a \"Custom Browser Docks\" entry.](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px \"OBS - Dock menu\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Custom Browser Docks dialog, with a new dock called \"My chat\".](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px \"OBS - Dock dialog\")" msgstr "" #. type: Plain text @@ -2561,7 +2663,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of OBS with a new dock including the chat. The user is logged in with their Peertube account, and can chat directly from OBS.](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px \"OBS - Dock\")" msgstr "" #. type: Plain text @@ -2625,7 +2727,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![New live](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the Peertube new live form.](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px \"New live\")" msgstr "" #. type: Plain text @@ -2635,7 +2737,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Activate the chat](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the form, with a \"{{% livechat_label use_chat %}}\" checkbox.](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px \"Activate the chat\")" msgstr "" #. type: Plain text @@ -2707,7 +2809,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label web %}} tab. There is a url you can copy.](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px \"Share link popup - web tab\")" msgstr "" #. type: Plain text @@ -2717,7 +2819,8 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - xmpp tab](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +#: support/documentation/content/en/documentation/user/xmpp_clients.md +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label connect_using_xmpp %}}\" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px \"{{% livechat_label connect_using_xmpp %}}\")" msgstr "" #. type: Title ## @@ -2765,7 +2868,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/basics.md #: build/documentation/pot_in/documentation/user/streamers/moderation.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px \"Chat menu\")" msgstr "" #. type: Plain text @@ -2775,7 +2878,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Configure chat room](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat configuration form.](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px \"Configure chat room\")" msgstr "" #. type: Plain text @@ -2823,7 +2926,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md -msgid "![Commands configuration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with some fields to configure the bot commands.](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px \"Commands configuration\")" msgstr "" #. type: Plain text @@ -2845,12 +2948,12 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Forbidden words configuration](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with several fields to configure the forbidden words.](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px \"Forbidden words configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Deleted message](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px)" +msgid "![Screenshot of a chat message that was deleted, with the following reason: \"No url allowed\".](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px \"Deleted message\")" msgstr "" #. type: Plain text @@ -2888,6 +2991,12 @@ msgstr "" msgid "These words are case insensitive." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md +#: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +msgid "You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them." +msgstr "" + #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md msgid "This features is still experimental. There might be some issues with non-latin alphabets. You can [open an issue](https://github.com/JohnXLivingston/peertube-plugin-livechat/issues) to report your problems." @@ -2925,7 +3034,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px \"Channel configuration\")" msgstr "" #. type: Plain text @@ -2962,7 +3071,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/quotes.md -msgid "![Timers configuration](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the channel options page, with some fields to configure a new timer.](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px \"Timers configuration\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -2984,7 +3093,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the chatrooms configuration page. The page list the user's channels.](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px \"Chatrooms menu\")" msgstr "" #. type: Plain text @@ -3057,12 +3166,12 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis configuration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the emoji configuration page. There is a form where you can add new emojis.](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session, with messages containing custom emojis. The emoji picker is open, and shows custom emojis.](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis\")" msgstr "" #. type: Title ### @@ -3129,6 +3238,7 @@ msgstr "" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #: support/documentation/content/en/documentation/user/streamers/tasks.md #: support/documentation/content/en/intro/_index.md @@ -3164,7 +3274,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel option form, with a field to configure the moderation delay.](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Moderation delay\")" msgstr "" #. type: Plain text @@ -3207,7 +3317,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Moderation delay timer](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border)" +msgid "![Screenshot of a chat message. A timer is displayed next to the message datetime. The timer is in seconds.](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border \"Moderation delay timer\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -3285,7 +3395,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the room configuration form. There is a \"{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}\" checkbox.](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px \"Room configuration / Mute anonymous users\")" msgstr "" #. type: Plain text @@ -3295,7 +3405,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. The current user has no message field. There is a message: \"{{% livechat_label muted_anonymous_message %}}\"](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px \"Room configuration / Muted anonymous users\")" msgstr "" #. type: Plain text @@ -3329,6 +3439,81 @@ msgstr "" msgid "You can promote users as moderators, if you need some help." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "It is possible to anonymize moderation actions, to avoid disclosing who is banning/kicking/… occupants." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the \"configure\" menu. In the form, you will find a \"{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}\" checkbox." +msgstr "" + +#. type: Title ## +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#, no-wrap +msgid "Participant message history search" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "As a room admin or owner, you can search all messages sent by a given participant." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To do so, you have several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "![Screenshot of a chat session. The moderator has open the message menu, and there is a \"{{% livechat_label search_occupant_message %}}\" button.](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px \"Message history search\")" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "To have more space and better readability, open the chat in full-page mode." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "In the search results, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "if the current nickname is different than the nickname when the participant has sent the message, the original nickname will be shown" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "The search result will also include all messages related to participants who had the same nickname. You can differenciate them by comparing [JID](https://xmpp.org/extensions/xep-0029.html) and [occupant-id](https://xmpp.org/extensions/xep-0421.html)." +msgstr "" + #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap @@ -3361,6 +3546,230 @@ msgstr "" msgid "From there, you can also promote yourself as room moderator by using the \"{{% livechat_label promote %}}\" button on the right." msgstr "" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Plugin peertube-plugin-livechat moderation notes" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Moderation notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The livechat plugin includes a Moderator Notes Application: you can write some notes, that could be associated to chat participants. Every room's admins have access to these notes, so they can edit them collaboratively." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can for example use this Application to:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "share some notes between moderators" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "take notes about participants that were kicked or caused troubles" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "..." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Using the Moderator Notes Application" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Opening the Moderator Notes Application" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "To open the Moderator Notes Application, there is a \"{{% livechat_label \"moderator_notes\" %}}\" button in the top chat menu:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_video.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Clicking this button will toggle the Application display:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/polls.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +#, no-wrap +msgid "Access rights" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Every room's admins have access to this Application (read and write access)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you promote someone as room admin or owner, they gets instant access to this Application. When you remove admin or owner rights to someone, they instantly lose access to this Application." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Scope" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Notes are only available in the room in which you have created them." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Chatrooms can be releated to video or channel. If you want to keep notes from one video to another, please consider using rooms associated to channels." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Currently the video vs channel rooms is an instance-wide settings. Only Peertube admins can change it, and it applies to all chatrooms. In the future, this choice will be added in your channel's options." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes" +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Create/Edit Notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can use the plus button on the top to create a new note. You can also edit existing notes using the edit button, or delate any note." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can create a note associated to a participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When a note is associated to a participant, you will see their nickname and avatar on the top of the note." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes filtering" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can filter notes to find all notes related to a given participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button that is available on notes to find all notes related to the same participant" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can remove the filter by clicking on the close button." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of the note application, with a filter enabled for user \"Mike\". The only notes that are shown are the notes for the Mike user.](/peertube-plugin-livechat/images/moderation_notes_filters.png?classes=shadow,border&height=200px \"Moderator Notes Application - filtering\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you filters notes on a participant, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "if the current nickname is different than the nickname when you created the note, the original nickname will be shown" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The search result will also include all notes related to participants who had the same nickname. So you can also take note for anonymous users (who don't have any consistent JID or occupant-id). You can differenciate them by comparing JID and occupant-id." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Sorting notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can sort notes simply using drag & drop." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/polls.md #, no-wrap @@ -3386,7 +3795,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a \"{{% livechat_label new_poll %}}\" form. The form contains several fields: question, duration, choices, …](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px \"Poll form\")" msgstr "" #. type: Plain text @@ -3440,13 +3849,6 @@ msgstr "" msgid "If there was a previous unfinished poll, it will end and its result will be shown." msgstr "" -#. type: Title ### -#: support/documentation/content/en/documentation/user/streamers/polls.md -#: support/documentation/content/en/documentation/user/streamers/tasks.md -#, no-wrap -msgid "Access rights" -msgstr "" - #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md msgid "Every room's admins can create a new poll." @@ -3485,7 +3887,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. In the chat, there is a message with the poll question, and the different choices. There is also a banner on the top of the chat, where you can see the question, and the number of votes for each answers.](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px \"Poll start\")" msgstr "" #. type: Plain text @@ -3505,7 +3907,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll votes](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with an ongoing poll. The current user has just voted by sending \"!1\".](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px \"Poll votes\")" msgstr "" #. type: Plain text @@ -3530,7 +3932,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with poll that has ended. The banner no more accept new votes. There is a message in the chat with the poll results. For each choice, there is the number of votes, and the percentage of the total it represents.](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px \"Poll end\")" msgstr "" #. type: Plain text @@ -3598,7 +4000,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Channel configuration / Slow Mode](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a slow mode field.](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Slow Mode\")" msgstr "" #. type: Plain text @@ -3625,7 +4027,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. There is a banner on the bottom of the chat, indicating that the slow mode is enabled, and that users can send a message every 2 seconds.](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px \"Slow mode infobox\")" msgstr "" #. type: Plain text @@ -3670,11 +4072,6 @@ msgstr "" msgid "highlight questions from your viewers, so you can come back to them later without forgetting to answer them" msgstr "" -#. type: Bullet: '* ' -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "..." -msgstr "" - #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap @@ -3694,12 +4091,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text @@ -3709,17 +4106,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "To have more space and better readability, open the chat in full-page mode." +msgid "![Screenshot of a Peertube chat, fullscreen. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text @@ -3755,12 +4147,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task lists](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgid "![Screenshot of a chat session, with the Task application. There are several task lists.](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px \"Task lists\")" msgstr "" #. type: Title ### @@ -3782,12 +4169,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task form](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, there is a form to create a new task.](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px \"Task form\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, a new task was created.](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Title #### @@ -3808,7 +4195,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under task lists, there are several tasks. Some of them are checked, other not.](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px \"Tasks\")" msgstr "" #. type: Title #### @@ -3824,12 +4211,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to sort](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another.](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px \"Drag and drop to sort\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to move to another list](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another task list.](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px \"Drag and drop to move to another list\")" msgstr "" #. type: Title #### @@ -3845,17 +4232,17 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Create task from message](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. The menu besides a message is open, with a button to create a new task.](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px \"Create task from message\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Choose the task list](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog, where you can choose in which task list you want to add the new task.](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px \"Choose the task list\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. A new task was added in the \"chat questions\" task list, with the user's nickname, and the message as content.](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Plain text @@ -3893,7 +4280,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Channel configuration / Terms](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a field to configure your terms and conditions.](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px \"Channel configuration / Terms\")" msgstr "" #. type: Plain text @@ -3914,7 +4301,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. On the top of the chat, there are terms and conditions for both the server and the channel.](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px \"Terms\")" msgstr "" #. type: Plain text @@ -3963,7 +4350,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/_index.md #: support/documentation/content/en/intro/_index.md -msgid "![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px \"Chat screenshot\")" msgstr "" #. type: Plain text @@ -3984,7 +4371,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat. In the participant list, there is John Livingston, and an anonymous account using \"Anonymous 212873\" nickname.](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px \"Chat with an anonymous user\")" msgstr "" #. type: Plain text @@ -3994,7 +4381,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Joining chat when not connected](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat. The current user is not logged in, and must choose a nickname before being able to write in the chat.](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px \"Joining chat when not connected\")" msgstr "" #. type: Title #### @@ -4047,7 +4434,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label login_using_external_account %}}\" dialog. There is a field where you can enter a Peertube url.](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px \"External login dialog\")" msgstr "" #. type: Title ## @@ -4089,7 +4476,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Participants list](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with on the right the list of participants.](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px \"Participants list\")" msgstr "" #. type: Plain text @@ -4122,7 +4509,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/intro/_index.md -msgid "![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px \"Fullscreen chat screenshot\")" msgstr "" #. type: Plain text @@ -4180,7 +4567,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share button](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat, with a share button on the top.](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px \"Share button\")" msgstr "" #. type: Plain text @@ -4190,12 +4577,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "Then, choose \"Connect using XMPP\":" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +msgid "Then, choose \"{{% livechat_label connect_using_xmpp %}}\":" msgstr "" #. type: Plain text @@ -4203,10 +4585,16 @@ msgstr "" msgid "Then you just have to click on \"open\" or copy/paste the address of the chat room into your XMPP client (using the \"join a room\" feature)." msgstr "" -#. type: Title # +#. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/_index.md #, no-wrap -msgid "PeerTube plugin livechat" +msgid "Peertube plugin livechat documentation" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/_index.md +#, no-wrap +msgid "Peertube livechat" msgstr "" #. type: Plain text @@ -4283,7 +4671,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/intro/_index.md -msgid "![Livechat installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of Peertube plugins admin page. The search fields contains \"livechat\", and the search results show the livechat plugin.](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px \"Livechat installation\")" msgstr "" #. type: Title ## diff --git a/support/documentation/po/livechat.de.po b/support/documentation/po/livechat.de.po index b14f8ae4..97bd10c3 100644 --- a/support/documentation/po/livechat.de.po +++ b/support/documentation/po/livechat.de.po @@ -7,16 +7,18 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-07-10 16:54+0200\n" -"PO-Revision-Date: 2024-07-05 19:12+0000\n" -"Last-Translator: Victor Hampel \n" -"Language-Team: German \n" +"POT-Creation-Date: 2024-08-30 16:24+0200\n" +"PO-Revision-Date: 2024-08-31 17:22+0000\n" +"Last-Translator: Victor Hampel " +"\n" +"Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Weblate 5.6.2\n" +"X-Generator: Weblate 5.7\n" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/contact/_index.md @@ -457,8 +459,18 @@ msgstr "Die Dokumentation wird mit [Hugo](https://gohugo.io/) erstellt. Sie mü #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md -msgid "The used theme is [hugo-theme-learn](https://learn.netlify.app/). You should read its documentation before starting editing the documentation." -msgstr "Das verwendete Thema ist [hugo-theme-learn](https://learn.netlify.app/). Sie sollten dessen Dokumentation lesen, bevor Sie mit der Bearbeitung der Dokumentation beginnen." +msgid "The minimum required version for Hugo is 0.121.0. It was tested using version 0.132.2." +msgstr "" +"Die erforderliche Mindestversion für Hugo ist 0.121.0. Es wurde mit Version " +"0.132.2 getestet." + +#. type: Plain text +#: support/documentation/content/en/contributing/document/_index.md +msgid "The used theme is [hugo-theme-relearn](https://mcshelby.github.io/hugo-theme-relearn). You should read its documentation before starting editing the documentation." +msgstr "" +"Das verwendete Thema ist [hugo-theme-learn](https://mcshelby.github.io/hugo-" +"theme-relearn). Sie sollten dessen Dokumentation lesen, bevor Sie mit der " +"Bearbeitung der Dokumentation beginnen." #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md @@ -1386,8 +1398,8 @@ msgstr "Sie müssen diese Zertifikate dann in einem Ordner ablegen, auf den der #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md -msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx`" -msgstr "Wenn Sie das Programm ProsodyCtl verwenden möchten, um Zertifikate zu importieren, ist es (sobald Peertube gestartet ist) mit folgendem Befehl verfügbar (passen Sie den Pfad zu Ihrem Peertube-Datenordner an und ersetzen Sie \"xxx\" durch die Argumente, die Sie an prosodyctl übergeben wollen): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx`" +msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx`" +msgstr "Wenn Sie das Programm ProsodyCtl verwenden möchten, um Zertifikate zu importieren, ist es (sobald Peertube gestartet ist) mit folgendem Befehl verfügbar (passen Sie den Pfad zu Ihrem Peertube-Datenordner an und ersetzen Sie \"xxx\" durch die Argumente, die Sie an prosodyctl übergeben wollen): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx`" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -1767,14 +1779,24 @@ msgstr "Dies ermöglicht es den Nutzern auch, dem Chat beizutreten, ohne ein Pee #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" -msgstr "![Externes Anmelden Schaltfläche](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a \"{{% livechat_label login_using_external_account %}}\" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px \"{{% livechat_label login_using_external_account %}} button\")" +msgstr "" +"![Screenshot einer Peertube-Videoseite, mit einem Chat auf der rechten " +"Seite. Am unteren Ende des Chats befindet sich die Schaltfläche \"{{% " +"livechat_label login_using_external_account %}}\".](/peertube-" +"plugin-livechat/images/" +"external_login_button.png?classes=shadow,border&height=200px \"{{% " +"livechat_label login_using_external_account %}} button\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px)" -msgstr "![Externer Anmeldedialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog with an \"OpenID Connect\" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px \"External login dialog - OpenID Connect\")" +msgstr "" +"![Screenshot eines Dialogs mit einer Schaltfläche \"OpenID Connect\"" +".](/peertube-plugin-livechat/images/" +"external_login_dialog_oidc.png?classes=shadow,border&height=200px \"Externer " +"Anmeldedialog - OpenID Connect\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md @@ -1921,6 +1943,102 @@ msgstr "Plugin Peertube Livechat Administation" msgid "Admin documentation" msgstr "Admin Dokumentation" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Advanced firewall rules for the Prosody server" +msgstr "Erweiterte Firewall-Regeln für den Prosody-Server" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Prosody mod_firewall" +msgstr "Prosody mod_firewall" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "This feature comes with the livechat plugin version 11.0.0." +msgstr "Diese Funktion wird mit dem Livechatplugin Version 11.0.0 verfügbar sein." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server." +msgstr "Sie können [mod_firewall](https://modules.prosody.im/mod_firewall) auf Ihrem Prosody-Server aktivieren." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Doing so, Peertube admins will be able to define advanced firewall rules." +msgstr "Auf diese Weise können Peertube-Administratoren erweiterte Firewall-Regeln definieren." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "These rules could be used to run arbitrary code on the server. If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. You can still use mod_firewall by editing files directly on the server." +msgstr "Diese Regeln könnten verwendet werden, um beliebigen Code auf dem Server auszuführen. Wenn Sie ein Hosting-Anbieter sind und Peertube-Administratoren nicht erlauben wollen, solche Regeln zu schreiben, können Sie die Online-Bearbeitung deaktivieren, indem Sie eine `disable_mod_firewall_editing`-Datei im Plugin-Verzeichnis erstellen (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). Dies ist ein Opt-Out, da Peertube-Administratoren bereits beliebigen Code ausführen können, indem sie ein beliebiges Plugin installieren. Sie können mod_firewall immer noch verwenden, indem Sie Dateien direkt auf dem Server bearbeiten." + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Edit rules" +msgstr "Regeln bearbeiten" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "First, you must enable the feature in the [plugin settings](/peertube-plugin-livechat/documentation/admin/settings)." +msgstr "Zuerst müssen Sie die Funktion in den [Plugin-Einstellungen](/peertube-plugin-livechat/de/documentation/admin/settings) aktivieren." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Just bellow the settings, you will find a \"Configure mod_firewall\" button. This button will open a configuration page." +msgstr "Direkt unter den Einstellungen finden Sie die Schaltfläche \"Configure mod_firewall\". Mit dieser Schaltfläche wird eine Konfigurationsseite geöffnet." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "![Screenshot of the \"{{% livechat_label prosody_firewall_configuration %}}\" form.](/peertube-plugin-livechat/images/mod_firewall.png?classes=shadow,border&height=400px \"{{% livechat_label prosody_firewall_configuration %}}\")" +msgstr "" +"![Screenshot des Formulars \"{{% livechat_label " +"prosody_firewall_configuration %}}\".](/peertube-plugin-livechat/images/" +"mod_firewall.png?classes=shadow,border&height=400px \"{{% livechat_label " +"prosody_firewall_configuration %}}\")" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Here you can add several configuration files." +msgstr "Hier können Sie mehrere Konfigurationsdateien hinzufügen." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can enable/disable each files." +msgstr "Sie können jede Datei aktivieren/deaktivieren." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Files will be loaded in the alphabetical order. You can use a number as prefix to easily choose the order." +msgstr "Die Dateien werden in alphabetischer Reihenfolge geladen. Sie können eine Zahl als Präfix verwenden, um die Reihenfolge einfach zu wählen." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can also edit these firewall rules directly on the server, in the `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` directory. File names must only contains alphanumerical characters, underscores and hyphens. The extension must be `.pfw`, or `.pfw.disabled` if you want to disable a file. Please be sure that the peertube system user has write access to these files, else the web editing interface will fail. Once you have edited these files, you must reload prosody. This can be done by saving the plugin settings, or saving the mod_firewall configuration in the web interface, or by restarting Peertube." +msgstr "Sie können diese Firewall-Regeln auch direkt auf dem Server im Verzeichnis `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` bearbeiten. Die Dateinamen dürfen nur alphanumerische Zeichen, Unterstriche und Bindestriche enthalten. Die Erweiterung muss `.pfw` sein, oder `.pfw.disabled`, wenn Sie eine Datei deaktivieren wollen. Vergewissern Sie sich, dass der Peertube-Systembenutzer Schreibrechte für diese Dateien hat, sonst schlägt die Bearbeitung über die Webschnittstelle fehl. Nachdem Sie diese Dateien bearbeitet haben, müssen Sie prosody neu laden. Dies kann durch Speichern der Plugin-Einstellungen, durch Speichern der mod_firewall-Konfiguration im Web-Interface oder durch einen Neustart von Peertube geschehen." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "When you save the configuration, the server will automatically reload it, and your rules will apply immediatly. You can check that there is no parsing error in the Prosody error log. To do so, you can read the `plugins/data/peertube-plugin-livechat/prosody/prosody.err` file, or use the [diagnostic tool](/peertube-plugin-livechat/documentation/installation/troubleshooting/) that will show last Prosody errors." +msgstr "Wenn Sie die Konfiguration speichern, wird der Server sie automatisch neu laden, und Ihre Regeln werden sofort angewendet. Sie können im Prosody-Fehlerprotokoll überprüfen, ob ein Parsing-Fehler aufgetreten ist. Dazu können Sie die Datei `plugins/data/peertube-plugin-livechat/prosody/prosody.err` lesen oder das [Diagnose-Tool](/peertube-plugin-livechat/de/documentation/installation/troubleshooting/) verwenden, das die letzten Prosody-Fehler anzeigt." + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Examples" +msgstr "Beispiele" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Don't hesitate to share your rules. To do so, you can for example edit this [page](/peertube-plugin-livechat/contributing/document/#write-documentation)." +msgstr "Zögern Sie nicht, Ihre Regeln zu teilen. Um dies zu tun, können Sie zum Beispiel diese [Seite](/peertube-plugin-livechat/contributing/document/#write-documentation) bearbeiten." + #. type: Yaml Front Matter Hash Value: description #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap @@ -2063,8 +2181,10 @@ msgstr "{{% livechat_label avatar_set_option_sepia %}}: [David Revoy's Peertube #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px)" -msgstr "![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px)" +msgid "![Sepia avatar example](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px \"Sepia\")" +msgstr "" +"![Sepia Avatar Beispiel](/peertube-plugin-livechat/images/" +"avatar_sepia.png?classes=shadow,border&height=40px \"Sepia\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2073,8 +2193,10 @@ msgstr "{{% livechat_label avatar_set_option_cat %}}: [David Revoy's Katzen Avat #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Cats](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px)" -msgstr "![Katzen](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px)" +msgid "![Cats avatar example](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px \"Cats\")" +msgstr "" +"![Katzen AvatarBeispiel](/peertube-plugin-livechat/images/" +"avatar_cat.png?classes=shadow,border&height=40px \"Katzen\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2083,8 +2205,10 @@ msgstr "{{% livechat_label avatar_set_option_bird %}}: [David Revoy's Vögel Ava #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Birds](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px)" -msgstr "![Vögel](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px)" +msgid "![Birds avatar example](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px \"Birds\")" +msgstr "" +"![Vogel Avatar Beispiel](/peertube-plugin-livechat/images/" +"avatar_bird.png?classes=shadow,border&height=40px \"Vögel\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2093,8 +2217,10 @@ msgstr "{{% livechat_label avatar_set_option_fenec %}}: [David Revoy's Fenec/Mob #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px)" -msgstr "![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px)" +msgid "![Fenecs avatar example](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px \"Fenecs\")" +msgstr "" +"![Fenecs Avatar Beispiel](/peertube-plugin-livechat/images/" +"avatar_fenec.png?classes=shadow,border&height=40px \"Fenecs\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2103,8 +2229,10 @@ msgstr "{{% livechat_label avatar_set_option_abstract %}}: [David Revoy's Abstra #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Abstracts](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px)" -msgstr "![Abstrakt](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px)" +msgid "![Abstracts avatar example](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px \"Abtracts\")" +msgstr "" +"![Abstrakt Avatar Beispiel](/peertube-plugin-livechat/images/" +"avatar_abstract.png?classes=shadow,border&height=40px \"Abstrakt\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2113,8 +2241,10 @@ msgstr "{{% livechat_label avatar_set_option_legacy %}}: Basierend auf [David Re #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Legacy](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px)" -msgstr "![Alte Avatare](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px)" +msgid "![Legacy avatar example](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px \"Legacy\")" +msgstr "" +"![Alte Avatare Beispiel](/peertube-plugin-livechat/images/" +"avatar_legacy.jpg?classes=shadow,border&height=40px \"Alte Avatare\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2138,8 +2268,8 @@ msgstr "Default ConverseJS theme: Dies ist das standard ConverseJS Thema." #. type: Bullet: '- ' #: build/documentation/pot_in/documentation/admin/settings.md -msgid "ConverseJS concord theme: this is a theme provided by ConverseJS." -msgstr "ConverseJS concord theme: Dies ist ein von ConverseJS bereitgestelltes Thema." +msgid "ConverseJS cyberpunk theme: this is a theme provided by ConverseJS." +msgstr "ConverseJS cyberpunk theme: Dies ist ein von ConverseJS bereitgestelltes Thema." #. type: Title ## #: build/documentation/pot_in/documentation/admin/settings.md @@ -2193,6 +2323,11 @@ msgstr "Diese Funktion könnte für die Verbindung von Brücken oder Bots genutz msgid "More informations on Prosody external components [here](https://prosody.im/doc/components)." msgstr "Weitere Informationen zu den externen Komponenten von Prosody finden Sie [hier](https://prosody.im/doc/components)." +#. type: Plain text +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "For more information, please check [the documentation](/peertube-plugin-livechat/documentation/admin/mod_firewall/)." +msgstr "Weitere Informationen finden Sie in [der Dokumentation](/peertube-plugin-livechat/de/documentation/admin/mod_firewall/)." + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/_index.md #, no-wrap @@ -2386,8 +2521,12 @@ msgstr "Öffnen Sie die Plugin-Einstellungen, und klicken Sie auf die Schaltflä #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Launch diagnostic](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px)" -msgstr "![Diagnose starten](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the plugin's settings page, with a \"launch diagnostic\" button.](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px \"Launch diagnostic\")" +msgstr "" +"![Screenshot der Einstellungsseite des Plugins, mit einer Schaltfläche " +"\"Diagnose starten\".](/peertube-plugin-livechat/images/" +"launch_diagnostic.png?classes=shadow,border&height=200px \"Diagnose starten\"" +")" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md @@ -2396,8 +2535,12 @@ msgstr "Wenn auf der Diagnoseseite ein Fehler auftritt, können Sie auf dieser S #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Diagnostic result](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px)" -msgstr "![Diagnoseergebnis](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the diagnostic result page. This gives a lot of information, with status for different test suites.](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px \"Diagnostic result\")" +msgstr "" +"![Screenshot der Seite mit den Diagnoseergebnissen. Dies gibt eine Menge " +"Informationen, mit Status für verschiedene Testsuiten.](/peertube-" +"plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px " +"\"Diagnoseergebnisse\")" #. type: Title ## #: support/documentation/content/en/documentation/installation/troubleshooting.md @@ -2524,8 +2667,12 @@ msgstr "Sie können den Chat ganz einfach in Ihren Videostream integrieren." #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px)" -msgstr "![Einbetten des Chats in einen Live-Stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px \"Embeding the chat in a live stream\")" +msgstr "" +"![Screenshot einer Peertube Live-Übertragung, bei der der Chat am unteren " +"Ende des Video-Streams eingebettet ist.](/peertube-plugin-livechat/images/" +"embed_chat_in_livestream.png?classes=shadow,border&height=200px \"Einbettung " +"des Chats in einen Live-Stream\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2540,8 +2687,12 @@ msgstr "Aktivieren Sie das Kontrollkästchen \"{{% livechat_label read_only %}}\ #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" -msgstr "![Link Teilen Popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, where the \"{{% livechat_label read_only %}}\" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px \"Share link popup\")" +msgstr "" +"![Screenshot des \"{{% livechat_label share_chat_link %}}\"-Dialogs, in dem " +"die Option \"{{% livechat_label read_only %}}\" aktiviert ist](/peertube-" +"plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px " +"\"Link teilen popup\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2551,8 +2702,12 @@ msgstr "Verwenden Sie dann diesen Link als \"Webbrowser-Quelle\" in OBS." #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px)" -msgstr "![Den Chat in OBS einbetten](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px \"Embeding the chat in OBS\")" +msgstr "" +"![Screenshot der OBS-Software, wo der Chat als Webbrowser-Quelle hinzugefügt " +"wurde.](/peertube-plugin-livechat/images/" +"embed_chat_in_obs.png?classes=shadow,border&height=200px \"Den Chat in OBS " +"einbetten\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2609,8 +2764,13 @@ msgstr "Verwenden Sie dazu einfach die \"{{% livechat_label share_chat_link %}}\ #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" -msgstr "![Link teilen Fenster - Dock Reiter](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable.\"](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px \"Share link popup - dock tab\")" +msgstr "" +"![Screenshot des \"{{% livechat_label share_chat_link %}}\"-Dialogs auf der " +"Registerkarte \"{{% livechat_label share_chat_dock %}}\". Ein Token wurde " +"generiert und ist auswählbar.\"](/peertube-plugin-livechat/images/" +"share_dock.png?classes=shadow,border&height=200px \"Link teilen popup - Dock " +"Registerkarte\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2619,13 +2779,19 @@ msgstr "Kopieren Sie dann die URL und verwenden Sie das Menü \"Docks / Benutzer #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock menu](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px)" -msgstr "![OBS - Dock Menü](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Dock menu, with a \"Custom Browser Docks\" entry.](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px \"OBS - Dock menu\")" +msgstr "" +"![Screenshot des OBS Dock Menüs mit dem Eintrag \"Benutzerdefinierte Browser-" +"Docks\"](/peertube-plugin-livechat/images/" +"obs_dock_menu.png?classes=shadow,border&height=200px \"OBS - Dock menu\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px)" -msgstr "![OBS - Dock-Dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Custom Browser Docks dialog, with a new dock called \"My chat\".](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px \"OBS - Dock dialog\")" +msgstr "" +"![Screenshot des OBS Custom Browser Docks Dialogs, mit einem neuen Dock " +"namens \"Mein Chat\"](/peertube-plugin-livechat/images/" +"obs_dock_dialog.png?classes=shadow,border&height=200px \"OBS - Dock Dialog\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2634,8 +2800,12 @@ msgstr "Danach haben Sie ein neues Dock, das mit dem Chat und Ihrem Konto verbun #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" -msgstr "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of OBS with a new dock including the chat. The user is logged in with their Peertube account, and can chat directly from OBS.](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px \"OBS - Dock\")" +msgstr "" +"![Screenshot von OBS mit einem neuen Dock inklusive Chat. Der Nutzer ist mit " +"seinem Peertube-Account eingeloggt und kann direkt von OBS aus chatten" +".](/peertube-plugin-livechat/images/" +"obs_dock.png?classes=shadow,border&height=200px \"OBS - Dock\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2698,8 +2868,11 @@ msgstr "Wenn Sie eine Peertube Live-Stream erstellen oder ändern, gibt es eine #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![New live](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px)" -msgstr "![Neuer Live-Stream](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the Peertube new live form.](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px \"New live\")" +msgstr "" +"![Screenshot des Peertube-Formulars für die Live-Übertragung (/peertube-" +"plugin-livechat/images/new_live.png?classes=shadow,border&height=200px " +"\"Neuer Livestream\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2708,8 +2881,12 @@ msgstr "Auf der Registerkarte \"Plugin-Einstellungen\" gibt es ein Kontrollkäst #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Activate the chat](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px)" -msgstr "![Den Chat aktivieren](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the form, with a \"{{% livechat_label use_chat %}}\" checkbox.](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px \"Activate the chat\")" +msgstr "" +"![Screenshot des Formulars mit dem Kontrollkästchen \"{{% livechat_label " +"use_chat %}}\"](/peertube-plugin-livechat/images/" +"new_live_activate_chat.png?classes=shadow,border&height=200px \"Chat " +"aktivieren\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2780,8 +2957,13 @@ msgstr "Auf der Registerkarte \"{{% livechat_label web %}}\" öffnet die angegeb #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" -msgstr "![Link teilen Fenster - Web Reiter](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label web %}} tab. There is a url you can copy.](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px \"Share link popup - web tab\")" +msgstr "" +"![Screenshot des \"{{% livechat_label share_chat_link %}}\"-Dialogs auf der " +"Registerkarte \"{{% livechat_label web %}}\". Es gibt eine Url, die Sie " +"kopieren können](/peertube-plugin-livechat/images/" +"share_web.png?classes=shadow,border&height=200px Link teilen popup - Web " +"Registerkarte\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2790,8 +2972,14 @@ msgstr "Das \"{{% livechat_label share_chat_link %}}\" Popup-Fenster kann auch e #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - xmpp tab](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" -msgstr "![Link teilen Fenster - xmpp Reiter](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +#: support/documentation/content/en/documentation/user/xmpp_clients.md +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label connect_using_xmpp %}}\" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px \"{{% livechat_label connect_using_xmpp %}}\")" +msgstr "" +"![Screenshot des \"{{% livechat_label share_chat_link %}}\"-Dialogs, auf der " +"Registerkarte \"{{% livechat_label connect_using_xmpp %}}\".](/peertube-" +"plugin-livechat/images/" +"share_xmpp_dialog.png?classes=shadow,border&height=200px \"{{% " +"livechat_label connect_using_xmpp %}}\")" #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2838,8 +3026,11 @@ msgstr "Sie können das Dauerhaftigkeitsverhalten ändern. [Öffnen Sie das Cha #: support/documentation/content/en/documentation/user/streamers/basics.md #: build/documentation/pot_in/documentation/user/streamers/moderation.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px)" -msgstr "![Chat Menü](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px \"Chat menu\")" +msgstr "" +"![Screenshot des Dropdown-Menüs oben im Chat. Es sind mehrere Einträge " +"verfügbar. ](/peertube-plugin-livechat/images/" +"top_menu.png?classes=shadow,border&height=200px \"Chat Menü\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2848,8 +3039,11 @@ msgstr "Es gibt mehrere Optionen, die geändert werden können." #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Configure chat room](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px)" -msgstr "![Chatraum konfigurieren](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat configuration form.](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px \"Configure chat room\")" +msgstr "" +"![Screenshot des Chat-Konfigurationsformulars.](/peertube-plugin-livechat/" +"images/configure.png?classes=shadow,border&height=200px \"Chatraum " +"konfigurieren\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2896,8 +3090,11 @@ msgstr "Befehle" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md -msgid "![Commands configuration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px)" -msgstr "![Befehlskonfiguration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with some fields to configure the bot commands.](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px \"Commands configuration\")" +msgstr "" +"![Screenshot der Seite mit den Kanaloptionen, mit einigen Feldern zur " +"Konfiguration der Chatbot-Befehle.](/peertube-plugin-livechat/images/" +"bot_commands.png?classes=shadow,border&height=400px \"Befehlskonfiguration\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md @@ -2918,13 +3115,21 @@ msgstr "Verbotene Wörter" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Forbidden words configuration](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px)" -msgstr "![Konfiguration der verbotenen Wörter](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with several fields to configure the forbidden words.](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px \"Forbidden words configuration\")" +msgstr "" +"![Screenshot der Seite mit den Kanaloptionen, mit mehreren Feldern zur " +"Konfiguration der verbotenen Wörter.](/peertube-plugin-livechat/images/" +"bot_forbidden_words.png?classes=shadow,border&height=400px \"Konfiguration " +"der verbotenen Wörter\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Deleted message](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px)" -msgstr "![Gelöschte Nachricht](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px)" +msgid "![Screenshot of a chat message that was deleted, with the following reason: \"No url allowed\".](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px \"Deleted message\")" +msgstr "" +"![Screenshot einer Chat-Nachricht, die mit folgender Begründung gelöscht " +"wurde: \"Keine URL erlaubt\".](/peertube-plugin-livechat/images/" +"bot_deleted_message.png?classes=shadow,border&height=100px \"Gelöschte " +"Nachricht\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md @@ -2961,6 +3166,12 @@ msgstr "Wenn Sie einige nützliche Wörterlisten haben, können Sie die gerne zu msgid "These words are case insensitive." msgstr "Bei diesen Wörtern wird die Groß- und Kleinschreibung nicht berücksichtigt." +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md +#: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +msgid "You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them." +msgstr "Sie können eine kurze [Moderationsverzögerung](/peertube-plugin-livechat/de/documentation/user/streamers/moderation_delay) (z.B. 1 Sekunde) mit dem [Moderations-Chatbot](/peertube-plugin-livechat/de/documentation/user/streamers/bot) kombinieren, um Nachrichten mit Schimpfwörtern zu löschen, bevor ein Nicht-Moderator sie sieht." + #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md msgid "This features is still experimental. There might be some issues with non-latin alphabets. You can [open an issue](https://github.com/JohnXLivingston/peertube-plugin-livechat/issues) to report your problems." @@ -2998,8 +3209,12 @@ msgstr "Sie können einen Chatbot für Ihre Chaträume aktivieren. Die Chatbotk #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px)" -msgstr "![Kanalkonfiguration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px \"Channel configuration\")" +msgstr "" +"![Screenshot der Kanaloptionen; es gibt ein Formular mit mehreren Feldern" +".](/peertube-plugin-livechat/images/" +"channel_configuration.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md @@ -3035,8 +3250,11 @@ msgstr "Wenn sich kein Benutzer im Chatraum befindet, sendet der Chatbot keine N #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/quotes.md -msgid "![Timers configuration](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px)" -msgstr "[Konfiguration der Timer](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the channel options page, with some fields to configure a new timer.](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px \"Timers configuration\")" +msgstr "" +"![Screenshot der Seite mit den Kanaloptionen, mit einigen Feldern zur " +"Konfiguration eines neuen Timers.](/peertube-plugin-livechat/images/" +"bot_quotes.png?classes=shadow,border&height=200px \"Timer konfiguration\")" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/channel.md @@ -3057,8 +3275,11 @@ msgstr "Im linken Menü von Peertube gibt es einen Eintrag \"{{% livechat_label #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" -msgstr "![Chaträume Menü](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the chatrooms configuration page. The page list the user's channels.](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px \"Chatrooms menu\")" +msgstr "" +"![Screenshot der Konfigurationsseite für Chaträume. Die Seite listet die " +"Kanäle des Benutzers auf.](/peertube-plugin-livechat/images/" +"chatrooms_menu.png?classes=shadow,border&height=400px \"Chaträume Menü\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md @@ -3130,13 +3351,22 @@ msgstr "Öffnen Sie auf der [Kanal Konfigurationsseite](/peertube-plugin-livecha #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis configuration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px)" -msgstr "![Kanal-Konfiguration / Kanal-Emojis-Konfiguration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the emoji configuration page. There is a form where you can add new emojis.](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis configuration\")" +msgstr "" +"![Screenshot der Emoji-Konfigurationsseite. Es gibt ein Formular, in dem Sie " +"neue Emojis hinzufügen können.](/peertube-plugin-livechat/images/" +"channel_custom_emojis_configuration.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration / Kanal Emojis Konfiguration\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px)" -msgstr "![Kanal-Konfiguration / Kanal-Emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session, with messages containing custom emojis. The emoji picker is open, and shows custom emojis.](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis\")" +msgstr "" +"![Screenshot einer Chatsitzung mit Nachrichten, die benutzerdefinierte " +"Emojis enthalten. Die Emoji-Auswahl ist geöffnet und zeigt " +"benutzerdefinierte Emojis.](/peertube-plugin-livechat/images/" +"channel_custom_emojis.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration / Kanal Emojis\")" #. type: Title ### #: build/documentation/pot_in/documentation/user/streamers/emojis.md @@ -3208,6 +3438,7 @@ msgstr "Diese Funktion wird mit dem Livechatplugin Version 10.3.0 verfügbar sei #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #: support/documentation/content/en/documentation/user/streamers/tasks.md #: support/documentation/content/en/intro/_index.md @@ -3243,8 +3474,12 @@ msgstr "Auf der [Kanal Konfigurationsseite](/peertube-plugin-livechat/de/documen #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" -msgstr "![Kanalkonfiguration / Moderationsverzögerung](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel option form, with a field to configure the moderation delay.](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Moderation delay\")" +msgstr "" +"![Screenshot des Formulars für die Kanaloptionen, mit einem Feld zur " +"Konfiguration der Moderationsverzögerung.](/peertube-plugin-livechat/images/" +"moderation_delay_channel_option.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration / Moderationsverzögerung\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md @@ -3271,26 +3506,27 @@ msgstr "Um den Wert für einen bereits bestehenden Raum zu ändern, öffnen Sie #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md msgid "Currently, this feature has one known bug: users that join the chat will get all messages, even messages that are still pending for other participants. However, messages sent after they joined will be delayed correctly." -msgstr "" +msgstr "Derzeit gibt es bei dieser Funktion einen bekannten Fehler: Benutzer, die dem Chat beitreten, erhalten alle Nachrichten, auch solche, die noch für andere Teilnehmer ausstehen. Allerdings werden Nachrichten, die nach dem Beitritt gesendet werden, korrekt verzögert." #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -#, fuzzy, no-wrap -#| msgid "Share the chat" +#, no-wrap msgid "In the chat" -msgstr "Teilen Sie den Chat" +msgstr "Im Chat" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md msgid "As a moderator, you will see the remaining time (in seconds) before the message is broadcasted, just besides the message datetime." -msgstr "" +msgstr "Als Moderator sehen Sie neben dem Datum der Nachricht auch die verbleibende Zeit (in Sekunden), bevor die Nachricht veröffentlicht wird." #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -#, fuzzy -#| msgid "![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" -msgid "![Moderation delay timer](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border)" -msgstr "![Kanalkonfiguration / Moderationsverzögerung](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat message. A timer is displayed next to the message datetime. The timer is in seconds.](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border \"Moderation delay timer\")" +msgstr "" +"![Screenshot einer Chat-Nachricht. Neben dem Datum der Nachricht wird ein " +"Timer angezeigt. Der Timer ist in Sekunden.](/peertube-plugin-livechat/" +"images/moderation_delay_timer.png?classes=shadow,border " +"\"Moderationsverzögerungstimer\")" #. type: Yaml Front Matter Hash Value: description #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3337,8 +3573,6 @@ msgstr "Über das [Chat Dropdown Menü](/peertube-plugin-livechat/de/documentati #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -#, fuzzy -#| msgid "The video owner will be owner of the chat room. This means he can configure the room, delete it, promote other users as admins, ..." msgid "The video owner will be owner of the chat room. This means they can configure the room, delete it, promote other users as admins, ..." msgstr "Der Videobesitzer ist der Besitzer des Chatraums. Das bedeutet, er kann den Raum konfigurieren, löschen, andere Benutzer als Administratoren befördern, ..." @@ -3371,8 +3605,13 @@ msgstr "Um diese Funktion zu aktivieren oder zu deaktivieren, verwenden Sie das #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" -msgstr "![Raumkonfiguration / Anonyme Benutzer stummschalten](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the room configuration form. There is a \"{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}\" checkbox.](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px \"Room configuration / Mute anonymous users\")" +msgstr "" +"![Screenshot des Raumkonfigurationsformulars. Es gibt ein \"{{% " +"livechat_label livechat_configuration_channel_mute_anonymous_label %}}\"" +"-Kontrollkästchen.](/peertube-plugin-livechat/images/" +"configure_mute_anonymous.png?classes=shadow,border&height=400px " +"\"Raumkonfiguration / Anonyme Benutzer stummschalten\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3381,8 +3620,13 @@ msgstr "Anonyme Benutzer haben das Nachrichtenfeld nicht und sehen folgende Auff #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" -msgstr "![Raumkonfiguration / Stummgeschaltete anonyme Benutzer](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. The current user has no message field. There is a message: \"{{% livechat_label muted_anonymous_message %}}\"](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px \"Room configuration / Muted anonymous users\")" +msgstr "" +"![Screenshot einer Chatsitzung. Der aktuelle Benutzer hat kein " +"Nachrichtenfeld. Es gibt eine Nachricht: \"{{% livechat_label " +"muted_anonymous_message %}}\"](/peertube-plugin-livechat/images/" +"anonymous_muted.png?classes=shadow,border&height=400px \"Raumkonfiguration / " +"Stummgeschaltete anonyme Benutzer\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3415,6 +3659,86 @@ msgstr "Es gibt verschiedene Rollen, die Benutzern in Chaträumen zugewiesen wer msgid "You can promote users as moderators, if you need some help." msgstr "Sie können Benutzer zu Moderatoren befördern, wenn Sie Hilfe benötigen." +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "It is possible to anonymize moderation actions, to avoid disclosing who is banning/kicking/… occupants." +msgstr "Es ist möglich, Moderationsaktionen zu anonymisieren, um zu vermeiden, dass bekannt wird, wer Teilnehmer bannt/verweist/..." + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the \"configure\" menu. In the form, you will find a \"{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}\" checkbox." +msgstr "Um diese Funktion zu aktivieren oder zu deaktivieren, verwenden Sie das [Chat-Dropdown-Menü](/peertube-plugin-livechat/de/documentation/user/viewers), öffnen Sie das Menü \"Konfigurieren\". In dem Formular finden Sie eine Checkbox \"{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}\"." + +#. type: Title ## +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#, no-wrap +msgid "Participant message history search" +msgstr "Im Nachrichtenverlauf eines Teilnehmers suchen" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "As a room admin or owner, you can search all messages sent by a given participant." +msgstr "Als Raumadministrator oder -besitzer können Sie alle von einem bestimmten Teilnehmer gesendeten Nachrichten durchsuchen." + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To do so, you have several ways:" +msgstr "Dazu haben Sie mehrere Möglichkeiten:" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "die Aktion \"{{% livechat_label search_occupant_message %}}\" im Dropdown-Menü neben den Teilnehmern in der Seitenleiste verwenden" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides chat messages" +msgstr "die Aktion \"{{% livechat_label search_occupant_message %}}\" im Dropdown-Menü neben den Chat-Nachrichten verwenden" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "![Screenshot of a chat session. The moderator has open the message menu, and there is a \"{{% livechat_label search_occupant_message %}}\" button.](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px \"Message history search\")" +msgstr "" +"![Screenshot einer Chatsitzung. Der Moderator hat das Nachrichtenmenü " +"geöffnet, und es gibt eine Schaltfläche \"{{% livechat_label " +"search_occupant_message %}}\".](/peertube-plugin-livechat/images/" +"message_search.png?classes=shadow,border&height=200px " +"\"Nachrichtenverlaufssuche\")" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "To have more space and better readability, open the chat in full-page mode." +msgstr "Um mehr Platz und eine bessere Lesbarkeit zu erhalten, öffnen Sie den Chat im neuen Fenster." + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "In the search results, there are several informations that are shown at the right of the participant nickname:" +msgstr "In den Suchergebnissen werden rechts neben dem Spitznamen des Teilnehmers verschiedene Informationen angezeigt:" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "if the current nickname is different than the nickname when the participant has sent the message, the original nickname will be shown" +msgstr "wenn der aktuelle Nickname nicht mit dem Nicknamen übereinstimmt, unter dem der Teilnehmer die Nachricht gesendet hat, wird der ursprüngliche Nickname angezeigt" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant" +msgstr "sehen Sie die [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) des Teilnehmers" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant" +msgstr "Sie sehen auch die [occupant-id](https://xmpp.org/extensions/xep-0421.html) des Teilnehmers" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "The search result will also include all messages related to participants who had the same nickname. You can differenciate them by comparing [JID](https://xmpp.org/extensions/xep-0029.html) and [occupant-id](https://xmpp.org/extensions/xep-0421.html)." +msgstr "Das Suchergebnis enthält auch alle Nachrichten, die sich auf Teilnehmer beziehen, die denselben Spitznamen hatten. Sie können sie unterscheiden, indem Sie [JID](https://xmpp.org/extensions/xep-0029.html) und [occupant-id](https://xmpp.org/extensions/xep-0421.html) vergleichen." + #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap @@ -3447,6 +3771,255 @@ msgstr "Sie können alle bestehenden Chaträume auflisten: in den Einstellungen msgid "From there, you can also promote yourself as room moderator by using the \"{{% livechat_label promote %}}\" button on the right." msgstr "Von dort aus kannst du dich auch als Moderator des Raums bewerben, indem du die Schaltfläche \"{{% livechat_label promote %}}\" auf der rechten Seite benutzt." +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Plugin peertube-plugin-livechat moderation notes" +msgstr "Plugin peertube-plugin-livechat Moderationnotizen" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Moderation notes" +msgstr "Moderationsnotizen" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The livechat plugin includes a Moderator Notes Application: you can write some notes, that could be associated to chat participants. Every room's admins have access to these notes, so they can edit them collaboratively." +msgstr "Das Livechat Plugin enthält eine Anwendung für Moderator-Notizen: Sie können einige Notizen schreiben, die den Chat-Teilnehmern zugeordnet werden können. Die Administratoren jedes Raums haben Zugriff auf diese Notizen, so dass sie diese gemeinsam bearbeiten können." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can for example use this Application to:" +msgstr "Sie können diese Anwendung zum Beispiel verwenden, um:" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "share some notes between moderators" +msgstr "einige Notizen zwischen Moderatoren austauschen" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "take notes about participants that were kicked or caused troubles" +msgstr "Notizen über Teilnehmer machen, die aus dem Chat geworfen wurden oder Probleme verursachten" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "..." +msgstr "..." + +#. type: Title ## +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Using the Moderator Notes Application" +msgstr "Verwendung der Anwendung Moderationsnotizen" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Opening the Moderator Notes Application" +msgstr "Öffnen der Anwendung Moderationsnotizen" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "To open the Moderator Notes Application, there is a \"{{% livechat_label \"moderator_notes\" %}}\" button in the top chat menu:" +msgstr "Um die Anwendung Moderationsnotizen zu öffnen, gibt es eine Schaltfläche \"{{% livechat_label \"moderator_notes\" %}}\" im oberen Chatmenü:" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_video.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" +"![Screenshot eines Peertube-Videos, mit dem Chat auf der rechten Seite. Das " +"obere Menü des Chats ist geöffnet, mit einer \"{{% livechat_label " +"\"moderator_notes\" %}}\"-Schaltfläche.](/peertube-plugin-livechat/images/" +"moderation_notes_open_app_video.png?classes=shadow,border&height=200px " +"\"Öffnen des Moderator Notizen Programms\")" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" +"![Screenshot eines Peertube-Chats, Vollbild. Das obere Menü des Chats ist " +"geöffnet, mit einer \"{{% livechat_label \"moderator_notes\" %}}\"" +"-Schaltfläche.](/peertube-plugin-livechat/images/" +"moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px " +"\"Öffnen des Moderator Notizen Programms\")" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Clicking this button will toggle the Application display:" +msgstr "Wenn Sie auf diese Schaltfläche klicken, wird die Anzeige der Anwendung umgeschaltet:" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" +"![Screenshot eines Peertube-Videos, mit dem Chat auf der rechten Seite. Die " +"Anwendung für Moderationsnotizen ist geöffnet. Es gibt mehrere Notizen, " +"einige davon sind mit Benutzern verknüpft.](/peertube-plugin-livechat/images/" +"moderator_notes_app_video_1.png?classes=shadow,border&height=200px " +"\"Moderator Notizen Programm\")" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" +"![Screenshot eines Peertube-Chats, Vollbild. Die Anwendung für " +"Moderationsnotizen ist geöffnet. Es gibt mehrere Notizen, einige davon sind " +"mit Benutzern verknüpft.](/peertube-plugin-livechat/images/" +"moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px " +"\"Moderator Notizen Programm\")" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/polls.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +#, no-wrap +msgid "Access rights" +msgstr "Zugriffsrechte" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Every room's admins have access to this Application (read and write access)." +msgstr "Die Administratoren jedes Raums haben Zugriff auf diese Anwendung (Lese- und Schreibzugriff)." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you promote someone as room admin or owner, they gets instant access to this Application. When you remove admin or owner rights to someone, they instantly lose access to this Application." +msgstr "Wenn Sie jemanden zum Raumadministrator oder -besitzer befördern, erhält er sofortigen Zugang zu dieser Anwendung. Wenn Sie jemandem die Admin- oder Eigentümerrechte entziehen, verliert er sofort den Zugang zu dieser Anwendung." + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Scope" +msgstr "Umfang" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Notes are only available in the room in which you have created them." +msgstr "Notizen sind nur in dem Raum verfügbar, in dem Sie sie erstellt haben." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Chatrooms can be releated to video or channel. If you want to keep notes from one video to another, please consider using rooms associated to channels." +msgstr "Chaträume können einem Video oder einem Kanal zugeordnet werden. Wenn Sie Notizen von einem Video zum anderen aufbewahren möchten, sollten Sie Räume verwenden, die mit Kanälen verbunden sind." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Currently the video vs channel rooms is an instance-wide settings. Only Peertube admins can change it, and it applies to all chatrooms. In the future, this choice will be added in your channel's options." +msgstr "Derzeit ist die Einstellung Video vs. Kanalräume eine instanzweite Einstellung. Nur Peertube-Administratoren können diese Einstellung ändern, und sie gilt für alle Chaträume. In der Zukunft wird diese Wahl in den Optionen Ihres Channels hinzugefügt werden." + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes" +msgstr "Hinweise" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Create/Edit Notes" +msgstr "Notizen erstellen/bearbeiten" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can use the plus button on the top to create a new note. You can also edit existing notes using the edit button, or delate any note." +msgstr "Mit der Plus-Schaltfläche am oberen Rand können Sie eine neue Notiz erstellen. Sie können auch bestehende Notizen mit der Schaltfläche \"Bearbeiten\" bearbeiten oder jede Notiz löschen." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgstr "Alle Änderungen sind sofort in allen Registerkarten Ihres Browsers und für alle Raumadministratoren sichtbar." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can create a note associated to a participant in several ways:" +msgstr "Sie können eine Notiz zu einem Teilnehmer auf verschiedene Weise erstellen:" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "die Aktion \"{{% livechat_label moderator_note_create_for_participant %}}\" im Dropdown-Menü neben den Teilnehmern in der Seitenleiste verwenden" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides chat messages" +msgstr "Verwendung der Aktion \"{{% livechat_label moderator_note_create_for_participant %}}\" im Dropdown-Menü neben den Chat-Nachrichten" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When a note is associated to a participant, you will see their nickname and avatar on the top of the note." +msgstr "Wenn eine Notiz mit einem Teilnehmer verknüpft ist, sehen Sie dessen Spitznamen und Avatar oben in der Notiz." + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes filtering" +msgstr "Notizen filtern" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can filter notes to find all notes related to a given participant in several ways:" +msgstr "Sie können die Notizen filtern, um alle Notizen zu einem bestimmten Teilnehmer zu finden, und haben dabei mehrere Möglichkeiten:" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button that is available on notes to find all notes related to the same participant" +msgstr "Klicken Sie auf die Schaltfläche \"{{% livechat_label moderator_note_search_for_participant %}}\", die auf den Notizen verfügbar ist, um alle Notizen zu finden, die sich auf denselben Teilnehmer beziehen" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides participants in the sidebar" +msgstr "auf die Schaltfläche \"{{% livechat_label moderator_note_search_for_participant %}}\" im Dropdown-Menü neben den Teilnehmern in der Seitenleiste klicken" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides chat messages" +msgstr "Klicken Sie auf die Schaltfläche \"{{% livechat_label moderator_note_search_for_participant %}}\" im Dropdown-Menü neben den Chat-Nachrichten" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can remove the filter by clicking on the close button." +msgstr "Sie können den Filter entfernen, indem Sie auf die Schaltfläche \"Schließen\" klicken." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of the note application, with a filter enabled for user \"Mike\". The only notes that are shown are the notes for the Mike user.](/peertube-plugin-livechat/images/moderation_notes_filters.png?classes=shadow,border&height=200px \"Moderator Notes Application - filtering\")" +msgstr "" +"![Screenshot der Notizen-Anwendung, mit einem aktivierten Filter für den " +"Benutzer \"Mike\". Es werden nur die Notizen für den Benutzer \"Mike\" " +"angezeigt.](/peertube-plugin-livechat/images/" +"moderation_notes_filters.png?classes=shadow,border&height=200px \"Moderator " +"Notizen Programm - Filtern\")" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you filters notes on a participant, there are several informations that are shown at the right of the participant nickname:" +msgstr "Wenn Sie Notizen zu einem Teilnehmer filtern, werden rechts neben dem Spitznamen des Teilnehmers verschiedene Informationen angezeigt:" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "if the current nickname is different than the nickname when you created the note, the original nickname will be shown" +msgstr "wenn der aktuelle Spitzname nicht mit dem Spitznamen übereinstimmt, unter dem Sie die Notiz erstellt haben, wird der ursprüngliche Spitzname angezeigt" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The search result will also include all notes related to participants who had the same nickname. So you can also take note for anonymous users (who don't have any consistent JID or occupant-id). You can differenciate them by comparing JID and occupant-id." +msgstr "Das Suchergebnis enthält auch alle Notizen zu Teilnehmern, die denselben Spitznamen hatten. Sie können also auch anonyme Benutzer (die keine einheitliche JID oder occupant-id haben) notieren. Sie können sie durch den Vergleich von JID und occupant-id unterscheiden." + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Sorting notes" +msgstr "Notizen sortieren" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can sort notes simply using drag & drop." +msgstr "Sie können Notizen einfach per Drag & Drop sortieren." + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/polls.md #, no-wrap @@ -3472,8 +4045,12 @@ msgstr "Sie können eine neue Umfrage erstellen, indem Sie die Aktion \"{{% live #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" -msgstr "![Umfrageformular](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a \"{{% livechat_label new_poll %}}\" form. The form contains several fields: question, duration, choices, …](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px \"Poll form\")" +msgstr "" +"![Screenshot eines \"{{% livechat_label new_poll %}}\"-Formulars. Das " +"Formular enthält mehrere Felder: Frage, Dauer, Auswahlmöglichkeiten, " +"...](/peertube-plugin-livechat/images/" +"polls_form.png?classes=shadow,border&height=200px \"Umfrageformular\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md @@ -3526,13 +4103,6 @@ msgstr "Sobald Sie das Formular abschicken, wird die Umfrage sofort gestartet." msgid "If there was a previous unfinished poll, it will end and its result will be shown." msgstr "Wenn es eine vorherige, noch nicht beendete Umfrage gab, wird diese beendet und ihr Ergebnis angezeigt." -#. type: Title ### -#: support/documentation/content/en/documentation/user/streamers/polls.md -#: support/documentation/content/en/documentation/user/streamers/tasks.md -#, no-wrap -msgid "Access rights" -msgstr "Zugriffsrechte" - #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md msgid "Every room's admins can create a new poll." @@ -3571,8 +4141,13 @@ msgstr "Außerdem wird ein Banner erscheinen, das die Umfrage anzeigt und regelm #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" -msgstr "![Umfrage Start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. In the chat, there is a message with the poll question, and the different choices. There is also a banner on the top of the chat, where you can see the question, and the number of votes for each answers.](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px \"Poll start\")" +msgstr "" +"![Screenshot einer Chatsitzung. Im Chat gibt es eine Nachricht mit der Frage " +"und den verschiedenen Wahlmöglichkeiten. Außerdem gibt es ein Banner am " +"oberen Rand des Chats, auf dem die Frage und die Anzahl der Stimmen für die " +"einzelnen Antworten zu sehen sind.](/peertube-plugin-livechat/images/" +"polls_start.png?classes=shadow,border&height=200px \"Umfragestart\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md @@ -3591,8 +4166,12 @@ msgstr "Die Zuschauer können ihre Wahl jederzeit ändern, indem sie einfach ein #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll votes](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px)" -msgstr "![Abstimmungen](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with an ongoing poll. The current user has just voted by sending \"!1\".](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px \"Poll votes\")" +msgstr "" +"![Screenshot einer Chatsitzung mit einer laufenden Umfrage. Der aktuelle " +"Benutzer hat gerade abgestimmt, indem er \"!1\" gesendet hat.](/peertube-" +"plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px " +"\"Umfrage abstimmen\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md @@ -3616,8 +4195,14 @@ msgstr "Wenn die Umfrage beendet ist, wird im Chat eine neue Nachricht mit den E #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" -msgstr "![Umfrageende](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with poll that has ended. The banner no more accept new votes. There is a message in the chat with the poll results. For each choice, there is the number of votes, and the percentage of the total it represents.](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px \"Poll end\")" +msgstr "" +"![Screenshot einer Chatsitzung, mit einer Umfrage, die beendet wurde. Das " +"banner nimmt keine neuen Stimmen mehr an. Im Chat erscheint eine Nachricht " +"mit den Ergebnissen der Umfrage. Für jede Wahl gibt es die Anzahl der " +"Stimmen und den prozentualen Anteil an der Gesamtzahl.](/peertube-" +"plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px " +"\"Umfrageende\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md @@ -3684,8 +4269,12 @@ msgstr "Auf der [Kanal Konfigurations Seite](/peertube-plugin-livechat/de/docume #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Channel configuration / Slow Mode](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px)" -msgstr "![Kanalkonfiguration / Langsamer Modus](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a slow mode field.](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Slow Mode\")" +msgstr "" +"![Screenshot des Formulars für die Kanaloptionen, mit einem Feld für den " +"langsamen Modus.](/peertube-plugin-livechat/images/" +"slow_mode_channel_option.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration / Langsamer Modus\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md @@ -3711,8 +4300,13 @@ msgstr "Wenn der langsame Modus aktiviert ist, wird der Benutzer durch eine Nach #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" -msgstr "![Infobox Langsamer Modus](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. There is a banner on the bottom of the chat, indicating that the slow mode is enabled, and that users can send a message every 2 seconds.](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px \"Slow mode infobox\")" +msgstr "" +"![Screenshot einer Chatsitzung. Am unteren Rand des Chats befindet sich ein " +"Banner, das darauf hinweist, dass der langsame Modus aktiviert ist und die " +"Benutzer alle 2 Sekunden eine Nachricht senden können.](/peertube-" +"plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px " +"\"Langsamer Modus Infobox\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md @@ -3756,11 +4350,6 @@ msgstr "eine Liste der Themen vorzubereiten, die Sie während Ihres Livestreams msgid "highlight questions from your viewers, so you can come back to them later without forgetting to answer them" msgstr "Fragen Ihrer Zuschauer markieren, damit Sie später darauf zurückkommen können, ohne zu vergessen, sie zu beantworten" -#. type: Bullet: '* ' -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "..." -msgstr "..." - #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap @@ -3780,13 +4369,23 @@ msgstr "Um die Aufgabenanwendung zu öffnen, gibt es eine Schaltfläche \"{{% li #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px)" -msgstr "[Öffnen der Aufgabenanwendung](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px \"Opening the Task Application\")" +msgstr "" +"![Screenshot eines Peertube-Videos, mit dem Chat auf der rechten Seite. Das " +"obere Menü des Chats ist geöffnet, mit einer Schaltfläche \"{{% " +"livechat_label tasks %}}\".](/peertube-plugin-livechat/images/" +"task_open_app_video.png?classes=shadow,border&height=200px \"Öffnen der " +"Aufgabenanwendung\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px)" -msgstr "[Öffnen der Aufgabenanwendung](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Task Application\")" +msgstr "" +"![Screenshot eines Peertube-Chats, Vollbild. Das obere Menü des Chats ist " +"geöffnet, mit der Schaltfläche \"{{% livechat_label tasks %}}\".](/peertube-" +"plugin-livechat/images/" +"task_open_app_fullpage.png?classes=shadow,border&height=200px \"Öffnen der " +"Aufgabenanwendung\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3795,18 +4394,23 @@ msgstr "Wenn Sie auf diese Schaltfläche klicken, wird die Anzeige der Aufgabena #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabenanwendung](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px \"Task Application\")" +msgstr "" +"![Screenshot eines Peertube-Videos, mit dem Chat auf der rechten Seite. Die " +"Aufgabenanwendung ist geöffnet. Es gibt eine Aufgabenliste und ein Formular, " +"um eine neue Aufgabe zu erstellen.](/peertube-plugin-livechat/images/" +"task_app_video_1.png?classes=shadow,border&height=200px \"Aufgabenanwendung\"" +")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabenanwendung](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "To have more space and better readability, open the chat in full-page mode." -msgstr "Um mehr Platz und eine bessere Lesbarkeit zu erhalten, öffnen Sie den Chat im neuen Fenster." +msgid "![Screenshot of a Peertube chat, fullscreen. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px \"Task Application\")" +msgstr "" +"![Screenshot eines Peertube-Chats, Vollbild. Die Aufgabenanwendung ist " +"geöffnet. Es gibt eine Aufgabenliste und ein Formular, um eine neue Aufgabe " +"zu erstellen.](/peertube-plugin-livechat/images/" +"task_app_fullpage_1.png?classes=shadow,border&height=200px " +"\"Aufgabenanwendung\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3841,13 +4445,12 @@ msgstr "Die Aufgabenlisten sind alphabetisch sortiert." #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task lists](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabenlisten](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px)" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." -msgstr "Alle Änderungen sind sofort in allen Registerkarten Ihres Browsers und für alle Raumadministratoren sichtbar." +msgid "![Screenshot of a chat session, with the Task application. There are several task lists.](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px \"Task lists\")" +msgstr "" +"![Screenshot einer Chatsitzung mit der Aufgabenanwendung. Es gibt mehrere " +"Aufgabenlisten.](/peertube-plugin-livechat/images/" +"task_app_task_lists.png?classes=shadow,border&height=200px \"Aufgabenlisten\"" +")" #. type: Title ### #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3868,13 +4471,21 @@ msgstr "Sie können eine Aufgabe über die Schaltfläche rechts neben der Aufgab #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task form](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabenformular](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, there is a form to create a new task.](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px \"Task form\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Unter der ersten Aufgabenliste befindet " +"sich ein Formular zum Erstellen einer neuen Aufgabe.](/peertube-" +"plugin-livechat/images/" +"task_app_task_form.png?classes=shadow,border&height=200px \"Aufgabenformular" +"\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabe erstellt](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, a new task was created.](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px \"Task created\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Unter der ersten Aufgabenliste wurde " +"eine neue Aufgabe erstellt.](/peertube-plugin-livechat/images/" +"task_app_task_1.png?classes=shadow,border&height=200px \"Aufgabe erstellt\")" #. type: Title #### #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3894,8 +4505,12 @@ msgstr "Aufgaben können durch direktes Anklicken des Kontrollkästchens in der #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" -msgstr "![Aufgaben](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under task lists, there are several tasks. Some of them are checked, other not.](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px \"Tasks\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Unter den Aufgabenlisten gibt es mehrere " +"Aufgaben. Einige von ihnen sind markiert, andere nicht.](/peertube-" +"plugin-livechat/images/" +"task_app_task_2.png?classes=shadow,border&height=200px \"Aufgaben\")" #. type: Title #### #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3910,13 +4525,21 @@ msgstr "Sie können Aufgaben sortieren oder von einer Liste in eine andere versc #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to sort](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px)" -msgstr "![Drag und Drop zum Sortieren](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another.](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px \"Drag and drop to sort\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Eine Aufgabe wird über eine andere " +"gezogen](/peertube-plugin-livechat/images/" +"task_drag_drop.png?classes=shadow,border&height=200px \"Drag und drop zum " +"sortieren\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to move to another list](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px)" -msgstr "![Drag und Drop zum Verschieben zu einer anderen Liste](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another task list.](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px \"Drag and drop to move to another list\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Es gibt eine Aufgabe, die über eine " +"andere Aufgabenliste gezogen wird.](/peertube-plugin-livechat/images/" +"task_drag_drop_task_list.png?classes=shadow,border&height=200px \"Drag und " +"drop zum verschieben zu einer anderen Liste\")" #. type: Title #### #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3931,18 +4554,33 @@ msgstr "Sie können eine Aufgabe aus einer Nachricht in einem Chat erstellen, in #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Create task from message](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabe aus einer Nachricht erstellen](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. The menu besides a message is open, with a button to create a new task.](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px \"Create task from message\")" +msgstr "" +"![Screenshot einer Chatsitzung. Das Menü neben einer Nachricht ist geöffnet, " +"mit einer Schaltfläche zum Erstellen einer neuen Aufgabe.](/peertube-" +"plugin-livechat/images/" +"task_from_message_1.png?classes=shadow,border&height=200px \"Aufgabe aus " +"einer Nachricht erstellen\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Choose the task list](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabenliste auswählen](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog, where you can choose in which task list you want to add the new task.](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px \"Choose the task list\")" +msgstr "" +"![Screenshot eines Dialogs, in dem Sie auswählen können, in welcher " +"Aufgabenliste Sie die neue Aufgabe hinzufügen möchten.](/peertube-" +"plugin-livechat/images/" +"task_from_message_2.png?classes=shadow,border&height=200px \"Aufgabenliste " +"auswählen\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabe erstellt](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. A new task was added in the \"chat questions\" task list, with the user's nickname, and the message as content.](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px \"Task created\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Eine neue Aufgabe wurde in der " +"Aufgabenliste \"Chat-Fragen\" hinzugefügt, mit dem Spitznamen des Benutzers " +"und der Nachricht als Inhalt.](/peertube-plugin-livechat/images/" +"task_from_message_3.png?classes=shadow,border&height=200px \"Aufgabe " +"erstellt\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3979,8 +4617,12 @@ msgstr "Um die Nutzungsbedingungen zu konfigurieren, gehen Sie auf die [Kanal-Ko #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Channel configuration / Terms](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px)" -msgstr "![Kanalkonfiguration / Nutzungsbedingungen](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a field to configure your terms and conditions.](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px \"Channel configuration / Terms\")" +msgstr "" +"![Screenshot des Formulars für die Kanaloptionen mit einem Feld für die " +"Konfiguration der Nutzungsbedingungen.](/peertube-plugin-livechat/images/" +"channel_terms_config.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration / Nutzungsbedingungen\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md @@ -4000,8 +4642,11 @@ msgstr "Wenn Sie dem Chat beitreten, sehen die Zuschauer die Bedingungen:" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" -msgstr "![Bedingungen](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. On the top of the chat, there are terms and conditions for both the server and the channel.](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px \"Terms\")" +msgstr "" +"![Screenshot einer Chatsitzung. Am oberen Rand des Chats stehen die " +"Bedingungen für den Server und den Kanal.](/peertube-plugin-livechat/images/" +"terms.png?classes=shadow,border&height=400px \"Nutzungsbedingungen\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md @@ -4049,8 +4694,11 @@ msgstr "Wenn Sie ein Peertube-Video ansehen, bei dem der Chat aktiviert ist, seh #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/_index.md #: support/documentation/content/en/intro/_index.md -msgid "![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px)" -msgstr "![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px \"Chat screenshot\")" +msgstr "" +"![Screenshot einer Peertube-Videoseite mit einem Web-Chat rechts neben dem " +"Video.](/peertube-plugin-livechat/images/" +"chat.png?classes=shadow,border&height=200px \"Chat Bildschirmfoto\")" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4070,8 +4718,13 @@ msgstr "Wenn Sie auf der Peertube-Instanz, auf der Sie das Video ansehen, nicht #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" -msgstr "![Chat mit einem anonymen Benutzer](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat. In the participant list, there is John Livingston, and an anonymous account using \"Anonymous 212873\" nickname.](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px \"Chat with an anonymous user\")" +msgstr "" +"![Screenshot eines Chats. In der Teilnehmerliste gibt es John Livingston und " +"einen anonymen Account mit dem Nicknamen \"Anonymous 212873\".](/peertube-" +"plugin-livechat/images/" +"chat_with_anonymous.png?classes=shadow,border&height=200px \"Chat mit einem " +"anonymen Benutzer\")" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4080,8 +4733,12 @@ msgstr "Bevor Sie im Chatraum sprechen können, müssen Sie einen Spitznamen in #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Joining chat when not connected](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px)" -msgstr "![Dem Chat beitreten, wenn nicht verbunden](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat. The current user is not logged in, and must choose a nickname before being able to write in the chat.](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px \"Joining chat when not connected\")" +msgstr "" +"![Screenshot des Chats. Der aktuelle Benutzer ist nicht eingeloggt und muss " +"einen Spitznamen wählen, bevor er im Chat schreiben kann.](/peertube-" +"plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px " +"\"Chat beitreten, wenn nicht verbunden\")" #. type: Title #### #: support/documentation/content/en/documentation/user/viewers.md @@ -4133,8 +4790,13 @@ msgstr "Wenn Sie ein Peertube Konto haben, aber nicht auf der aktuellen Instanz, #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" -msgstr "![Externer Anmeldedialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label login_using_external_account %}}\" dialog. There is a field where you can enter a Peertube url.](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px \"External login dialog\")" +msgstr "" +"![Screenshot des Dialogs \"{{% livechat_label login_using_external_account " +"%}}\". Es gibt ein Feld, in das Sie eine Peertube-URL eingeben können" +".](/peertube-plugin-livechat/images/" +"external_login_dialog.png?classes=shadow,border&height=200px \"Externer " +"Anmeldedialog\")" #. type: Title ## #: support/documentation/content/en/documentation/user/viewers.md @@ -4175,8 +4837,12 @@ msgstr "Um die Liste der Teilnehmer zu sehen, öffnen Sie einfach das rechte Men #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Participants list](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px)" -msgstr "![Teilnehmerliste](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with on the right the list of participants.](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px \"Participants list\")" +msgstr "" +"![Screenshot einer Chatsitzung, rechts die Liste der Teilnehmer](/peertube-" +"plugin-livechat/images/" +"open_participants_list.png?classes=shadow,border&height=200px " +"\"Teilnehmerliste\")" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4208,8 +4874,11 @@ msgstr "Oben im Chat gibt es eine Schaltfläche, um den Chat im Vollbildmodus zu #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/intro/_index.md -msgid "![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px)" -msgstr "![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px \"Fullscreen chat screenshot\")" +msgstr "" +"![Screenshot des Chats über die gesamte Webseite.](/peertube-plugin-livechat/" +"images/fullscreen.png?classes=shadow,border&height=200px \"Vollbild Chat " +"Bildschirmfoto\")" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4266,8 +4935,11 @@ msgstr "Um die Adresse des Raums, dem Sie beitreten möchten, zu erhalten, könn #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share button](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px)" -msgstr "![Teilen Schaltfläche](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat, with a share button on the top.](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px \"Share button\")" +msgstr "" +"![Screenshot des Chats mit einer Schaltfläche zum Teilen am oberen Rand" +".](/peertube-plugin-livechat/images/" +"share_button.png?classes=shadow,border&height=200px \"Teilen Schaltfläche\")" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md @@ -4276,24 +4948,25 @@ msgstr "Standardmäßig ist die Schaltfläche \"Freigeben\" nur für den Eigent #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "Then, choose \"Connect using XMPP\":" -msgstr "Wählen Sie dann \"Verbinden über XMPP\":" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" -msgstr "![XMPP Link teilen](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +msgid "Then, choose \"{{% livechat_label connect_using_xmpp %}}\":" +msgstr "Wählen Sie dann \"{{% livechat_label connect_using_xmpp %}}\":" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md msgid "Then you just have to click on \"open\" or copy/paste the address of the chat room into your XMPP client (using the \"join a room\" feature)." msgstr "Dann müssen Sie nur noch auf \"Öffnen\" klicken oder die Adresse des Chatraums in Ihren XMPP-Client kopieren/einfügen (mit der Funktion \"einem Raum beitreten\")." -#. type: Title # +#. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/_index.md #, no-wrap -msgid "PeerTube plugin livechat" -msgstr "PeerTube plugin livechat" +msgid "Peertube plugin livechat documentation" +msgstr "Peertube Plugin Livechat Dokumentation" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/_index.md +#, no-wrap +msgid "Peertube livechat" +msgstr "Peertube livechat" #. type: Plain text #: support/documentation/content/en/_index.md @@ -4369,8 +5042,13 @@ msgstr "Als Peertube-Administrator können Sie dieses Plugin auf Ihrer Instanz e #. type: Plain text #: support/documentation/content/en/intro/_index.md -msgid "![Livechat installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px)" -msgstr "![Livechat-Installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of Peertube plugins admin page. The search fields contains \"livechat\", and the search results show the livechat plugin.](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px \"Livechat installation\")" +msgstr "" +"![Screenshot der Peertube Plugin Administrator Seite. Die Suchfelder " +"enthalten \"livechat\", und die Suchergebnisse zeigen das Livechat-Plugin" +".](/peertube-plugin-livechat/images/" +"installation.png?classes=shadow,border&height=200px \"Livechat installation\"" +")" #. type: Title ## #: support/documentation/content/en/intro/_index.md @@ -4502,6 +5180,63 @@ msgstr "[Meilensteine auf Github](https://github.com/JohnXLivingston/peertube-pl msgid "If you are a webdesigner or a ConverseJS/Prosody/XMPP expert, and want to help improve this plugin, you are welcome." msgstr "Wenn Sie ein Webdesigner oder ein ConverseJS/Prosody/XMPP-Experte sind und helfen wollen, dieses Plugin zu verbessern, sind Sie gerne willkommen." +#~ msgid "![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" +#~ msgstr "![Externes Anmelden Schaltfläche](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" + +#~ msgid "![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" +#~ msgstr "![Link Teilen Popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" + +#~ msgid "![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" +#~ msgstr "![Link teilen Fenster - Dock Reiter](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" + +#~ msgid "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" +#~ msgstr "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" + +#~ msgid "![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" +#~ msgstr "![Link teilen Fenster - Web Reiter](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" + +#~ msgid "![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" +#~ msgstr "![Chaträume Menü](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" + +#~ msgid "![Message history search](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px)" +#~ msgstr "![Nachrichtenverlaufssuche](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px)" + +#~ msgid "![Moderator Notes Application](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px)" +#~ msgstr "![Moderationsnotizen Anwendung](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px)" + +#~ msgid "![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" +#~ msgstr "![Umfrageformular](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" + +#~ msgid "![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" +#~ msgstr "![Umfrage Start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" + +#~ msgid "![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" +#~ msgstr "![Umfrageende](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" + +#~ msgid "![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" +#~ msgstr "![Infobox Langsamer Modus](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" + +#~ msgid "![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" +#~ msgstr "![Aufgabenanwendung](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" + +#~ msgid "![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" +#~ msgstr "![Aufgabenanwendung](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" + +#~ msgid "![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" +#~ msgstr "![Aufgaben](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" + +#~ msgid "![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" +#~ msgstr "![Aufgabe erstellt](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" + +#~ msgid "![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" +#~ msgstr "![Bedingungen](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" + +#~ msgid "![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" +#~ msgstr "![Externer Anmeldedialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" + +#~ msgid "![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +#~ msgstr "![XMPP Link teilen](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" + #, fuzzy, no-wrap #~ msgid "" #~ "
+ ${ptTr(LOC_PROSODY_FIREWALL_CONFIGURATION_HELP, true)} + + +
${ptTr(LOC_PROSODY_FIREWALL_DISABLED_WARNING, true)}
@@ -30,9 +42,9 @@ export function tplPollForm (el) {
${el.instructions}
@@ -83,21 +83,21 @@ export function tplPoll (el, currentPoll, canVote) { return html`
${currentPoll.over - ? html` + ? html` ` : '' } ${el.collapsed ? html` - + ` : html` - + { + return currentSize + }, + width_is: (sizes) => { + if (!Array.isArray(sizes)) { + sizes = [sizes] + } + if (!currentSize) { return false } + return sizes.includes(currentSize.width) + }, + height_is: (sizes) => { + if (!Array.isArray(sizes)) { + sizes = [sizes] + } + if (!currentSize) { return false } + return sizes.includes(currentSize.height) + } + } + }) _converse.api.listen.on('connected', start) _converse.api.listen.on('reconnected', start) _converse.api.listen.on('disconnected', stop) @@ -42,6 +65,7 @@ function start () { } function stop () { + currentSize = undefined rootResizeObserver.disconnect() const root = document.querySelector('converse-root') if (root) { @@ -60,8 +84,9 @@ function handle (el) { el.setAttribute('livechat-converse-root-width', width) el.setAttribute('livechat-converse-root-height', height) - api.trigger('livechatSizeChanged', { + currentSize = { height: height, width: width - }) + } + api.trigger('livechatSizeChanged', Object.assign({}, currentSize)) // cloning... } diff --git a/conversejs/custom/plugins/tasks/components/muc-task-app-view.js b/conversejs/custom/plugins/tasks/components/muc-task-app-view.js index 155b75a2..d1b8378e 100644 --- a/conversejs/custom/plugins/tasks/components/muc-task-app-view.js +++ b/conversejs/custom/plugins/tasks/components/muc-task-app-view.js @@ -2,36 +2,20 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { api } from '@converse/headless/core' -import { CustomElement } from 'shared/components/element.js' +import { api } from '@converse/headless' +import { MUCApp } from '../../../shared/components/muc-app/index.js' import { tplMUCTaskApp } from '../templates/muc-task-app.js' -import '../styles/muc-task-app.scss' - /** * Custom Element to display the Task Application. */ -export default class MUCTaskApp extends CustomElement { - static get properties () { - return { - model: { type: Object, attribute: true }, // mucModel - show: { type: Boolean, attribute: false } - } - } - - async initialize () { - this.show = api.settings.get('livechat_task_app_restore') && - (window.sessionStorage?.getItem?.('livechat-converse-task-app-show') === '1') - } +export default class MUCTaskApp extends MUCApp { + restoreSettingName = 'livechat_task_app_restore' + sessionStorageRestoreKey = 'livechat-converse-task-app-show' render () { return tplMUCTaskApp(this, this.model) } - - toggleApp () { - this.show = !this.show - window.sessionStorage?.setItem?.('livechat-converse-task-app-show', this.show ? '1' : '') - } } api.elements.define('livechat-converse-muc-task-app', MUCTaskApp) diff --git a/conversejs/custom/plugins/tasks/components/muc-task-list-view.js b/conversejs/custom/plugins/tasks/components/muc-task-list-view.js index 3f97abfd..8688468c 100644 --- a/conversejs/custom/plugins/tasks/components/muc-task-list-view.js +++ b/conversejs/custom/plugins/tasks/components/muc-task-list-view.js @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { CustomElement } from 'shared/components/element.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import tplMucTaskList from '../templates/muc-task-list' import { __ } from 'i18n' diff --git a/conversejs/custom/plugins/tasks/components/muc-task-lists-view.js b/conversejs/custom/plugins/tasks/components/muc-task-lists-view.js index c4429827..a04faa2e 100644 --- a/conversejs/custom/plugins/tasks/components/muc-task-lists-view.js +++ b/conversejs/custom/plugins/tasks/components/muc-task-lists-view.js @@ -2,17 +2,14 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { CustomElement } from 'shared/components/element.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import tplMucTaskLists from '../templates/muc-task-lists' import { __ } from 'i18n' +import { DraggablesCustomElement } from '../../../shared/components/draggables/index.js' import '../styles/muc-task-lists.scss' -import '../styles/muc-task-drag.scss' - -export default class MUCTaskListsView extends CustomElement { - currentDraggedTask = null +export default class MUCTaskListsView extends DraggablesCustomElement { static get properties () { return { model: { type: Object, attribute: true }, @@ -27,42 +24,22 @@ export default class MUCTaskListsView extends CustomElement { return } + this.draggableTagName = 'livechat-converse-muc-task' + this.droppableTagNames = ['livechat-converse-muc-task', 'livechat-converse-muc-task-list'] + this.droppableAlwaysBottomTagNames = ['livechat-converse-muc-task-list'] + // Adding or removing a new task list: we must update. this.listenTo(this.model, 'add', () => this.requestUpdate()) this.listenTo(this.model, 'remove', () => this.requestUpdate()) this.listenTo(this.model, 'sort', () => this.requestUpdate()) - this._handleDragStartBinded = this._handleDragStart.bind(this) - this._handleDragOverBinded = this._handleDragOver.bind(this) - this._handleDragLeaveBinded = this._handleDragLeave.bind(this) - this._handleDragEndBinded = this._handleDragEnd.bind(this) - this._handleDropBinded = this._handleDrop.bind(this) + return super.initialize() } render () { return tplMucTaskLists(this, this.model) } - connectedCallback () { - super.connectedCallback() - this.currentDraggedTask = null - this.addEventListener('dragstart', this._handleDragStartBinded) - this.addEventListener('dragover', this._handleDragOverBinded) - this.addEventListener('dragleave', this._handleDragLeaveBinded) - this.addEventListener('dragend', this._handleDragEndBinded) - this.addEventListener('drop', this._handleDropBinded) - } - - disconnectedCallback () { - super.disconnectedCallback() - this.currentDraggedTask = null - this.removeEventListener('dragstart', this._handleDragStartBinded) - this.removeEventListener('dragover', this._handleDragOverBinded) - this.removeEventListener('dragleave', this._handleDragLeaveBinded) - this.removeEventListener('dragend', this._handleDragEndBinded) - this.removeEventListener('drop', this._handleDropBinded) - } - async submitCreateTaskList (ev) { ev.preventDefault() @@ -96,15 +73,7 @@ export default class MUCTaskListsView extends CustomElement { } } - _getParentTaskEl (target) { - return target.closest?.('livechat-converse-muc-task') - } - - _getParentTaskOrTaskListEl (target) { - return target.closest?.('livechat-converse-muc-task, livechat-converse-muc-task-list') - } - - _isATaskEl (target) { + isATaskEl (target) { return target.nodeName?.toLowerCase() === 'livechat-converse-muc-task' } @@ -112,71 +81,18 @@ export default class MUCTaskListsView extends CustomElement { return target.nodeName?.toLowerCase() === 'livechat-converse-muc-task-list' } - _isOnTopHalf (ev, taskEl) { - const y = ev.clientY - const bounding = taskEl.getBoundingClientRect() - return (y <= bounding.y + (bounding.height / 2)) - } - - _resetDropOver () { - document.querySelectorAll('.livechat-drag-bottom-half, .livechat-drag-top-half').forEach( - el => el.classList.remove('livechat-drag-bottom-half', 'livechat-drag-top-half') - ) - } - - _handleDragStart (ev) { - // The draggable=true is on a livechat-converse-muc-task child - const possibleTaskEl = ev.target.parentElement - if (!this._isATaskEl(possibleTaskEl)) { return } - console.log('[livechat task drag&drop] Starting to drag a task...') - this.currentDraggedTask = possibleTaskEl - this._resetDropOver() - } - - _handleDragOver (ev) { - if (!this.currentDraggedTask) { return } - const taskOrTaskListEl = this._getParentTaskOrTaskListEl(ev.target) - if (!taskOrTaskListEl) { return } - - // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event says we should preventDefault - ev.preventDefault() - - // Are we on the top or bottom part of the taskEl? - // Note: for task list, we always add the task in the task list, so no need to test here. - const topHalf = this._isATaskEl(taskOrTaskListEl) ? this._isOnTopHalf(ev, taskOrTaskListEl) : false - taskOrTaskListEl.classList.add(topHalf ? 'livechat-drag-top-half' : 'livechat-drag-bottom-half') - taskOrTaskListEl.classList.remove(topHalf ? 'livechat-drag-bottom-half' : 'livechat-drag-top-half') - } - - _handleDragLeave (ev) { - if (!this.currentDraggedTask) { return } - const taskOrTaskListEl = this._getParentTaskOrTaskListEl(ev.target) - if (!taskOrTaskListEl) { return } - taskOrTaskListEl.classList.remove('livechat-drag-bottom-half', 'livechat-drag-top-half') - } - - _handleDragEnd (_ev) { - this.currentDraggedTask = null - this._resetDropOver() - } - - _handleDrop (_ev) { - if (!this.currentDraggedTask) { return } - - const droppedOnEl = document.querySelector('.livechat-drag-bottom-half, .livechat-drag-top-half') - const droppedOntaskOrTaskListEl = this._getParentTaskOrTaskListEl(droppedOnEl) - if (!droppedOntaskOrTaskListEl) { return } - + _dropDone (draggedEl, droppedOnEl, onTopHalf) { + super._dropDone(...arguments) console.log('[livechat task drag&drop] Task dropped...') - const task = this.currentDraggedTask.model + const task = draggedEl.model let newOrder, targetTasklist - if (this.isATaskListEl(droppedOntaskOrTaskListEl)) { + if (this.isATaskListEl(droppedOnEl)) { // We dropped on a task list, we must add as first entry. newOrder = 0 - targetTasklist = droppedOntaskOrTaskListEl.model + targetTasklist = droppedOnEl.model if (task.get('list') !== targetTasklist.get('id')) { console.log('[livechat task drag&drop] Changing task list...') task.set('list', targetTasklist.get('id')) @@ -185,9 +101,9 @@ export default class MUCTaskListsView extends CustomElement { console.log('[livechat task drag&drop] Task dropped on tasklist, but already first item, nothing to do') return } - } else if (this._isATaskEl(droppedOntaskOrTaskListEl)) { + } else if (this.isATaskEl(droppedOnEl)) { // We dropped on a task, we must get its order (+1 if !onTopHalf) - const droppedOnTask = droppedOntaskOrTaskListEl.model + const droppedOnTask = droppedOnEl.model if (task === droppedOnTask) { // But of course, if dropped on itself there is nothing to do. console.log('[livechat task drag&drop] Task dropped on itself, nothing to do') @@ -199,9 +115,8 @@ export default class MUCTaskListsView extends CustomElement { task.set('list', droppedOnTask.get('list')) } - const topHalf = droppedOnEl.classList.contains('livechat-drag-top-half') newOrder = droppedOnTask.get('order') ?? 0 - if (!topHalf) { newOrder = Math.max(0, newOrder + 1) } + if (!onTopHalf) { newOrder = Math.max(0, newOrder + 1) } if (typeof newOrder !== 'number' || isNaN(newOrder)) { console.error( @@ -217,45 +132,7 @@ export default class MUCTaskListsView extends CustomElement { return } - if (typeof newOrder !== 'number' || isNaN(newOrder)) { - console.error('[livechat task drag&drop] Computed new order is not a number, aborting.') - return - } - console.log('[livechat task drag&drop] Task new order will be ' + newOrder) - - console.log('[livechat task drag&drop] Reordering tasks...') - let currentOrder = newOrder + 1 - for (const t of targetTasklist.getTasks()) { - if (t === task) { - console.log('[livechat task drag&drop] Skipping the currently moved task') - continue - } - - let order = t.get('order') ?? 0 - if (typeof order !== 'number' || isNaN(order)) { - console.error('[livechat task drag&drop] Found a task with an invalid order, fixing it.') - order = currentOrder // this will cause the code bellow to increment task order - } - if (order < newOrder) { continue } - - currentOrder++ - if (order > currentOrder) { - console.log( - `Task "${t.get('name')}" as already on order greater than ${currentOrder.toString()}, stoping.` - ) - break - } - - console.log(`Changing order of task "${t.get('name')}" to ${currentOrder}`) - t.set('order', currentOrder) - t.saveItem() // TODO: handle errors? - } - - console.log('[livechat task drag&drop] Setting new order on the moved task') - task.set('order', newOrder) - task.saveItem() // TODO: handle errors? - - this._resetDropOver() + this._saveOrders(targetTasklist.getTasks(), task, newOrder) } } diff --git a/conversejs/custom/plugins/tasks/components/muc-task-view.js b/conversejs/custom/plugins/tasks/components/muc-task-view.js index 1d02af85..db7bfdda 100644 --- a/conversejs/custom/plugins/tasks/components/muc-task-view.js +++ b/conversejs/custom/plugins/tasks/components/muc-task-view.js @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { CustomElement } from 'shared/components/element.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import { tplMucTask } from '../templates/muc-task' import { __ } from 'i18n' diff --git a/conversejs/custom/plugins/tasks/index.js b/conversejs/custom/plugins/tasks/index.js index 50b6afd7..8c7b53fb 100644 --- a/conversejs/custom/plugins/tasks/index.js +++ b/conversejs/custom/plugins/tasks/index.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { _converse, converse } from '../../../src/headless/core.js' +import { _converse, converse } from '../../../src/headless/index.js' import { ChatRoomTaskLists } from './task-lists.js' import { ChatRoomTaskList } from './task-list.js' import { ChatRoomTasks } from './tasks.js' @@ -18,9 +18,14 @@ converse.plugins.add('livechat-converse-tasks', { dependencies: ['converse-muc', 'converse-disco', 'converse-pubsub'], initialize () { - _converse.ChatRoomTaskLists = ChatRoomTaskLists - _converse.ChatRoomTaskList = ChatRoomTaskList - _converse.ChatRoomTasks = ChatRoomTasks + Object.assign( + _converse.exports, + { + ChatRoomTaskLists, + ChatRoomTaskList, + ChatRoomTasks + } + ) _converse.api.settings.extend({ livechat_task_app_enabled: false, diff --git a/conversejs/custom/plugins/tasks/modals/pick-task-list.js b/conversejs/custom/plugins/tasks/modals/pick-task-list.js index 7258bd33..1c5e1701 100644 --- a/conversejs/custom/plugins/tasks/modals/pick-task-list.js +++ b/conversejs/custom/plugins/tasks/modals/pick-task-list.js @@ -4,7 +4,7 @@ import BaseModal from 'plugins/modal/modal.js' import tplPickTaskList from './templates/pick-task-list.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import { __ } from 'i18n' export default class PickTaskListModal extends BaseModal { diff --git a/conversejs/custom/plugins/tasks/modals/templates/pick-task-list.js b/conversejs/custom/plugins/tasks/modals/templates/pick-task-list.js index 998fc93a..d872ae16 100644 --- a/conversejs/custom/plugins/tasks/modals/templates/pick-task-list.js +++ b/conversejs/custom/plugins/tasks/modals/templates/pick-task-list.js @@ -19,22 +19,22 @@ export default function (el) { return html` el.onPick(ev)}> - - - ${ - repeat(muc.tasklists, (tasklist) => tasklist.get('id'), (tasklist) => { - return html`${tasklist.get('name')}` - }) - } - - - ${i18nMessage} - - + + + ${ + repeat(muc.tasklists, (tasklist) => tasklist.get('id'), (tasklist) => { + return html`${tasklist.get('name')}` + }) + } + + + ${i18nMessage} + + - - ${__('OK')} - - + + ${__('OK')} + + ` } diff --git a/conversejs/custom/plugins/tasks/styles/muc-task-drag.scss b/conversejs/custom/plugins/tasks/styles/muc-task-drag.scss deleted file mode 100644 index 60904a3d..00000000 --- a/conversejs/custom/plugins/tasks/styles/muc-task-drag.scss +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2024 John Livingston - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -.conversejs { - livechat-converse-muc-task { - &.livechat-drag-bottom-half .task-line { - border-bottom: 4px solid blue; - } - - &.livechat-drag-top-half .task-line { - border-top: 4px solid blue; - } - } - - livechat-converse-muc-task-list { - &.livechat-drag-bottom-half .task-list-line { - border-bottom: 4px solid blue; - } - - &.livechat-drag-top-half .task-list-line { - border-top: 4px solid blue; - } - } -} diff --git a/conversejs/custom/plugins/tasks/task-list.js b/conversejs/custom/plugins/tasks/task-list.js index e58106de..09564263 100644 --- a/conversejs/custom/plugins/tasks/task-list.js +++ b/conversejs/custom/plugins/tasks/task-list.js @@ -7,7 +7,7 @@ import { Model } from '@converse/skeletor/src/model.js' /** * A chat room task list. * @class - * @namespace _converse.ChatRoomTaskList + * @namespace _converse.exports.ChatRoomTaskList * @memberof _converse */ class ChatRoomTaskList extends Model { @@ -40,7 +40,7 @@ class ChatRoomTaskList extends Model { data.list = this.get('id') if (!data.order) { - data.order = 0 + Math.max( + data.order = 1 + Math.max( 0, ...(this.getTasks().map(t => t.get('order') ?? 0).filter(o => !isNaN(o))) ) diff --git a/conversejs/custom/plugins/tasks/task-lists.js b/conversejs/custom/plugins/tasks/task-lists.js index d74edb81..17130a73 100644 --- a/conversejs/custom/plugins/tasks/task-lists.js +++ b/conversejs/custom/plugins/tasks/task-lists.js @@ -7,9 +7,9 @@ import { ChatRoomTaskList } from './task-list' import { initStorage } from '@converse/headless/utils/storage.js' /** - * A list of {@link _converse.ChatRoomTaskList} instances, representing task lists associated to a MUC. + * A list of {@link _converse.exports.ChatRoomTaskList} instances, representing task lists associated to a MUC. * @class - * @namespace _converse.ChatRoomTaskLists + * @namespace _converse.exports.ChatRoomTaskLists * @memberOf _converse */ class ChatRoomTaskLists extends Collection { diff --git a/conversejs/custom/plugins/tasks/task.js b/conversejs/custom/plugins/tasks/task.js index 7d21b947..9bf9c6f6 100644 --- a/conversejs/custom/plugins/tasks/task.js +++ b/conversejs/custom/plugins/tasks/task.js @@ -7,7 +7,7 @@ import { Model } from '@converse/skeletor/src/model.js' /** * A chat room task. * @class - * @namespace _converse.ChatRoomTask + * @namespace _converse.exports.ChatRoomTask * @memberof _converse */ class ChatRoomTask extends Model { diff --git a/conversejs/custom/plugins/tasks/tasks.js b/conversejs/custom/plugins/tasks/tasks.js index 239a1723..facc16d6 100644 --- a/conversejs/custom/plugins/tasks/tasks.js +++ b/conversejs/custom/plugins/tasks/tasks.js @@ -7,9 +7,9 @@ import { ChatRoomTask } from './task' import { initStorage } from '@converse/headless/utils/storage.js' /** - * A list of {@link _converse.ChatRoomTask} instances, representing all tasks associated to a MUC. + * A list of {@link _converse.exports.ChatRoomTask} instances, representing all tasks associated to a MUC. * @class - * @namespace _converse.ChatRoomTasks + * @namespace _converse.exports.ChatRoomTasks * @memberOf _converse */ class ChatRoomTasks extends Collection { diff --git a/conversejs/custom/plugins/tasks/templates/muc-task-app.js b/conversejs/custom/plugins/tasks/templates/muc-task-app.js index 5dcbd8a3..aeaf68e9 100644 --- a/conversejs/custom/plugins/tasks/templates/muc-task-app.js +++ b/conversejs/custom/plugins/tasks/templates/muc-task-app.js @@ -3,28 +3,24 @@ // SPDX-License-Identifier: AGPL-3.0-only import { converseLocalizedHelpUrl } from '../../../shared/lib/help' +import { tplMUCApp } from '../../../shared/components/muc-app/templates/muc-app.js' import { html } from 'lit' import { __ } from 'i18n' export function tplMUCTaskApp (el, mucModel) { if (!mucModel) { // should not happen - el.classList.add('hidden') // we must do this, otherwise will have CSS side effects return html`` } if (!mucModel.tasklists) { // too soon, not initialized yet (this will happen) - el.classList.add('hidden') // we must do this, otherwise will have CSS side effects return html`` } if (!el.show) { - el.classList.add('hidden') return html`` } - el.classList.remove('hidden') - // eslint-disable-next-line no-undef const i18nTasks = __(LOC_tasks) // eslint-disable-next-line no-undef @@ -33,19 +29,11 @@ export function tplMUCTaskApp (el, mucModel) { page: 'documentation/user/streamers/tasks' }) - return html` - - ${i18nTasks} - - - - - - - - ` + return tplMUCApp( + el, + i18nTasks, + helpUrl, + i18nHelp, + html`` + ) } diff --git a/conversejs/custom/plugins/tasks/templates/muc-task-list.js b/conversejs/custom/plugins/tasks/templates/muc-task-list.js index d226211f..0de8494e 100644 --- a/conversejs/custom/plugins/tasks/templates/muc-task-list.js +++ b/conversejs/custom/plugins/tasks/templates/muc-task-list.js @@ -16,17 +16,17 @@ export default function tplMucTaskList (el, tasklist) { // eslint-disable-next-line no-undef const i18nTaskListName = __(LOC_task_list_name) return html` - + ${el.collapsed ? html` - + ` : html` - + ${tasklist.get('name')} - + - - diff --git a/conversejs/custom/plugins/tasks/templates/muc-task-lists.js b/conversejs/custom/plugins/tasks/templates/muc-task-lists.js index 0c94758f..d875f4c1 100644 --- a/conversejs/custom/plugins/tasks/templates/muc-task-lists.js +++ b/conversejs/custom/plugins/tasks/templates/muc-task-lists.js @@ -24,7 +24,7 @@ export default function tplMucTaskLists (el, tasklists) { }) } - + ${i18nCreateTaskList} @@ -34,6 +34,6 @@ export default function tplMucTaskLists (el, tasklists) { ? '' : html`${el.create_tasklist_error_message}` } - + ` } diff --git a/conversejs/custom/plugins/tasks/templates/muc-task.js b/conversejs/custom/plugins/tasks/templates/muc-task.js index 82f8419f..e003341a 100644 --- a/conversejs/custom/plugins/tasks/templates/muc-task.js +++ b/conversejs/custom/plugins/tasks/templates/muc-task.js @@ -13,7 +13,7 @@ export function tplMucTask (el, task) { const doneId = 'livechat-task-done-id-' + task.get('id') return !el.edit ? html` - + ${task.get('description') ?? ''} - - ` : html` - + ${_tplTaskForm(task)} - + + return html` ${_tplTaskForm(undefined)} - + +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { CustomElement } from 'shared/components/element.js' + +import './styles/draggables.scss' + +/** + * This is the base class for custom elements that contains draggable items. + */ +export class DraggablesCustomElement extends CustomElement { + currentDragged = null + + /** + * The tag name for draggable elements. + * Example: livechat-converse-muc-note. + * Must be set in derived class. + */ + draggableTagName = 'invalid-tag-name' + + /** + * The tag names on which we can drop the element. + * Examples: livechat-converse-muc-note, livechat-converse-muc-task, livechat-converse-muc-task-list. + * Must be set in derived class. + */ + droppableTagNames = [] + + /** + * Tag names for which we will always drop to bottom (for example: task lists) + */ + droppableAlwaysBottomTagNames = [] + + initialize () { + this._handleDragStartBinded = this._handleDragStart.bind(this) + this._handleDragOverBinded = this._handleDragOver.bind(this) + this._handleDragLeaveBinded = this._handleDragLeave.bind(this) + this._handleDragEndBinded = this._handleDragEnd.bind(this) + this._handleDropBinded = this._handleDrop.bind(this) + + return super.initialize() + } + + connectedCallback () { + super.connectedCallback() + this.currentDragged = null + this.addEventListener('dragstart', this._handleDragStartBinded) + this.addEventListener('dragover', this._handleDragOverBinded) + this.addEventListener('dragleave', this._handleDragLeaveBinded) + this.addEventListener('dragend', this._handleDragEndBinded) + this.addEventListener('drop', this._handleDropBinded) + } + + disconnectedCallback () { + super.disconnectedCallback() + this.currentDragged = null + this.removeEventListener('dragstart', this._handleDragStartBinded) + this.removeEventListener('dragover', this._handleDragOverBinded) + this.removeEventListener('dragleave', this._handleDragLeaveBinded) + this.removeEventListener('dragend', this._handleDragEndBinded) + this.removeEventListener('drop', this._handleDropBinded) + } + + _isADraggableEl (target) { + return target.nodeName?.toLowerCase() === this.draggableTagName + } + + _getParentDroppableEl (target) { + return target.closest?.(this.droppableTagNames.join(',')) + } + + _isOnTopHalf (ev, el) { + const y = ev.clientY + const bounding = el.getBoundingClientRect() + return (y <= bounding.y + (bounding.height / 2)) + } + + _resetDropOver () { + document.querySelectorAll('.livechat-drag-bottom-half, .livechat-drag-top-half').forEach( + el => el.classList.remove('livechat-drag-bottom-half', 'livechat-drag-top-half') + ) + } + + _handleDragStart (ev) { + // The draggable=true is on a child bode + const possibleEl = ev.target.parentElement + if (!this._isADraggableEl(possibleEl)) { return } + console.log('[livechat drag&drop] Starting to drag a ' + this.draggableTagName + '...') + this.currentDragged = possibleEl + this._resetDropOver() + } + + _handleDragOver (ev) { + if (!this.currentDragged) { return } + const droppableEl = this._getParentDroppableEl(ev.target) + if (!droppableEl) { return } + + // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/drop_event says we should preventDefault + ev.preventDefault() + + // Are we on the top or bottom part of the droppableEl? + let topHalf = false + if (!this.droppableAlwaysBottomTagNames.includes(droppableEl.nodeName.toLowerCase())) { + topHalf = this._isOnTopHalf(ev, droppableEl) + } + droppableEl.classList.add(topHalf ? 'livechat-drag-top-half' : 'livechat-drag-bottom-half') + droppableEl.classList.remove(topHalf ? 'livechat-drag-bottom-half' : 'livechat-drag-top-half') + } + + _handleDragLeave (ev) { + if (!this.currentDragged) { return } + const el = this._getParentDroppableEl(ev.target) + if (!el) { return } + el.classList.remove('livechat-drag-bottom-half', 'livechat-drag-top-half') + } + + _handleDragEnd (_ev) { + this.currentDragged = null + this._resetDropOver() + } + + _handleDrop (_ev) { + if (!this.currentDragged) { return } + + let droppedOnEl = document.querySelector('.livechat-drag-bottom-half, .livechat-drag-top-half') + droppedOnEl = this._getParentDroppableEl(droppedOnEl) + if (!droppedOnEl) { return } + + console.log('[livechat drag&drop] ' + this.draggableTagName + ' dropped...') + + try { + this._dropDone(this.currentDragged, droppedOnEl, droppedOnEl.classList.contains('livechat-drag-top-half')) + } catch (err) { + console.error(err) + } + this._resetDropOver() + } + + /** + * The callback when a valid drop occurs. + * Must be overloaded. + */ + _dropDone (draggedEl, droppedOnEl, onTopHalf) { + console.debug('[livechat drag&drop] Drop done:', draggedEl, droppedOnEl, onTopHalf) + } + + /** + * This method can be called from _dropDone to save the new objects orders. + * For it to work, models must respect following constraints: + * * be a Model + * * have the order attribute + * * have an id attribute (for logging) + * * have get, set and saveItem methods + */ + _saveOrders (models, currentModel, newOrder) { + if (typeof newOrder !== 'number' || isNaN(newOrder)) { + console.error('[livechat drag&drop] Computed new order is not a number, aborting.') + return + } + + console.log('[livechat drag&drop] Reordering models... Model new order will be ' + newOrder) + let currentOrder = newOrder + 1 + for (const m of models) { + if (m === currentModel) { + console.log('[livechat drag&drop] Skipping the currently moved model') + continue + } + + let order = m.get('order') ?? 0 + if (typeof order !== 'number' || isNaN(order)) { + console.error('[livechat drag&drop] Found a model with an invalid order, fixing it.') + order = currentOrder // this will cause the code bellow to increment model order + } + if (order < newOrder) { continue } + + currentOrder++ + if (order > currentOrder) { + console.log( + `Object "${m.get('id')}" as already on order greater than ${currentOrder.toString()}, stoping.` + ) + break + } + + console.log(`Changing order of model "${m.get('id')}" to ${currentOrder}`) + m.set('order', currentOrder) + m.saveItem() // TODO: handle errors? + } + + console.log('[livechat drag&drop] Setting new order on the moved model') + currentModel.set('order', newOrder) + currentModel.saveItem() // TODO: handle errors? + } +} diff --git a/conversejs/custom/shared/components/draggables/styles/draggables.scss b/conversejs/custom/shared/components/draggables/styles/draggables.scss new file mode 100644 index 00000000..93279389 --- /dev/null +++ b/conversejs/custom/shared/components/draggables/styles/draggables.scss @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: 2024 John Livingston + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +.conversejs { + // FIXME: the use of ">" only works if the draggables-lines is a direct + // child of the element. + // We should find a better way to do this (and that will not break for nested + // elements, like task in tast-list). + .livechat-drag-bottom-half > .draggables-line { + border-bottom: 4px solid blue; + } + + .livechat-drag-top-half > .draggables-line { + border-top: 4px solid blue; + } +} diff --git a/conversejs/custom/shared/components/font-awesome.js b/conversejs/custom/shared/components/font-awesome.js index ce9407aa..18ad7348 100644 --- a/conversejs/custom/shared/components/font-awesome.js +++ b/conversejs/custom/shared/components/font-awesome.js @@ -4,7 +4,7 @@ /* eslint-disable max-len */ import { html } from 'lit' -import tplIcons from '../../../src/shared/templates/icons.js' +import tplIcons from '../../../src/shared/components/templates/icons.js' export default () => { // Here we are adding some additonal icons to ConverseJS defaults @@ -28,6 +28,16 @@ export default () => { + + + + + + + + + + ` } diff --git a/conversejs/custom/shared/components/muc-app/index.js b/conversejs/custom/shared/components/muc-app/index.js new file mode 100644 index 00000000..6c794868 --- /dev/null +++ b/conversejs/custom/shared/components/muc-app/index.js @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { CustomElement } from 'shared/components/element.js' +import { api, _converse } from '@converse/headless' +import './styles/muc-app.scss' + +/** + * Base class for MUC App custom elements (task app, notes app, ...). + * This is an abstract class, should not be called directly. + */ +export class MUCApp extends CustomElement { + restoreSettingName = undefined // must be overloaded + sessionStorageRestoreKey = undefined // must be overloaded + + static get properties () { + return { + model: { type: Object, attribute: true }, // mucModel + show: { type: Boolean, attribute: false } + } + } + + async initialize () { + this.classList.add('livechat-converse-muc-app') + this.show = this.restoreSettingName && + api.settings.get(this.restoreSettingName) && + this.sessionStorageRestoreKey && + (window.sessionStorage?.getItem?.(this.sessionStorageRestoreKey) === '1') + + // we listen for livechatSizeChanged event, + // and close all apps except the first if small or medium width. + // Note: this will also be triggered when we first open the page + this.listenTo(_converse, 'livechatSizeChanged', () => { + if (!this.show || !api.livechat_size?.width_is(['small', 'medium'])) { + return + } + // are we the first opened app? + for (const el of document.querySelectorAll('.livechat-converse-muc-app')) { + if (el === this) { break } + if (!el.show) { continue } + console.debug('The livechat size is small or medium, there is already an opened app, so closing myself', this) + // ok, there is already an opened app. + this.toggleApp() // we know we are open + break + } + }) + } + + render () { // must be overloaded. + return '' + } + + updated () { + if (this.innerText.trim() === '') { + this.classList.add('hidden') // we must do this, otherwise will have CSS side effects + } else { + this.classList.remove('hidden') + } + + super.updated() + } + + toggleApp () { + this.show = !this.show + if (this.sessionStorageRestoreKey) { + window.sessionStorage?.setItem?.(this.sessionStorageRestoreKey, this.show ? '1' : '') + } + + if ( + this.show && + api.livechat_size?.width_is(['small', 'medium']) + ) { + // When showing an App, if the screen width is small or medium, we hide the others. + this._closeOtherApps() + } + } + + showApp () { + if (!this.show) { return this.toggleApp() } + } + + hideApp () { + if (this.show) { return this.toggleApp() } + } + + _closeOtherApps () { + document.querySelectorAll('.livechat-converse-muc-app').forEach((el) => { + if (el !== this && el.show) { + console.debug('Closing another app, because livechat width is small or medium', el) + el.toggleApp() + } + }) + } +} diff --git a/conversejs/custom/plugins/tasks/styles/muc-task-app.scss b/conversejs/custom/shared/components/muc-app/styles/muc-app.scss similarity index 89% rename from conversejs/custom/plugins/tasks/styles/muc-task-app.scss rename to conversejs/custom/shared/components/muc-app/styles/muc-app.scss index e636a57e..59eb23a7 100644 --- a/conversejs/custom/plugins/tasks/styles/muc-task-app.scss +++ b/conversejs/custom/shared/components/muc-app/styles/muc-app.scss @@ -5,7 +5,7 @@ */ .conversejs { - livechat-converse-muc-task-app { + .livechat-converse-muc-app { border: var(--occupants-border-left); display: flex; flex-flow: column nowrap; @@ -42,8 +42,8 @@ &[livechat-converse-root-width="small"], &[livechat-converse-root-width="medium"] { - converse-muc-chatarea livechat-converse-muc-task-app:not(.hidden) ~ * { - // on small and medium width, we hide all subsequent siblings of the task app + converse-muc-chatarea .livechat-converse-muc-app:not(.hidden) ~ * { + // on small and medium width, we hide all subsequent siblings of the app // (when app is not hidden) display: none !important; } diff --git a/conversejs/custom/shared/components/muc-app/templates/muc-app.js b/conversejs/custom/shared/components/muc-app/templates/muc-app.js new file mode 100644 index 00000000..85413da9 --- /dev/null +++ b/conversejs/custom/shared/components/muc-app/templates/muc-app.js @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { html } from 'lit' +import { __ } from 'i18n' + +export function tplMUCApp (el, i18nTitle, helpUrl, i18nHelp, content) { + return html` + + ${i18nTitle} + + + + + + + ${content} + ` +} diff --git a/conversejs/custom/shared/lib/pubsub-manager.js b/conversejs/custom/shared/lib/pubsub-manager.js index ceabf378..9df114ef 100644 --- a/conversejs/custom/shared/lib/pubsub-manager.js +++ b/conversejs/custom/shared/lib/pubsub-manager.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { converse, _converse, api } from '../../../src/headless/core.js' +import { converse, _converse, api } from '../../../src/headless/index.js' const { $build, Strophe, $iq, sizzle } = converse.env /** @@ -50,7 +50,7 @@ export class PubSubManager { async start () { // FIXME: handle errors. Find a way to display to user that this failed. - this.stanzaHandler = _converse.connection.addHandler( + this.stanzaHandler = api.connection.get().addHandler( (message) => { try { this._handleMessage(message) @@ -79,7 +79,7 @@ export class PubSubManager { // Note: no need to unsubscribe from the pubsub node, the backend will do when users leave the room. if (this.stanzaHandler) { - _converse.connection.deleteHandler(this.stanzaHandler) + api.connection.get().deleteHandler(this.stanzaHandler) this.stanzaHandler = undefined } } @@ -123,6 +123,7 @@ export class PubSubManager { if (v === undefined) { continue } data[field] = v } + this._additionalModelToData(item, data) console.log('Saving item...') await this._save(type, data, id) @@ -178,6 +179,8 @@ export class PubSubManager { item.c(fieldName).t(data[fieldName]).up() } + this._additionalDataToItemNode(data, item) + await api.pubsub.publish(this.roomJID, this.node, item) } @@ -336,6 +339,7 @@ export class PubSubManager { } } } + this._additionalParseItemNode(itemNode, type, data) return data } @@ -351,4 +355,19 @@ export class PubSubManager { _typeFromCollection (collection) { return Object.values(this.types).find(type => type.collection === collection) } + + /** + * Overload to add some custom code for model to data conversion. + */ + _additionalModelToData (_item, _data) {} + + /** + * Overload to add some custom code for data to stanza conversion. + */ + _additionalDataToItemNode (_data, _item) {} + + /** + * Overload to add some custom code item parsing. + */ + _additionalParseItemNode (_itemNode, _type, _data) {} } diff --git a/conversejs/custom/shared/modals/livechat-external-login.js b/conversejs/custom/shared/modals/livechat-external-login.js index b0accf00..49f20771 100644 --- a/conversejs/custom/shared/modals/livechat-external-login.js +++ b/conversejs/custom/shared/modals/livechat-external-login.js @@ -4,7 +4,7 @@ import { __ } from 'i18n' import BaseModal from 'plugins/modal/modal.js' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import { html } from 'lit' import 'livechat-external-login-content.js' @@ -20,8 +20,8 @@ class ExternalLoginModal extends BaseModal { return __(LOC_login_using_external_account) } - onHide () { - super.onHide() + close () { + super.close() // kill the externalAuthGetResult handler if still there try { if (window.externalAuthGetResult) { window.externalAuthGetResult() } diff --git a/conversejs/custom/shared/styles/_peertubetheme.scss b/conversejs/custom/shared/styles/_peertubetheme.scss index ebee8392..6ac3f33d 100644 --- a/conversejs/custom/shared/styles/_peertubetheme.scss +++ b/conversejs/custom/shared/styles/_peertubetheme.scss @@ -8,7 +8,7 @@ .dropdown-menu { // Fixing all dropdown colors --text-color: #212529; // default bootstrap color for dropdown-items - --text-color-lighten-15-percent: #8c8c8c; // default ConverseJS theme color + --inverse-link-color: #8c8c8c; // default ConverseJS theme color background-color: #fff; // this is the default bootstrap color, used by ConverseJS @@ -27,6 +27,7 @@ border: 1px dashed var(--peertube-menu-background); color: var(--peertube-main-foreground); background-color: var(--peertube-main-background); + margin: 0 5px; .livechat-hide-slow-mode-info-box { cursor: pointer; diff --git a/conversejs/custom/shared/styles/_variables.scss b/conversejs/custom/shared/styles/_variables.scss index 4d43fd4c..828592fc 100644 --- a/conversejs/custom/shared/styles/_variables.scss +++ b/conversejs/custom/shared/styles/_variables.scss @@ -34,12 +34,16 @@ body.converse-fullscreen.theme-peertube, body.converse-embedded converse-root.theme-peertube { --foreground: var(--peertube-main-foreground); --background: var(--peertube-main-background); + --badge-color: var(--background); + --button-hover-text-color: var(--background); --subdued-color: #a8aba1; + --muc-color: var(--peertube-button-background); --green: #3aa569; // only in this file --redder-orange: #e77051; // only in this file --orange: #e7a151; // only in this file --light-blue: #578ea9; // only in this file --lighter-blue: #85b47b; // only in this file + --chat-color: var(--green); // FIXME: copied from Converse. Is there side effects? --chat-status-online: var(--green); --chat-status-busy: var(--redder-orange); --chat-status-away: var(--orange); @@ -55,7 +59,6 @@ body.converse-embedded converse-root.theme-peertube { --text-shadow-color: var(--peertube-main-background); // FIXME: should be a little different from background --text-color: var(--peertube-input-foreground); --controlbox-text-color: var(--peertube-input-foreground); // Note: controlbox is not used - --text-color-lighten-15-percent: var(--peertube-input-foreground); --message-text-color: var(--peertube-input-foreground); --message-receipt-color: var(--green); --save-button-color: var(--green); @@ -73,7 +76,6 @@ body.converse-embedded converse-root.theme-peertube { --chat-correcting-color: var(--peertube-grey-background); --chat-head-color-dark: #1e9652; // should not be used in this plugin --chat-head-color-darker: #0e763b; // should not be used in this plugin - --chat-head-color-lighten-50-percent: #e7f7ee; // should not be used in this plugin --chat-head-color: var(--green); --chat-head-text-color: var(--peertube-input-foreground); --chat-toolbar-btn-color: var(--peertube-button-background); @@ -106,7 +108,6 @@ body.converse-embedded converse-root.theme-peertube { --controlbox-pane-background-color: #333; --controlbox-pane-bg-hover-color: #464646; --panel-divider-color: #333; - --chat-gutter: 0.5em; --minimized-chats-width: 130px; --mobile-chat-width: 100%; --mobile-chat-height: 400px; @@ -119,9 +120,10 @@ body.converse-embedded converse-root.theme-peertube { --chatroom-badge-color: var(--peertube-button-background); --chatroom-badge-hover-color: var(--peertube-button-background); --chatroom-correcting-color: var(--peertube-grey-background); - --chatroom-head-bg-color-dark: #d24e2b; + --chatroom-head-bg-color-dark: var(--peertube-button-background); --chatroom-head-bg-color: var(--peertube-menu-background); - --chatroom-head-border-bottom: 1px solid var(--peertube-grey-foreground); + --chatroom-head-border-bottom: 0.15em solid var(--peertube-grey-foreground); + --chatroom-head-fg-color: var(--subdued-color); --chatroom-head-button-color: #999; --chatroom-head-color: var(--peertube-menu-foreground); --chatroom-head-description-border-left: 1px solid #ddd; @@ -163,6 +165,7 @@ body.converse-embedded converse-root.theme-peertube { --fullpage-chat-width: 100%; --fullpage-emoji-picker-height: 300px; --fullpage-max-chat-textarea-height: 15em; + --overlayed-chat-gutter: 1em; --overlayed-chat-head-height: 55px; --overlayed-chat-height: 450px; --overlayed-chat-width: 300px; diff --git a/conversejs/custom/shared/styles/livechat.scss b/conversejs/custom/shared/styles/livechat.scss index 2471de67..1ae46aaf 100644 --- a/conversejs/custom/shared/styles/livechat.scss +++ b/conversejs/custom/shared/styles/livechat.scss @@ -60,7 +60,7 @@ body.livechat-readonly.livechat-noscroll { } } -// Viewer mode +// Viewer mode (before the user has chosen its nickname) .livechat-viewer-mode-content { display: none; @@ -73,7 +73,7 @@ body.livechat-readonly.livechat-noscroll { gap: 0.5em 10px; align-items: baseline; - .form-group, + fieldset, label { margin-bottom: 0 !important; // replaced by the gap on .livechat-viewer-mode-content } @@ -171,7 +171,8 @@ body.converse-embedded { #peertube-plugin-livechat-container { converse-muc-message-form { // For an unknown reason, message field in truncated... so adding a bottom margin. - margin-bottom: 6px; + // We also add left and right margin, as Converse v11 adds a g-0 class on converse-muc-chatarea + margin: 0 1px 6px 5px; } } @@ -187,4 +188,44 @@ body.converse-embedded { // So we must revert appearance: appearance: revert !important; } + + .toolbar-buttons { + // Converse v11 removed the toggle_occupant button on the right. + // To add it back, we must ensure that this toolbar takes all the width, and + // that the toggle-occupants button is on the right. + flex-grow: 2; + + .toggle-occupants { + // Cancelling the flex-grow from btn-group + flex-grow: 0 !important; + + // This margin-left trick is to align the button on the right. + margin-left: auto !important; + order: 99; + } + } +} + +/* stylelint-disable-next-line no-descending-specificity */ +#conversejs { // here we use the id have gretter priority + // These CSS are tricks: Converse v11 tries to hide the MUC when screen width is under 768px. + // We don't want that, so we cancel the d-none. + // FIXME: these hacks should be temporary, waiting for some improvement on Converse. + converse-muc-chatarea { + .chat-area.d-none { + display: flex !important; + } + + /* stylelint-disable-next-line no-descending-specificity */ + converse-muc-sidebar { + // we must not use !important for flex, it would break resizing. + // That's why we use #conversejs insteand of .conversejs for this block. + flex: 0 0 min(400px, 50%); + min-width: min(200px, 50%) !important; + + .occupants { + width: 100%; + } + } + } } diff --git a/conversejs/custom/templates/background_logo.js b/conversejs/custom/templates/background_logo.js index 585de358..4548f2a5 100644 --- a/conversejs/custom/templates/background_logo.js +++ b/conversejs/custom/templates/background_logo.js @@ -5,7 +5,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { html } from 'lit' -import { api } from '@converse/headless/core.js' +import { api } from '@converse/headless/index.js' export default () => html` diff --git a/conversejs/custom/templates/livechat-external-login-modal.js b/conversejs/custom/templates/livechat-external-login-modal.js index a8cbca95..0bba91de 100644 --- a/conversejs/custom/templates/livechat-external-login-modal.js +++ b/conversejs/custom/templates/livechat-external-login-modal.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { _converse, api } from '@converse/headless/core' +import { _converse, api } from '@converse/headless' import { __ } from 'i18n' import { html } from 'lit' @@ -117,7 +117,7 @@ export const tplExternalLoginModal = (el, o) => { ${ externalButtons.map(button => html` - { @@ -187,7 +187,9 @@ export const tplExternalLoginModal = (el, o) => { // eslint-disable-next-line no-undef __(LOC_login_remote_peertube_video_not_found_try_anyway) } - el.openUrlTargetTop(o.remote_peertube_try_anyway_url)}>${ + el.openUrlTargetTop(o.remote_peertube_try_anyway_url)} + >${ // eslint-disable-next-line no-undef __(LOC_login_remote_peertube_video_not_found_try_anyway_button) } diff --git a/conversejs/custom/templates/muc-bottom-panel.js b/conversejs/custom/templates/muc-bottom-panel.js index cfac6d2c..91b12bb4 100644 --- a/conversejs/custom/templates/muc-bottom-panel.js +++ b/conversejs/custom/templates/muc-bottom-panel.js @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { __ } from 'i18n' -import { _converse, api } from '@converse/headless/core' +import { _converse, api } from '@converse/headless' import { html } from 'lit' import tplMucBottomPanel from '../../src/plugins/muc-views/templates/muc-bottom-panel.js' import { CustomElement } from 'shared/components/element.js' @@ -79,7 +79,7 @@ class SlowMode extends CustomElement { api.elements.define('livechat-slow-mode', SlowMode) const tplSlowMode = (o) => { - if (!o.can_edit) { return html`` } + if (!o.can_post) { return html`` } return html`` } @@ -97,7 +97,7 @@ const tplViewerMode = (o) => { setNickname(ev, model)}> ${i18nHeading} - + { class="form-control" placeholder="${i18nNickname}"/> - + @@ -117,7 +117,7 @@ const tplViewerMode = (o) => { : html` - { + { ev.preventDefault() api.modal.show('converse-livechat-external-login') }}>${i18nExternalLogin} @@ -128,17 +128,9 @@ const tplViewerMode = (o) => { } export default (o) => { - // ConverseJS 10.x does not handle properly the visitor role in unmoderated rooms. - // See https://github.com/conversejs/converse.js/issues/3428 for more info. - // So we will do a dirty hack here to fix this case. - // Note: ConverseJS 11.x has changed the code, and could be fixed more cleanly (or will be fixed if #3428 is fixed). - if (o.can_edit && o.model.getOwnRole() === 'visitor') { - o.can_edit = false - } - let mutedAnonymousMessage if ( - !o.can_edit && + !o.can_post && o.model.features?.get?.('x_peertubelivechat_mute_anonymous') && _converse.api.settings.get('livechat_specific_is_anonymous') === true ) { diff --git a/conversejs/custom/templates/muc-chatarea.js b/conversejs/custom/templates/muc-chatarea.js index 726e8fd5..d2067c4e 100644 --- a/conversejs/custom/templates/muc-chatarea.js +++ b/conversejs/custom/templates/muc-chatarea.js @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: AGPL-3.0-only -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import tplMUCChatarea from '../../src/plugins/muc-views/templates/muc-chatarea.js' import { html } from 'lit' @@ -13,5 +13,15 @@ export default (o) => { ? html`` : '' } + ${ + o?.model && api.settings.get('livechat_note_app_enabled') + ? html`` + : '' + } + ${ + o?.model && api.settings.get('livechat_mam_search_app_enabled') + ? html`` + : '' + } ${tplMUCChatarea(o)}` } diff --git a/conversejs/custom/templates/muc-head.js b/conversejs/custom/templates/muc-head.js index d926ed32..ebd1ef7a 100644 --- a/conversejs/custom/templates/muc-head.js +++ b/conversejs/custom/templates/muc-head.js @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import { html } from 'lit' -import { api } from '@converse/headless/core' +import { api } from '@converse/headless' import { until } from 'lit/directives/until.js' import { repeat } from 'lit/directives/repeat.js' import { unsafeHTML } from 'lit/directives/unsafe-html.js' diff --git a/conversejs/custom/templates/muc.js b/conversejs/custom/templates/muc.js index f03ce6f1..d3da4caf 100644 --- a/conversejs/custom/templates/muc.js +++ b/conversejs/custom/templates/muc.js @@ -17,12 +17,12 @@ export default (o) => { ${ o.model ? html` - + - ${getChatRoomBodyTemplate(o)}` + ${getChatRoomBodyTemplate(o)}` : ''} ` } diff --git a/conversejs/custom/webpack.livechat.js b/conversejs/custom/webpack.livechat.js index bf6e041c..6825b35b 100644 --- a/conversejs/custom/webpack.livechat.js +++ b/conversejs/custom/webpack.livechat.js @@ -44,7 +44,7 @@ module.exports = merge(prod, { '../../templates/background_logo.js$': path.resolve(__dirname, 'custom/templates/background_logo.js'), './templates/muc-chatarea.js': path.resolve('custom/templates/muc-chatarea.js'), - '../templates/icons.js': path.resolve(__dirname, 'custom/shared/components/font-awesome.js'), + './templates/icons.js': path.resolve(__dirname, 'custom/shared/components/font-awesome.js'), 'shared/styles/index.scss$': path.resolve(__dirname, 'custom/shared/styles/livechat.scss'), diff --git a/conversejs/lib/converse-params.ts b/conversejs/lib/converse-params.ts index e7ef1c06..cbf28ed8 100644 --- a/conversejs/lib/converse-params.ts +++ b/conversejs/lib/converse-params.ts @@ -71,8 +71,7 @@ function defaultConverseParams ( visible_toolbar_buttons: { call: false, spoiler: false, - emoji: true, - toggle_occupants: true + emoji: true }, theme: theme || 'peertube', dark_theme: theme || 'peertube', // dark theme should be the same as theme @@ -97,6 +96,7 @@ function defaultConverseParams ( pruning_behavior: 'unscrolled', colorize_username: true, send_chat_markers: [], + reuse_scram_keys: false, // for now we don't use this. // This is a specific settings, that is used in ConverseJS customization, to force avatars loading in readonly mode. livechat_load_all_vcards: !!forceReadonly, diff --git a/conversejs/lib/plugins/livechat-mini-muc-head.ts b/conversejs/lib/plugins/livechat-mini-muc-head.ts index 82f71484..49815919 100644 --- a/conversejs/lib/plugins/livechat-mini-muc-head.ts +++ b/conversejs/lib/plugins/livechat-mini-muc-head.ts @@ -16,7 +16,7 @@ export const livechatMiniMucHeadPlugin = { }) _converse.api.listen.on('getHeadingButtons', (view: any, buttons: any[]) => { - if (view.model.get('type') !== _converse.CHATROOMS_TYPE) { + if (view.model.get('type') !== _converse.constants.CHATROOMS_TYPE) { // only on MUC. return buttons } diff --git a/conversejs/lib/plugins/livechat-specific.ts b/conversejs/lib/plugins/livechat-specific.ts index 6c7fdf43..6313564d 100644 --- a/conversejs/lib/plugins/livechat-specific.ts +++ b/conversejs/lib/plugins/livechat-specific.ts @@ -2,6 +2,14 @@ // // SPDX-License-Identifier: AGPL-3.0-only +import { customizeHeading } from './livechat-specific/heading' +import { customizeToolbar } from './livechat-specific/toolbar' +import { initReconnectionStuff } from './livechat-specific/reconnection' +import { chatRoomOverrides } from './livechat-specific/chatroom' +import { chatRoomMessageOverrides } from './livechat-specific/chatroom-message' +import { customizeMessageAction } from './livechat-specific/message-action' +import { customizeProfileModal } from './livechat-specific/profile' + export const livechatSpecificsPlugin = { dependencies: ['converse-muc', 'converse-muc-views'], initialize: function (this: any) { @@ -14,101 +22,17 @@ export const livechatSpecificsPlugin = { livechat_specific_is_anonymous: false }) - _converse.api.listen.on('getHeadingButtons', (view: any, buttons: any[]) => { - if (view.model.get('type') !== _converse.CHATROOMS_TYPE) { - // only on MUC. - return buttons - } - - if (_converse.api.settings.get('livechat_specific_external_authent')) { - // Adding a logout button - buttons.push({ - i18n_text: _converse.__('Log out'), - handler: async (ev: Event) => { - ev.preventDefault() - ev.stopPropagation() - - const messages = [_converse.__('Are you sure you want to leave this groupchat?')] - const result = await _converse.api.confirm(_converse.__('Confirm'), messages) - if (!result) { return } - - // Deleting access token in sessionStorage. - window.sessionStorage.removeItem('peertube-plugin-livechat-external-auth-oidc-token') - - const reconnectMode = _converse.api.settings.get('livechat_external_auth_reconnect_mode') - if (reconnectMode === 'button-close-open') { - const button = document.getElementsByClassName('peertube-plugin-livechat-button-close')[0] - if ((button as HTMLAnchorElement).click) { (button as HTMLAnchorElement).click() } - return - } - - window.location.reload() - }, - a_class: 'close-chatbox-button', - icon_class: 'fa-sign-out-alt', - name: 'signout' - }) - } - - return buttons - }) + customizeHeading(this) + customizeToolbar(this) + customizeMessageAction(this) + customizeProfileModal(this) _converse.api.listen.on('chatRoomViewInitialized', function (this: any, _model: any): void { // Remove the spinner if present... document.getElementById('livechat-loading-spinner')?.remove() }) - // Adding a method on window.converse, so we can close the chat on navigation-end event - // (when chatIncludeMode is peertube-*) - window.converse.livechatDisconnect = function livechatDisconnect () { - if (_converse.api.connection.connected()) { - console.log('[livechatSpecificsPlugin] disconnecting converseJS...') - _converse.api.user.logout() - } - } - - // To reconnect ConverseJS when joining another room (or the same one), - // we store the relevant closure function: - window.reconnectConverse = function reconnectConverse (params: any): void { - console.log('[livechatSpecificsPlugin] reconnecting converseJS...') - - // The new room to join: - _converse.api.settings.set('auto_join_rooms', params.auto_join_rooms) - _converse.api.settings.set('notify_all_room_messages', params.notify_all_room_messages) - - // update connection parameters (in case the user logged in after the first chat) - for (const k of [ - 'bosh_service_url', 'websocket_url', - 'authentication', 'nickname', 'muc_nickname_from_jid', 'auto_login', 'jid', 'password', 'keepalive' - ]) { - _converse.api.settings.set(k, params[k]) - } - - // update other settings - for (const k of [ - 'hide_muc_participants', - 'livechat_enable_viewer_mode', - 'livechat_external_auth_oidc_buttons', - 'livechat_external_auth_reconnect_mode', - 'livechat_mini_muc_head', - 'livechat_specific_external_authent', - 'livechat_task_app_enabled', - 'livechat_task_app_restore', - 'livechat_custom_emojis_url', - 'emoji_categories' - ]) { - _converse.api.settings.set(k, params[k]) - } - - // We also unload emojis, in case there are custom emojis. - window.converse.emojis = { - initialized: false, - initialized_promise: getOpenPromise() - } - - // Then login. - _converse.api.user.login() - } + initReconnectionStuff(this) if (window.location.protocol === 'http:') { // We are probably on a dev instance, so we will add _converse in window: @@ -116,70 +40,7 @@ export const livechatSpecificsPlugin = { } }, overrides: { - ChatRoom: { - getActionInfoMessage: function (this: any, code: string, nick: string, actor: any): any { - if (code === '303') { - // When there is numerous anonymous users joining at the same time, - // they can all change their nicknames at the same time, generating a log of action messages. - // To mitigate this, will don't display nickname changes if the previous nick is something like - // 'Anonymous 12345'. - if (/^Anonymous \d+$/.test(nick)) { - // To avoid displaying the message, we just have to return an empty one - // (createInfoMessage will ignore if !data.message). - return null - } - } - return this.__super__.getActionInfoMessage(code, nick, actor) - } - }, - ChatRoomMessage: { - /* By default, ConverseJS groups messages from the same users for a 10 minutes period. - * This make no sense in a livechat room. So we override isFollowup to ignore. */ - isFollowup: function isFollowup () { return false } - }, - ChatRoomOccupants: { - comparator: function (this: any, occupant1: any, occupant2: any): Number { - // Overriding Occupants comparators, to display anonymous users at the end of the list. - const nick1: string = occupant1.getDisplayName() - const nick2: string = occupant2.getDisplayName() - const b1 = nick1.startsWith('Anonymous ') - const b2 = nick2.startsWith('Anonymous ') - if (b1 === b2) { - // Both startswith anonymous, or non of it: fallback to the standard comparator. - return this.__super__.comparator(occupant1, occupant2) - } - // Else: Anonymous always last. - return b1 ? 1 : -1 - } - } + ChatRoom: chatRoomOverrides(), + ChatRoomMessage: chatRoomMessageOverrides() } } - -// FIXME: this function is copied from @converse. Should not do so. -function getOpenPromise (): any { - const wrapper: any = { - isResolved: false, - isPending: true, - isRejected: false - } - const promise: any = new Promise((resolve, reject) => { - wrapper.resolve = resolve - wrapper.reject = reject - }) - Object.assign(promise, wrapper) - promise.then( - function (v: any) { - promise.isResolved = true - promise.isPending = false - promise.isRejected = false - return v - }, - function (e: any) { - promise.isResolved = false - promise.isPending = false - promise.isRejected = true - throw (e) - } - ) - return promise -} diff --git a/conversejs/lib/plugins/livechat-specific/chatroom-message.ts b/conversejs/lib/plugins/livechat-specific/chatroom-message.ts new file mode 100644 index 00000000..c34ddff7 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/chatroom-message.ts @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +export function chatRoomMessageOverrides (): {[key: string]: Function} { + return { + /* By default, ConverseJS groups messages from the same users for a 10 minutes period. + * This make no sense in a livechat room. So we override isFollowup to ignore. */ + isFollowup: function isFollowup () { return false } + } +} diff --git a/conversejs/lib/plugins/livechat-specific/chatroom.ts b/conversejs/lib/plugins/livechat-specific/chatroom.ts new file mode 100644 index 00000000..297c0ed4 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/chatroom.ts @@ -0,0 +1,48 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +export function chatRoomOverrides (): {[key: string]: Function} { + return { + getActionInfoMessage: function getActionInfoMessage (this: any, code: string, nick: string, actor: any): any { + if (code === '303') { + // When there is numerous anonymous users joining at the same time, + // they can all change their nicknames at the same time, generating a log of action messages. + // To mitigate this, will don't display nickname changes if the previous nick is something like + // 'Anonymous 12345'. + if (/^Anonymous \d+$/.test(nick)) { + // To avoid displaying the message, we just have to return an empty one + // (createInfoMessage will ignore if !data.message). + return null + } + } + return this.__super__.getActionInfoMessage(code, nick, actor) + }, + canPostMessages: function canPostMessages (this: any) { + // ConverseJS does not handle properly the visitor role in unmoderated rooms. + // See https://github.com/conversejs/converse.js/issues/3428 for more info. + // FIXME: if #3428 is fixed, remove this override. + return this.isEntered() && this.getOwnRole() !== 'visitor' + }, + initOccupants: function initOccupants (this: any) { + const r = this.__super__.initOccupants() + + const originalComparatorFunction: Function = this.occupants.comparator + this.occupants.comparator = function (this: any, occupant1: any, occupant2: any): Number { + // Overriding Occupants comparators, to display anonymous users at the end of the list. + const nick1: string = occupant1.getDisplayName() + const nick2: string = occupant2.getDisplayName() + const b1 = nick1.startsWith('Anonymous ') + const b2 = nick2.startsWith('Anonymous ') + if (b1 === b2) { + // Both startswith anonymous, or non of it: fallback to the standard comparator. + return originalComparatorFunction.call(this, occupant1, occupant2) + } + // Else: Anonymous always last. + return b1 ? 1 : -1 + } + + return r + } + } +} diff --git a/conversejs/lib/plugins/livechat-specific/heading.ts b/conversejs/lib/plugins/livechat-specific/heading.ts new file mode 100644 index 00000000..38170d14 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/heading.ts @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { destroyMUC } from './utils' + +/** + * Do some customization on MUCHeading: + * * adds a logout button for users that are authenticated with an external account + * * change the destroyMUC handler + * + * @param plugin The plugin object + */ +export function customizeHeading (plugin: any): void { + const _converse = plugin._converse + _converse.api.listen.on('getHeadingButtons', getHeadingButtons.bind(plugin)) + overrideMUCHeadingElement(_converse) +} + +function getHeadingButtons (this: any, view: any, buttons: any[]): any { + const _converse = this._converse + + if (view.model.get('type') !== _converse.constants.CHATROOMS_TYPE) { + // only on MUC. + return buttons + } + + if (_converse.api.settings.get('livechat_specific_external_authent')) { + // Adding a logout button + buttons.push({ + i18n_text: _converse.__('Log out'), + handler: async (ev: Event) => { + ev.preventDefault() + ev.stopPropagation() + + const messages = [_converse.__('Are you sure you want to leave this groupchat?')] + const result = await _converse.api.confirm(_converse.__('Confirm'), messages) + if (!result) { return } + + // Deleting access token in sessionStorage. + window.sessionStorage.removeItem('peertube-plugin-livechat-external-auth-oidc-token') + + const reconnectMode = _converse.api.settings.get('livechat_external_auth_reconnect_mode') + if (reconnectMode === 'button-close-open') { + const button = document.getElementsByClassName('peertube-plugin-livechat-button-close')[0] + if ((button as HTMLAnchorElement).click) { (button as HTMLAnchorElement).click() } + return + } + + window.location.reload() + }, + a_class: 'close-chatbox-button', + icon_class: 'fa-sign-out-alt', + name: 'signout' + }) + } + + return buttons +} + +/** + * Override the MUCHeading custom element, to customize the destroyMUC function. + */ +function overrideMUCHeadingElement (_converse: any): void { + const MUCHeading = _converse.api.elements.registry['converse-muc-heading'] + if (MUCHeading) { + class MUCHeadingOverloaded extends MUCHeading { + async destroy (ev: Event): Promise { + ev.preventDefault() + await destroyMUC(_converse, this.model) // here we call a custom version of destroyMUC + } + } + _converse.api.elements.define('converse-muc-heading', MUCHeadingOverloaded) + } +} diff --git a/conversejs/lib/plugins/livechat-specific/message-action.ts b/conversejs/lib/plugins/livechat-specific/message-action.ts new file mode 100644 index 00000000..87bb70ca --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/message-action.ts @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * Do some customization on Message actions custom elements: + * * override the copy text method to add meta data + * + * @param plugin The plugin object + */ +export function customizeMessageAction (plugin: any): void { + const _converse = plugin._converse + const MessageActions = _converse.api.elements.registry['converse-message-actions'] + if (MessageActions) { + class MessageActionsOverloaded extends MessageActions { + async onMessageCopyButtonClicked (ev?: Event): Promise { + ev?.preventDefault?.() + let txt = '' + try { + txt += this.model.getDisplayName() as string + txt += ' - ' + const date = new Date(this.model.get('edited') || this.model.get('time')) + txt += date.toLocaleDateString() + ' ' + date.toLocaleTimeString() + txt += '\n' + } catch {} + txt += this.model.getMessageText() as string + await navigator.clipboard.writeText(txt) + } + } + _converse.api.elements.define('converse-message-actions', MessageActionsOverloaded) + } +} diff --git a/conversejs/lib/plugins/livechat-specific/profile.ts b/conversejs/lib/plugins/livechat-specific/profile.ts new file mode 100644 index 00000000..fbb60bd6 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/profile.ts @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * Livechat Converse does not include plugins/profile, + * so the default profile modal is broken. + * Moreover this modal includes some features that we do not want (password change, ...). + * So we simply define the converse-profile-modal to be converse-muc-occupant-modal! + * @param plugin The plugin object + */ +export function customizeProfileModal (plugin: any): void { + const _converse = plugin._converse + const OccupantModal = _converse.api.elements.registry['converse-muc-occupant-modal'] + if (!OccupantModal) { return } + class ProfileModal extends OccupantModal { + initialize (): any { + // We just need to change the modal for the occupant: + if (this.model?.getOccupant) { + this.model = this.model.getOccupant() + } + return super.initialize() + } + } + _converse.api.elements.define('converse-profile-modal', ProfileModal) +} diff --git a/conversejs/lib/plugins/livechat-specific/reconnection.ts b/conversejs/lib/plugins/livechat-specific/reconnection.ts new file mode 100644 index 00000000..b75bf488 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/reconnection.ts @@ -0,0 +1,66 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import { getOpenPromise } from './utils' + +/** + * Initialiaze some function on `window` that will be used for the reconnection process. + * + * @param plugin The plugin object + */ +export function initReconnectionStuff (plugin: any): void { + const _converse = plugin._converse + + // Adding a method on window.converse, so we can close the chat on navigation-end event + // (when chatIncludeMode is peertube-*) + window.converse.livechatDisconnect = function livechatDisconnect () { + if (_converse.api.connection.connected()) { + console.log('[livechatSpecificsPlugin] disconnecting converseJS...') + _converse.api.user.logout() + } + } + + // To reconnect ConverseJS when joining another room (or the same one), + // we store the relevant closure function: + window.reconnectConverse = function reconnectConverse (params: any): void { + console.log('[livechatSpecificsPlugin] reconnecting converseJS...') + + // The new room to join: + _converse.api.settings.set('auto_join_rooms', params.auto_join_rooms) + _converse.api.settings.set('notify_all_room_messages', params.notify_all_room_messages) + + // update connection parameters (in case the user logged in after the first chat) + for (const k of [ + 'bosh_service_url', 'websocket_url', + 'authentication', 'nickname', 'muc_nickname_from_jid', 'auto_login', 'jid', 'password', 'keepalive' + ]) { + _converse.api.settings.set(k, params[k]) + } + + // update other settings + for (const k of [ + 'hide_muc_participants', + 'livechat_enable_viewer_mode', + 'livechat_external_auth_oidc_buttons', + 'livechat_external_auth_reconnect_mode', + 'livechat_mini_muc_head', + 'livechat_specific_external_authent', + 'livechat_task_app_enabled', + 'livechat_task_app_restore', + 'livechat_custom_emojis_url', + 'emoji_categories' + ]) { + _converse.api.settings.set(k, params[k]) + } + + // We also unload emojis, in case there are custom emojis. + window.converse.emojis = { + initialized: false, + initialized_promise: getOpenPromise() + } + + // Then login. + _converse.api.user.login() + } +} diff --git a/conversejs/lib/plugins/livechat-specific/toolbar.ts b/conversejs/lib/plugins/livechat-specific/toolbar.ts new file mode 100644 index 00000000..32c4ce6f --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/toolbar.ts @@ -0,0 +1,58 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * Do some customization on the toolbar: + * * change the appearance of the toggle occupants button + * + * @param plugin The plugin object + */ +export function customizeToolbar (plugin: any): void { + const _converse = plugin._converse + _converse.api.listen.on('getToolbarButtons', getToolbarButtons.bind(plugin)) +} + +function getToolbarButtons (this: any, toolbarEl: any, buttons: any[]): any { + const _converse = this._converse + + // Adding a toggle_occupants button. + // Note: we don't need to test conditions, we know the button was here. + const i18nHideOccupants = _converse.__('Hide participants') + const i18nShowOccupants = _converse.__('Show participants') + const html = window.converse.env.html + const icon = toolbarEl.hidden_occupants + ? html` + + + ` + : html` + + + ` + buttons.push(html` + { + ev?.preventDefault() + toolbarEl.model.save({ + hidden_occupants: !toolbarEl.model.get('hidden_occupants') + }) + }}> + ${icon} + ` + ) + return buttons +} diff --git a/conversejs/lib/plugins/livechat-specific/utils.ts b/conversejs/lib/plugins/livechat-specific/utils.ts new file mode 100644 index 00000000..14644041 --- /dev/null +++ b/conversejs/lib/plugins/livechat-specific/utils.ts @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +// FIXME: this function is copied from @converse. Should not do so. +export function getOpenPromise (): any { + const wrapper: any = { + isResolved: false, + isPending: true, + isRejected: false + } + const promise: any = new Promise((resolve, reject) => { + wrapper.resolve = resolve + wrapper.reject = reject + }) + Object.assign(promise, wrapper) + promise.then( + function (v: any) { + promise.isResolved = true + promise.isPending = false + promise.isRejected = false + return v + }, + function (e: any) { + promise.isResolved = false + promise.isPending = false + promise.isRejected = true + throw (e) + } + ) + return promise +} + +export async function destroyMUC (_converse: any, model: any): Promise { + const __ = _converse.__ + const messages = [__('Are you sure you want to destroy this groupchat?')] + // Note: challenge and newjid make no sens for peertube-plugin-livechat, + // we remove them comparing to the original function. + let fields = [ + { + name: 'reason', + label: __('Optional reason for destroying this groupchat'), + placeholder: __('Reason'), + value: undefined + } + ] + try { + fields = await _converse.api.confirm(__('Confirm'), messages, fields) + const reason = fields.filter(f => f.name === 'reason').pop()?.value + const newjid = undefined + return model.sendDestroyIQ(reason, newjid).then(() => model.close()) + } catch (e) { + console.error(e) + } +} diff --git a/conversejs/lib/plugins/livechat-viewer-mode.ts b/conversejs/lib/plugins/livechat-viewer-mode.ts index b01fffab..4d889c88 100644 --- a/conversejs/lib/plugins/livechat-viewer-mode.ts +++ b/conversejs/lib/plugins/livechat-viewer-mode.ts @@ -17,11 +17,11 @@ export const livechatViewerModePlugin = { livechat_external_auth_reconnect_mode: undefined }) - const originalGetDefaultMUCNickname = _converse.getDefaultMUCNickname + const originalGetDefaultMUCNickname = _converse.exports.getDefaultMUCNickname if (!originalGetDefaultMUCNickname) { console.error('[livechatViewerModePlugin] getDefaultMUCNickname is not initialized.') } else { - Object.assign(_converse, { + Object.assign(_converse.exports, { getDefaultMUCNickname: function (this: any): any { if (!_converse.api.settings.get('livechat_enable_viewer_mode')) { return originalGetDefaultMUCNickname.apply(this, arguments) diff --git a/conversejs/loc.keys.js b/conversejs/loc.keys.js index 7a6f9283..ed3ebe95 100644 --- a/conversejs/loc.keys.js +++ b/conversejs/loc.keys.js @@ -49,7 +49,20 @@ const locKeys = [ 'poll_vote_instructions_xmpp', 'poll_is_over', 'poll_choice_invalid', - 'poll_anonymous_vote_ok' + 'poll_anonymous_vote_ok', + 'moderator_notes', + 'moderator_notes_create_error', + 'moderator_note_create', + 'moderator_note_description', + 'moderator_note_delete', + 'moderator_note_delete_confirm', + 'moderator_note_create_for_participant', + 'moderator_note_search_for_participant', + 'moderator_note_filters', + 'moderator_note_original_nick', + 'search_occupant_message', + 'message_search', + 'message_search_original_nick' ] module.exports = locKeys diff --git a/languages/ar.yml b/languages/ar.yml index e1a30bbc..4eef9af0 100644 --- a/languages/ar.yml +++ b/languages/ar.yml @@ -29,3 +29,67 @@ use_current_theme_color: استخدم ألوان الحلة الحالية connect_using_xmpp: للاتصال عبر XMPP important_note_title: ملاحظات هامة chat_title: الدردشة +moderator_note_delete: حذف الملاحظة +task_list_name: اسم قائمة المهام +tasks: المهام +avatar_set_option_cat: قطط +avatar_set_option_bird: عصافير +avatar_set_label: مجموعة الصور الرمزية +system_prosody_label: استخدم خدمة Prosody المثبتة على النظام +menu_configuration_label: غرف المحادثات +livechat_configuration_channel_command_label: سطر أوامر الروبوت +livechat_configuration_channel_command_cmd_label: سطر الأمر +moderator_note_description: الوصف +moderator_notes: ملاحظات الإشراف +moderator_note_filters: عوامل تصفية البحث +livechat_configuration_channel_forbidden_words_label_label: الملصقة +avatar_set_option_none: لا شيء +successfully_saved: تم الحفظ بنجاح +livechat_configuration_channel_slow_mode_label: الوضع البطيء +livechat_configuration_channel_forbidden_words_reason_label: السبب +livechat_configuration_channel_bot_options_title: خيارات روبوت الإشراف +livechat_configuration_channel_forbidden_words_comments_label: التعليقات +livechat_configuration_channel_command_message_label: رسالة +livechat_configuration_channel_bot_nickname: الاسم المستعار للروبوت +task_name: اسم المهمة +task_list_delete: حذف قائمة المهام +livechat_emojis_shortname: الإسم القصير +livechat_emojis_file: الملف +livechat_emojis_file_desc: "ملف الوجوه التعبيرية.\n" +share_chat_embed: الإدماج +action_remove_entry: احذف هذا الإدخال +poll: استطلاع رأي +message_search: البحث عن رسائل +search_occupant_message: البحث عن كل الرسائل +livechat_configuration_channel_title: خيارات القناة +moderator_note_search_for_participant: البحث عن ملاحظة +copied: تم نسخه +token_label: ملصقة +autocolors_label: استكشاف تلقائي للألوان +show_scrollbarr: إظهار شريط التمرير +livechat_configuration_channel_emojis_title: الوجوه التعبيرية الخاصة بالقناة +poll_choice_n: 'الخيار {{N}}:' +poll_end: 'ينتهي استطلاع الرأي :' +chat: المحادثة +livechat_configuration_channel_quote_label2: الرسائل +task_description: الوصف +action_import: استيراد +action_export: تصدير +token_date: التاريخ +poll_question: سؤال +prosody_firewall_file_enabled: مفعّل +prosody_firewall_name: الاسم +invalid_value: القيمة غير صالحة. +task_delete: حذف المهمة +poll_title: استطلاع رأي جديد +prosody_firewall_content: محتوى الملف +livechat_configuration_channel_enable_bot_label: تفعيل روبوت الإشراف +action_add_entry: إضافة إدخال +cancel: ألغِ +save: حِفْظ +external_auth_google_oidc_label: استخدم جوجل +external_auth_facebook_oidc_label: استخدم فايسبوك +chat_terms_label: شروط الاستخدام +list_rooms_description: "قائمة الغرف\n" +online_help: المساعدة على الويب diff --git a/languages/de.yml b/languages/de.yml index 3d209a22..f4a54595 100644 --- a/languages/de.yml +++ b/languages/de.yml @@ -121,7 +121,7 @@ converse_theme_description: "Bitte wählen Sie, welches ConverseJS Thema Sie nut möchten." converse_theme_option_peertube: "Peertube Thema" converse_theme_option_default: "Default ConverseJS Thema" -converse_theme_option_concord: "ConverseJS Concord Thema" +converse_theme_option_cyberpunk: "ConverseJS Cyberpunk Thema" autocolors_label: "Automatische Farberkennung" autocolors_description: | @@ -557,3 +557,47 @@ livechat_configuration_channel_moderation_delay_desc: "Standardwert der Moderati ganze Zahl: Nachrichten werden für Nicht-Moderator-Teilnehmer um X Sekunden verzögert, so dass Moderatoren die Nachricht löschen können, bevor ein anderer Benutzer sie lesen kann.\n\n" +avatar_set_option_none: Nichts +livechat_configuration_channel_anonymize_moderation_label: Moderationsaktionen anonymisieren +livechat_configuration_channel_anonymize_moderation_desc: "Moderationsaktionen anonymisieren + Standardwert für neue Räume.\nWenn dies aktiviert ist, werden Moderationsaktionen + anonymisiert, um zu vermeiden, dass bekannt wird, wer Teilnehmer bannt/verweist/....\n" +moderator_notes_create_error: Fehler beim Speichern der Notiz +moderator_note_description: Beschreibung +moderator_note_delete: Notiz löschen +moderator_note_create_for_participant: Eine neue Notiz erstellen +moderator_note_search_for_participant: Notizen suchen +moderator_note_filters: Suchfilter +moderator_note_original_nick: Nickname des Teilnehmers zum Zeitpunkt der Erstellung + der Notiz +moderator_note_create: Eine neue Notiz erstellen +moderator_note_delete_confirm: Sind Sie sicher, dass Sie diese Notiz löschen wollen? +moderator_notes: Moderationsnotizen +message_search: Suche nach Nachrichten +search_occupant_message: Alle Nachrichten suchen +message_search_original_nick: Nickname des Teilnehmers zum Zeitpunkt des Versendens + der Nachricht +prosody_firewall_label: Prosody mod_firewall einschalten +prosody_firewall_description: "Sie können mod_firewall auf Ihrem Prosody-Server aktivieren.\nWeitere + Informationen finden Sie unter in der Dokumentation.\n" +prosody_firewall_configure_button: "mod_firewall konfigurieren\n" +prosody_firewall_configuration: Prosody mod_firewall Konfiguration +prosody_firewall_configuration_help: "Hier können Sie das Modul Prosody mod_firewall konfigurieren.\n + Sie können mehrere Konfigurationsdateien unten erstellen und deren Reihenfolge ändern.\n + Zögern Sie nicht, Ihre Konfigurationen mit der Gemeinschaft zu teilen (z. B. indem + Sie einige Beispiele zu der Plugin-Dokumentation hinzufügen).\n" +prosody_firewall_disabled_warning: "Warnung: mod_firewall ist in den livechat Plugin-Einstellungen + deaktiviert, du musst es aktivieren, wenn du willst, dass diese Konfiguration + beachtet wird.\n" +prosody_firewall_file_enabled: Aktiviert +prosody_firewall_name: Name +prosody_firewall_name_desc: "Darf nur folgende Zeichen enthalten: alphanumerische + Zeichen, Unterstriche und Bindestriche.\nDie Skripte werden in alphabetischer Reihenfolge + geladen.\n" +prosody_firewall_content: Dateiinhalt +chat: Chat diff --git a/languages/en.yml b/languages/en.yml index 22b31a56..505cc906 100644 --- a/languages/en.yml +++ b/languages/en.yml @@ -1,3 +1,4 @@ +chat: Chat online_help: "Online help" open_chat: "Open chat" open_chat_new_window: "Open chat in a new window" @@ -182,12 +183,13 @@ avatar_set_option_fenec: "Fenecs (Mobilizon mascot)" avatar_set_option_abstract: Abstract avatar_set_option_legacy: "Legacy Sepia avatars (those included in previous plugin versions)" +avatar_set_option_none: None converse_theme_label: "ConverseJS theme" converse_theme_description: "Please choose the converseJS theme you want to use." converse_theme_option_peertube: "Peertube theme" converse_theme_option_default: "Default ConverseJS theme" -converse_theme_option_concord: "ConverseJS concord theme" +converse_theme_option_cyberpunk: "ConverseJS Cyberpunk theme" autocolors_label: "Automatic color detection" autocolors_description: | @@ -568,18 +570,23 @@ livechat_configuration_channel_terms_desc: | new_poll: Create a new poll poll: Poll poll_title: New poll -poll_instructions: Complete and submit this form to create a new poll. This will end and replace any existing poll. +poll_instructions: Complete and submit this form to create a new poll. This will end + and replace any existing poll. poll_question: Question poll_duration: Poll duration (in minutes) poll_anonymous_results: Anonymous results poll_choice_n: 'Choice {{N}}:' poll_end: 'Poll ends at:' -poll_vote_instructions: "To vote, click on your choice or send a message with an exclamation mark followed by your choice number (Example: !1)." -poll_vote_instructions_xmpp: "Send a message with an exclamation mark followed by your choice number to vote. Example: !1" +poll_vote_instructions: "To vote, click on your choice or send a message with an exclamation + mark followed by your choice number (Example: !1)." +poll_vote_instructions_xmpp: "Send a message with an exclamation mark followed by + your choice number to vote. Example: !1" poll_is_over: This poll is now over. poll_choice_invalid: This choice is not valid. -poll_anonymous_vote_ok: Your vote is taken into account. Votes are anonymous, they will not be shown to other participants. -poll_vote_ok: Your vote has been taking into account, the counters will be updated in a moment. +poll_anonymous_vote_ok: Your vote is taken into account. Votes are anonymous, they + will not be shown to other participants. +poll_vote_ok: Your vote has been taking into account, the counters will be updated + in a moment. moderation_delay: Moderation delay livechat_configuration_channel_moderation_delay_desc: | @@ -588,3 +595,45 @@ livechat_configuration_channel_moderation_delay_desc: | 0: moderation delay disabled Any positive integer: messages will be delayed for X seconds for non-moderator participants, allowing moderators to delete message before any user can read it. + +livechat_configuration_channel_anonymize_moderation_label: "Anonymize moderation actions" +livechat_configuration_channel_anonymize_moderation_desc: | + Anonymize moderation actions default value for new rooms. + When this is enabled, moderation actions will be anonymized, to avoid disclosing who is banning/kicking/… occupants. + +moderator_notes: Moderation notes +moderator_notes_create_error: 'Error when saving the note' +moderator_note_create: 'Create a new note' +moderator_note_description: 'Description' +moderator_note_delete: 'Delete note' +moderator_note_delete_confirm: 'Are you sure you want to delete this note?' +moderator_note_create_for_participant: 'Create a new note' +moderator_note_search_for_participant: 'Search notes' +moderator_note_filters: 'Search filters' +moderator_note_original_nick: 'Nickname of the participant at the time of the note + creation' + +search_occupant_message: 'Search all messages' +message_search: 'Message search' +message_search_original_nick: Nickname of the participant when the message was sent + +prosody_firewall_label: 'Enable Prosody mod_firewall' +prosody_firewall_description: | + You can enable mod_firewall on your Prosody server. + For more information, please check the documentation. +prosody_firewall_configure_button: | + Configure mod_firewall + +prosody_firewall_configuration: Prosody mod_firewall configuration +prosody_firewall_configuration_help: | + Here you can configure Prosody mod_firewall module. + You can create multiple configuration files bellow, and change their order. + Don't hesitate to share your configurations with the community (for example by adding some examples in the plugin documentation). +prosody_firewall_disabled_warning: | + Warning: mod_firewall is disabled in the livechat plugin settings, you have to enable it if you want this configuration to be taken into account. +prosody_firewall_file_enabled: Enabled +prosody_firewall_name: Name +prosody_firewall_name_desc: | + Can only contain: alphanumerical characters, underscores and hyphens. + Scripts will be loaded in alphabetical order. +prosody_firewall_content: File content diff --git a/languages/es.yml b/languages/es.yml index 225dc960..854dd306 100644 --- a/languages/es.yml +++ b/languages/es.yml @@ -103,7 +103,7 @@ no_anonymous_description: "Si se marca, los usuarios anónimos de Peertube no ve el chat.\nFuncionalidad en desarrollo.\nSi la activas, es muy recomendable marcar también \"No publicar información del chat\".\nDe lo contrario, algunas herramientas de terceros podrían intentar abrir el chat , generando comportamientos imprevistos.\n" -converse_theme_option_concord: Tema concord de ConverseJS +converse_theme_option_cyberpunk: Tema cyberpunk de ConverseJS help_builtin_prosody_description: "Este plugin utiliza el servidor XMPP Prosody para manejar salas de chat.\nEste plugin viene con una AppImage de Prosody, que se utilizará para ejecutar el servicio.\n" diff --git a/languages/fr.yml b/languages/fr.yml index fc444fe5..a6a4cba3 100644 --- a/languages/fr.yml +++ b/languages/fr.yml @@ -130,7 +130,7 @@ converse_theme_description: "Merci de choisir le thème ConverseJS que vous voul utiliser." converse_theme_option_peertube: "Thème Peertube" converse_theme_option_default: "Thème par défaut de ConverseJS" -converse_theme_option_concord: "Thème concord de ConverseJS" +converse_theme_option_cyberpunk: "Thème cyberpunk de ConverseJS" autocolors_label: "Détection automatique des couleurs" autocolors_description: | @@ -541,7 +541,8 @@ chat_terms_description: "Ces conditions d'utilisation seront affichées à tous utilisateur⋅rices lorsqu'iels rejoindront les salons de discussion.\nLes streameur⋅euses peuvent également configurer des conditions d'utilisation pour leurs canaux, qui seront affichées juste après les conditions de l'instance.\n" -livechat_configuration_channel_terms_label: Conditions d'utilisation tchat de la chaîne +livechat_configuration_channel_terms_label: Conditions d'utilisation du tchat de la + chaîne livechat_configuration_channel_terms_desc: "Vous pouvez configurer un message de \"\ conditions d'utilisation\" qui sera affiché aux utilisateur⋅rices qui rejoignent vos salons de discussion.\n" @@ -573,3 +574,49 @@ livechat_configuration_channel_moderation_delay_desc: "Valeur par défaut du dé non modérateur⋅rices, ce qui permet à ces derniers de supprimer les messages avant qu'un utilisateur⋅rice ne puisse le lire.\n\n" moderation_delay: Délai de modération +avatar_set_option_none: Aucun +livechat_configuration_channel_anonymize_moderation_label: Anonymiser les actions + de modération +livechat_configuration_channel_anonymize_moderation_desc: "Anonymiser les actions + de modération : valeur par défaut pour les nouveaux salons.\nLorsque cette option + est activée, les actions de modération sont rendues anonymes, afin d'éviter de révéler + qui bannit/expulse/... les occupants.\n" +moderator_notes_create_error: Erreur lors de l'enregistrement de la note +moderator_note_description: Description +moderator_note_delete: Supprimer la note +moderator_note_delete_confirm: Êtes-vous sûr de vouloir supprimer cette note ? +moderator_note_search_for_participant: Recherche de notes +moderator_note_filters: Filtres de recherche +moderator_note_original_nick: Pseudonyme du participant⋅e au moment de la création + de la note +moderator_note_create: Créer une nouvelle note +moderator_note_create_for_participant: Créer une nouvelle note +moderator_notes: Notes de modération +message_search: Recherche de messages +search_occupant_message: Rechercher tous les messages +message_search_original_nick: Pseudonyme du participant⋅e au moment de l'envoi du + message +prosody_firewall_configure_button: "Configurer mod_firewall\n" +prosody_firewall_configuration: Configuration de mod_firewall pour Prosody +prosody_firewall_label: Activer mod_firewall pour Prosody +prosody_firewall_description: "Vous pouvez activer mod_firewall sur votre serveur Prosody.\nPour plus d'informations, + veuillez consulter la documentation.\n" +prosody_firewall_configuration_help: "Ici vous pouvez configurer le module Prosody + mod_firewall.\n + Vous pouvez créer plusieurs fichiers de configuration ci-dessous, et changer leur + ordre.\nN'hésitez pas à partager vos configurations avec la communauté (par exemple + en ajoutant des exemples dans la documentation du plugin).\n" +prosody_firewall_disabled_warning: "Attention : mod_firewall est désactivé dans les + paramètres + du plugin livechat , vous devez l'activer si vous voulez que cette configuration + soit prise en compte.\n" +prosody_firewall_file_enabled: Activé +prosody_firewall_name: Nom +prosody_firewall_name_desc: "Ne peut contenir que des caractères alphanumériques, + des traits de soulignement et des traits d'union.\nLes scripts seront chargés par + ordre alphabétique.\n" +prosody_firewall_content: Contenu du fichier +chat: Tchat diff --git a/languages/gl.yml b/languages/gl.yml index 85f14db7..a0cc8949 100644 --- a/languages/gl.yml +++ b/languages/gl.yml @@ -1,31 +1,30 @@ -open_chat_new_window: Abrir chat en nova ventá -open_chat: Abrir chat -share_chat_link: Compartir ligazón ao chat +open_chat_new_window: Abrir charla en nova xanela +open_chat: Abrir charla +share_chat_link: Compartir ligazón á charla transparent_background: Fondo transparente (para integración coa retransmisión, por exemplo con OBS) -use_chat_help: Se o activas, haberá un chat xunto ao vídeo. +use_chat_help: Se o activas, haberá unha charla xunto ao vídeo. read_only: Só lectura show_scrollbarr: Mostrar barra desprazameto copy: Copiar important_note_title: Notas importantes -important_note_text: "Aquí tes a documentación acerca do complemento:\n\n\ - \ Documentación sobre Peertube Plugin Livechat\n.\n" -chat_title: Chat +important_note_text: "Aquí tes a documentación sobre o complemento:\n\n Documentación sobre Peertube Plugin Livechat\n.\n" +chat_title: Charla connect_using_xmpp_help: Podes conectarte á sala usando unha conta XMPP externa, e o teu cliente XMPP favorito. diagnostic: "Antes de pedir axuda, por favor usa a ferramenta de diagnóstico:\nIniciar o diagnóstico\n - (se o botón non abre outra ventá, intenta actualizar a páxina).\n" + (se o botón non abre outra xanela, intenta actualizar a páxina).\n" online_help: Axuda en liña -close_chat: Pechar chat -use_chat: Usar chat +close_chat: Fechar charla +use_chat: Usar charla link_copied: Ligazón copiada error: Erro open: Abrir use_current_theme_color: Usar esquema de cores actual -generate_iframe: Crear un iframe para incrustar o chat nun sitio web -chat_for_live_stream: 'Chat para a emisión en directo:' +generate_iframe: Crear un marco para incrustar a charla nun sitio web +chat_for_live_stream: 'Charla para a emisión en directo:' room_name: Nome da sala room_description: Descrición da sala not_found: Non se atopa diff --git a/languages/hr.yml b/languages/hr.yml index acae6a4d..fb96a32b 100644 --- a/languages/hr.yml +++ b/languages/hr.yml @@ -209,8 +209,8 @@ share_url_description: Postojat će gumb za dijeljenje URL-a chata (može se na koristiti za OBS integraciju). per_live_video_description: Ako je označeno, sva videa uživo imat će potvrdni okvir u svojim svojstvima za aktiviranje web chata. -per_live_video_label: Korisnici mogu aktivirati chat za svoja videa uživo -all_lives_label: Aktiviraj chat za sva videa uživo +per_live_video_label: Korisnici mogu aktivirati chat za svoje prijenose uživo +all_lives_label: Aktiviraj chat za sve prijenose uživo no_anonymous_label: Sakrij chat za anonimne korisnike theming_advanced_description: Izgled chat_style_description: "Dodatni stilovi koji se dodaju atributu stila iframea. \n @@ -234,15 +234,15 @@ external_auth_custom_oidc_description: "Možeš konfigurirati eksternog pružate external_auth_description: "Eksterna autentifikacija\nZa korisnike koji nemaju Peertube račun možeš aktivirati različite načine autentifikacije pomoću externih pružatelja usluge autentifikacije.\n" -all_lives_description: Ako je označeno, chat će se aktivirati za sva videa uživo. -all_non_lives_description: Ako je označeno, chat će se aktivirati za sva videa koja - nisu uživo. -all_non_lives_label: Aktiviraj chat za sva videa koja nisu uživo -converse_theme_option_concord: ConverseJS concord tema +all_lives_description: Ako je označeno, chat će se aktivirati za sve prijenose uživo. +all_non_lives_description: Ako je označeno, chat će se aktivirati za sve prijenose + koji nisu uživo. +all_non_lives_label: Aktiviraj chat za sve prijenose koji nisu uživo +converse_theme_option_cyberpunk: ConverseJS cyberpunk tema chat_style_label: Atribut stila ifreama za chat prosody_muc_log_by_default_description: "Ako je označeno, sadržaj sobe će se standardno spremati.\nSvaki korisnik koji se pridruži sobi vidjet će što je napisano prije - nego što se pridruži.\nArhiviranje sadržaja se uvijek moguće aktivirati/deaktivirati\n + nego što se pridruži.\nArhiviranje sadržaja se uvijek može aktivirati/deaktivirati\n za određenu sobu, uređivanjem njezinih svojstava.\n" auto_ban_anonymous_ip_label: Blokiraj IP anonimnog korisnika kada je korisniku zabranjen pristup chat sobi @@ -275,3 +275,72 @@ share_chat_embed: Ugradi token_label: Oznaka auth_description: "Autentifikacija\n" chat_terms_label: Uvjeti i odredbe +muted_anonymous_message: Samo registrirani korisnici mogu slati poruke. +livechat_configuration_channel_mute_anonymous_label: Isključi zvuk anonimnih korisnika +login_remote_peertube_video_not_found_try_anyway: U nekim slučajevima, video se ipak + može dohvatiti ako se povežeš s eksternom instancom. +livechat_configuration_channel_moderation_delay_desc: "Standardna vrijednost odgode + moderiranja:\n\n 0: odgoda moderiranja je deaktivirana\n Bilo + koji pozitivni cijeli broj: poruke će se odgoditi za X sekundi za sudionike koji + nisu moderatori, dopuštajući moderatorima da izbrišu poruku prije nego što je bilo + koji korisnik može pročitati.\n\n" +moderation_delay: Odgoda moderiranja +livechat_configuration_channel_forbidden_words_label_desc: Oznaka za ovo pravilo zabranjenih + riječi +livechat_configuration_channel_emojis_desc: "Možeš konfigurirati prilagođene emojije + za svoj kanal.\nOvi emojiji će biti dostupni u alatu za biranje emojija.\nKorisnici + ih također mogu koristiti sa svojim kratkim imenom (na primjer tako da napišu \"\ + :shortname:\").\n" +livechat_emojis_shortname_desc: "Emotikone možeš koristiti u chatu koristeći \":shortname:\"\ + .\nKratko ime može započeti i/ili završiti dvotočkom (:) te koristiti samo alfanumeričke + znakove, podvlake i crtice.\nPreporuča se započeti ih dvotočkom, kako bi korisnici + mogli koristiti automatsko dovršavanje (upisivanjem \":\" i pritiskom tipke tabulatora).\n" +validation_error: Došlo je do greške tijekom provjere valjanosti. +help_builtin_prosody_description: "Ovaj dodatak koristi Prosody XMPP server za upravljanje + sobama za razgovor.\nOvaj dodatak dolazi s Prosody AppImage koji će se koristiti + za pokretanje usluge.\n" +livechat_configuration_channel_terms_desc: "Možeš konfigurirati poruku „uvjeti i odredbe” + koja će se prikazati korisnicima koji se pridružuju tvojim chat sobama.\n" +poll_vote_instructions_xmpp: 'Pošalji poruku s uskličnikom i brojem izbora za glasanje. + Primjer: !1' +invalid_value_file_too_big: 'Datoteka je prevelika (maksimalna veličina: %s).' +per_live_video_warning_description: "\n Postavka „Korisnici mogu aktivirati chat za svoje prijenose uživo” je aktivirana.\n\ + \ Postavka je redundanta s postavkom „Aktiviraj chat za sve prijenose uživo”.\n\ + \n" +new_poll: Stvori novu anketu +invalid_value_missing: Ova je vrijednost obavezna. +invalid_value_wrong_type: Neispravna vrsta vrijednost. +invalid_value_wrong_format: Neispravan format vrijednosti. +invalid_value_not_in_range: Vrijednost ne odgovara dopuštenom rasponu. +invalid_value_duplicate: Dupla vrijednost +too_many_entries: Previše unosa +promote: Postani moderator +livechat_emojis_shortname: Kratko ime +action_import_emojis_info: Ako su uvezeni podaci u redu, ne zaboravi spremiti obrazac. +share_chat_peertube_tips: Ova će poveznica otvoriti chat unutar Peertube sučelja. +token_password: Token lozinke +token_date: Datum +token_action_create: Stvori novi token +token_default_label: Token generiran iz web sučelja +token_action_revoke: Opozovi token +token_action_revoke_confirm: Stvarno želiš opozvati ovaj token? +livechat_token_disabled_label: Deaktiviraj tokene za chat uživo +invalid_value_too_long: Vrijednost je predugačka +livechat_configuration_channel_terms_label: Uvjeti i odredbe kanala chata +poll: Anketa +poll_title: Nova anketa +poll_instructions: Ispuni i pošalji ovaj obrazac za izradu nove ankete. Ovo će zamijeniti + sve postojeće ankete. +poll_question: Pitanje +poll_duration: Trajanje ankete (u minutama) +poll_anonymous_results: Anonimni rezultati +poll_choice_n: 'Izbor {{N}}:' +poll_end: 'Anketa završava:' +poll_vote_instructions: 'Za glasanje klikni na svoj izbor ili pošalji poruku s uskličnikom + iza kojeg slijedi broj tvog izbora (Primjer: !1).' +poll_is_over: Ova anketa je sada završena. +poll_choice_invalid: Ovaj izbor nije valjan. +poll_anonymous_vote_ok: Tvoje se glasanje uzima u obzir. Glasanja su anonimna, neće + se prikazati drugim sudionicima. +poll_vote_ok: Tvoje se glasanje uzima u obzir. Brojači će se uskoro aktualizirati. diff --git a/languages/it.yml b/languages/it.yml index 0f386275..dc416b98 100644 --- a/languages/it.yml +++ b/languages/it.yml @@ -120,7 +120,7 @@ converse_theme_label: "Tema ConverseJS" converse_theme_description: "Scegli il tema converseJS che desideri utilizzare." converse_theme_option_peertube: "Tema Peertube" converse_theme_option_default: "Tema predefinito di ConverseJS" -converse_theme_option_concord: "Tema “Concord“ di ConverseJS" +converse_theme_option_cyberpunk: "Tema “cyberpunk“ di ConverseJS" autocolors_label: "Rilevamento automatico dei colori" autocolors_description: | diff --git a/languages/ja.yml b/languages/ja.yml index 8a796c03..ce141fbd 100644 --- a/languages/ja.yml +++ b/languages/ja.yml @@ -7,7 +7,7 @@ share_chat_link: チャットリンクを共有 read_only: 読み取り専用にする show_scrollbarr: スクロールバーを表示する transparent_background: 透過型背景にする (OBSなどを使用したストリーミング連携に最適) -tips_for_streamers: "ストリーマーの方へ: OBSにチャットを追加する場合は、読み取り専用のチャットリンクを生成し、ブラウザソースで読み込んでください。" +tips_for_streamers: "ストリーマーの方へ: OBSなどを使用して動画ストリームにチャットを埋め込む場合は、読み取り専用のチャットリンクを生成し、ブラウザソースで読み込んでください。\n" copy: コピー link_copied: リンクをコピーしました error: エラー @@ -70,7 +70,7 @@ converse_theme_label: ConverseJSのテーマ converse_theme_description: 使用したいconverseJSのテーマを選択してください。 converse_theme_option_peertube: PeerTubeのテーマ converse_theme_option_default: デフォルトのConverseJSテーマ -converse_theme_option_concord: ConverseJS concordテーマ +converse_theme_option_cyberpunk: ConverseJS cyberpunkテーマ autocolors_label: 色を自動検出 chat_style_label: チャットのiframeスタイル属性 chat_style_description: "追加のスタイル設定をiframeスタイルの属性に追加します。\n例)height:400px;\n" @@ -230,3 +230,150 @@ external_auth_custom_oidc_description: "チャットへのログインに、外 \ target=\"_blank\">設定ページ\n" external_auth_custom_oidc_button_label_description: このラベルは、OIDCプロバイダーを使用して認証するボタン名としてユーザーに表示されます。 copied: コピーしました +chat_terms_label: 利用規約 +tasks: タスク +task_list_create: '新規タスクリストを作成:' +task_list_name: タスクリスト名 +task_list_create_error: タスクリストを保存中にエラーが発生しました +task_delete_confirm: このタスクを削除してもよろしいですか? +task_delete: タスクを削除 +external_auth_oidc_redirect_uris_info_description: "Callback/Redirect URI:\n\ + 外部アプリケーション上に認証されたリダイレクションURIを設定したい場合は、このURLを追加してください:\n" +external_auth_google_oidc_label: Googleを使用 +login_using_external_account: 外部アカウントを使用してログイン +login_remote_peertube: 'ほかのPeerTubeインスタンスのアカウントを使用してログイン:' +login_remote_peertube_url: あなたのPeerTubeインスタンスURL +login_remote_peertube_url_invalid: 無効なPeerTube URLです。 +login_remote_peertube_no_livechat: このPeerTubeインスタンスには、ライブチャットプラグインがインストールされていません。 +login_remote_peertube_video_not_found_try_anyway_button: PeerTubeインスタンスで動画を開く +livechat_configuration_channel_anonymize_moderation_label: モデレーション操作を匿名か +moderator_notes_create_error: ノートを保存中にエラーが発生しました +moderator_note_create: 新規ノートを作成 +moderator_note_description: 概要 +moderator_note_delete: ノートを削除 +moderator_note_delete_confirm: このノートを削除してもよろしいですか? +moderator_note_create_for_participant: 新規ノートを作成 +moderator_note_filters: フィルタ検索 +moderator_note_original_nick: ノート作成時の参加者のニックネーム +prosody_c2s_interfaces_label: クライアントとサーバー間のネットワークインタフェース +login_remote_peertube_video_not_found: この動画はこのPeerTubeインスタンス上では利用できません。 +avatar_set_option_none: なし +validation_error: 検証中にエラーが発生しました。 +invalid_value_missing: この項目は必須です。 +invalid_value_wrong_type: 誤った形式の値です。 +too_many_entries: エントリーが多すぎます +chatroom_not_accessible: このチャットルームは存在しないか、アクセス権限がありません。 +login_remote_peertube_searching: PeerTubeインスタンス上の動画を検索中... +login_remote_peertube_video_open_failed: 'お使いのブラウザーはリモートインスタンスを開くことがブロックされています。このリンクを手動で開いてください:' +task_list_delete: タスクリストを削除 +task_list_delete_confirm: このタスクリストを削除してもよろしいですか? +task_create: 新規タスクを作成 +task_name: タスク名 +task_list_pick_title: タスクリストを選択してください +livechat_emojis_shortname: 短縮名 +livechat_emojis_shortname_desc: "チャット上で、絵文字を\":shortname:\"で使用できます。\n短縮名は、先頭と末尾にコロン(:)があり、英数字、アンダーバー、ハイフンのみが含まれている必要があります。\n\ + 自動補完(\":\"を入力後、Tabキーを押下)を利用できるようコロンから入力することを強くお勧めします。\n" +livechat_emojis_file: ファイル +promote: モデレーターにする +livechat_emojis_file_desc: "絵文字に使用するファイルです。\n" +action_import: インポート +action_export: エクスポート +action_import_emojis_info: インポートしたデータが問題ない場合、フォームを保存することを忘れないでください。 +action_remove_entry: このエントリーを削除 +action_remove_entry_confirm: このエントリーを削除してもよろしいですか? +loading_error: データの読み込み中にエラーが発生しました。 +share_chat_embed: 埋め込み +share_chat_peertube_tips: このリンクで、PeerTubeインスタンス内でチャットが開きます。 +token_password: パスワードトークン +token_date: 日付 +token_action_create: 新規トークンを作成 +token_action_revoke: トークンを無効化 +token_default_label: Web UIから生成されたトークン +livechat_token_disabled_description: "ユーザーはチャットに接続するための長期的なトークンを生成できます。\nこれらのトークンは、OBSのカスタムブラウザドックなどで使用できます。\n + 詳細は、ドキュメントをご確認ください。\nこの設定にチェックをつけることで、この機能を無効にできます。\n" +muted_anonymous_message: 登録済みユーザーのみがメッセージを送信できます。 +livechat_configuration_channel_mute_anonymous_label: 匿名ユーザーをミュート +livechat_configuration_channel_mute_anonymous_desc: "新規チャットルームのデフォルト値です。\n既存のチャットルームは、ルーム設定フォーム内の機能で変更できます。\n\ + この設定が有効の場合、匿名ユーザーはチャットは閲覧のみ可能で、メッセージの送信はできません。\n" +livechat_configuration_channel_terms_label: チャンネルのチャットルームの利用規約 +livechat_configuration_channel_terms_desc: "ユーザーがチャットルームに参加する際に表示される\"利用規約\"メッセージを設定します。\n" +new_poll: 新規投票を作成 +poll: 投票 +poll_title: 新規投票 +poll_instructions: 投票を作成するには、内容を設定して送信してください。既存の投票は終了し、この投票で置き換えられます。 +poll_question: 質問 +poll_duration: 投票期間 (分) +poll_anonymous_results: 匿名の結果 +poll_choice_n: '選択肢 {{N}}:' +poll_end: '投票は次で終了:' +poll_vote_instructions: '投票するには、選択肢をクリックするか、感嘆符(!)を付与した後に選択肢番号を入力してメッセージを送信してください(例: + !1)。' +poll_vote_instructions_xmpp: '投票するには、感嘆符(!)を付与した後に選択肢番号を入力してメッセージを送信してください。例: !1' +poll_is_over: この投票は終了しました。 +poll_vote_ok: あなたの投票は受け付けられ、まもなくカウンターが更新されます。 +moderation_delay: モデレーションの遅延時間 +livechat_configuration_channel_moderation_delay_desc: "モデレーションの遅延時間のデフォルト値:\n\n\ + \ 0: モデレーション遅延無効\n 任意の整数値n: モデレーター以外の参加者からのメッセージはn秒後に表示されるようになり、モデレーターはメッセージが他の参加者に読まれる前に削除することができます。\n\ + \n" +livechat_configuration_channel_forbidden_words_label_label: ラベル +livechat_configuration_channel_forbidden_words_label_desc: この禁止語規則のラベル +login_remote_peertube_video_not_found_try_anyway: 場合によっては、外部インスタンスに接続できれば動画を取得できることがあります。 +login_external_auth_alert_message: 認証に失敗しました +invalid_value_not_in_range: 値が許可された範囲にありません。 +search_occupant_message: 全てのメッセージを検索 +message_search: メッセージ検索 +chat_terms_description: "この利用規約はチャットルームに参加する際に全てのユーザーに表示されます。\n配信者はこの利用規約に加えて、チャンネル独自の利用規約を設定できます。チャンネル独自の利用規約は全てのユーザー向けの利用規約の右側に表示されます。\n" +invalid_value_too_long: 値が大きすぎます +moderator_notes: モデレーションノート +moderator_note_search_for_participant: ノートを検索 +message_search_original_nick: メッセージ送信時の参加者のニックネーム +prosody_firewall_label: Prosody mod_firewallを有効にする +prosody_firewall_configure_button: "mod_firewallを設定\n" +prosody_firewall_file_enabled: 有効 +prosody_firewall_name: 名前 +prosody_firewall_name_desc: "次の値のみ利用可能: 英数字、アンダーバー、ハイフン。\nスクリプトは、英字順で読み込まれます。\n" +prosody_firewall_content: ファイルコンテンツ +token_label: ラベル +task_description: 概要 +livechat_configuration_channel_emojis_title: チャンネルの絵文字 +invalid_value_file_too_big: 'ファイルサイズが大きすぎます(最大ファイルサイズ: %s)。' +action_add_entry: エントリーを追加 +share_chat_dock: ドック +livechat_token_disabled_label: ライブチャットトークンを無効化 +token_action_revoke_confirm: 子のトークンを無効化してよろしいですか? +auth_description: "認証\n" +poll_choice_invalid: この選択肢は無効です。 +poll_anonymous_vote_ok: あなたの投票を受け付けました。投票は匿名で行われ、他の参加者には表示されません。 +task_list_pick_message: "タスクリストを選択すると、新しいタスクが作成されます。\nタスクを確認するには、トップメニューからタスクアプリケーションを開いてください。\n\ + 詳細については、ライブチャットプラグインのドキュメントを参照してください。\n" +share_chat_dock_tips: "チャットページを新規ページかつPeerTubeアカウントにログインした状態で開くリンクを生成できます。\n例えばこのリンクをOBSのカスタムブラウザドックに設定し、OBS上から直接チャットを読んだり、反応することができるようになります。\n\ + このリンクは、あなた自身として接続するため、外部には絶対に公開しないでください。\n既に生成した認証トークンのリストを以下から見つけてください。\n新しいトークンを生成したり、既に生成済みのトークンを無効化できます。\n\ + これらのトークンには有効期限はありません。\n" +livechat_configuration_channel_anonymize_moderation_desc: "新しいルームに対するモデレーションアクションの匿名化をデフォルトにします。 + \nこれを有効にすると、誰がユーザーを追放、キックなどのモデレーションアクションを実施したのかを匿名化します。\n" +prosody_firewall_description: "お使いのProsodyサーバーのmod_firewallを有効にできます。\n詳しくは、ドキュメントをご確認ください。\n" +prosody_firewall_configuration: Prosody mod_firewallの設定 +prosody_firewall_configuration_help: "ここでProsody mod_firewallモジュールの設定が可能です。\n複数の設定ファイルを作成し、以下から順番を変更できます。\n\ + 最適な設定が出来たら、コミュニティメンバーと設定を共有してみてください(プラグインドキュメントに掲載させていただくこともあります)。\n" +prosody_firewall_disabled_warning: "警告: mod_firewallは、ライブチャットのプラグイン設定で無効になっており、この設定を反映するには、機能を有効にする必要があります。\n" +task_list_pick_empty: タスクリストが存在しません。新規作成してください +external_auth_facebook_oidc_label: Facebookを使用 +external_auth_facebook_oidc_description: "有効にすると\"Facebookでログイン\"ボタンを追加します。\nFacebook + OAuthアプリケーションの設定が必要です。\n" +external_auth_google_oidc_description: "有効にすると\"Googleでログイン\"ボタンを追加します。\nGoogle OAuthアプリケーションの設定が必要です。\n" +prosody_c2s_interfaces_description: "クライアントとサーバー間接続のためにリッスンするネットワークインターフェイスです。\n\ + この設定は、より高度な設定が可能なユーザー向けに提供されています。設定内容を完全に理解できていない場合は変更しないでください。\nリッスンするIPのリストをコンマ区切り(スペースは削除されます)で指定してください。\n\ + «*»を指定することで全てのIPv4アドレス、«::»を指定することで全てのIPv6アドレスをリッスンします。\n例:\n\n *, + ::\n *\n 127.0.0.1, ::1\n 127.0.0.1, ::1, 172.18.0.42\n\ + \n" +invalid_value_wrong_format: 誤ったフォーマットの値です。 +invalid_value_duplicate: 重複した値です +livechat_configuration_channel_emojis_desc: "チャンネルのカスタム絵文字を設定できます。\nこれらの絵文字は、絵文字ピッカーから利用できます。\n\ + 絵文字は、短縮名でも使用できます。(例えば\":shortname:\")\n" +chat: チャット diff --git a/languages/sq.yml b/languages/sq.yml index e69de29b..acd56b38 100644 --- a/languages/sq.yml +++ b/languages/sq.yml @@ -0,0 +1,248 @@ +muted_anonymous_message: Mesazhe mund të dërgojnë vetëm përdorues të regjistruar. +moderation_delay: Vonesë moderimi +share_url_label: Shfaq butonin “Ndani me të tjerët lidhje fjalosjeje” +no_anonymous_label: Fshihe fjalosjen për përdorues anonimë +theming_advanced_description: Aplikim temash +login_remote_peertube_url_invalid: URL PeerTube e pavlefshme. +tasks: Punë +task_list_create: 'Krijoni një listë të re punësh:' +task_list_create_error: Gabim teksa ruhej lista e punëve +task_list_delete_confirm: Jeni i sigurt se doni të fshihet kjo listë punësh? +avatar_set_option_abstract: Abstrakte +prosody_s2s_interfaces_label: Ndërfaqe rrjeti shërbyes te shërbyes +per_live_video_label: Përdoruesit mund të aktivizojnë fjalosje për transmetimet e + tyre të drejtpërdrejta +successfully_saved: U ruajtën me sukses +livechat_configuration_channel_quote_delay_label: Dërgoje çdo X minuta +livechat_configuration_title: Formësoni dhoma fjalosjeje të transmetimit tuaj të drejtpërdrejtë +avatar_set_label: Grup avatarësh +prosody_s2s_port_label: Portë Prosody shërbyes te shërbyes +prosody_c2s_label: Aktivizo lidhje klient te shërbyes +livechat_configuration_channel_bot_options_title: Mundësi roboti moderimi +livechat_configuration_channel_forbidden_words_label: Fjalë, ose shprehje të ndaluara +livechat_configuration_channel_forbidden_words_regexp_label: Konsideroji shprehje + të rregullta +livechat_configuration_channel_command_label: Urdhër roboti +prosody_c2s_interfaces_label: Ndërfaqe rrjeti klient te shërbyes +livechat_configuration_channel_command_message_label: Mesazh +task_list_name: Emër liste punësh +promote: Bëhuni moderator +open_chat: Hapni fjalosje +open_chat_new_window: Hap fjalosjen në dritare të re +read_only: Vetëm për lexim +show_scrollbarr: Shfaq rrëshqitës +close_chat: Mbylle fjalosjen +use_chat: Përdorni fjalosjen +use_chat_help: Në u aktivizoftë, në krah të videos do të ketë një fjalosje. +share_chat_link: Ndajeni lidhjen e fjalosjes me të tjerët +copy: Kopjoje +copied: U kopjua +use_current_theme_color: Përdor ngjyra të temës së tanishme +room_name: Emër dhome +room_description: Përshkrim dhome +not_found: S’u gjet +video: Video +important_note_title: Shënim të rëndësishme +chat_terms_label: Terma & Kushte +channel: Kanal +last_activity: Veprimtaria e fundit +connect_using_xmpp: Lidhu duke përdorur XMPP +connect_using_xmpp_help: Mund të lidheni me dhomën duke përdorur një llogari të jashtme + XMPP dhe klientin tuaj të parapëlqyer XMPP. +list_rooms_label: Paraqit dhoma ekzistuese +list_rooms_description: "Paraqit dhomat\n" +external_auth_description: "Mirëfilltësim së jashtmi\nPër përdorues që s’kanë + llogari Peertube, mund të aktivizoni mënyra të ndryshme mirëfilltësimi, bazuar në + shërbyes të largët mirëfilltësimesh.\n" +external_auth_custom_oidc_button_label_label: Etiketë për butonin e lidhjes +external_auth_oidc_client_id_label: ID Klienti +external_auth_oidc_client_secret_label: E fshehtë klienti +external_auth_facebook_oidc_label: Përdor Facebook +open_blank_description: Do të ketë një buton për hapje dritareje fjalosjeje në një + dritare të re. +room_type_option_video: Çdo video ka dhomën e vet të fjalosjes +share_url_option_nobody: Mos e shfaq për ndonjë +room_type_option_channel: Dhomat e fjalosjes grupohen sipas kanaleve +share_url_option_everyone: Shfaqe për këdo +videos_list_label: Aktivizo fjalosje për këto video +avatar_set_option_sepia: Sepia (simboli i PeerTube-it) +converse_theme_label: Temë ConverseJS +converse_theme_option_peertube: Temë PeerTube +converse_theme_option_default: Tema Parazgjedhje ConverseJS +system_prosody_label: Përdor Prosody sistemi +disable_websocket_label: Çaktivizo Websocket-in +prosody_peertube_uri_label: URL Peertube për thirrje API +cancel: Anuloje +livechat_configuration_channel_slow_mode_label: Mënyra ngadalë +livechat_configuration_channel_enable_bot_label: Aktivizo robot moderimi +prosody_muc_expiration_description: "Këtu mund të zgjidhni se për sa kohë mbahet lënda + e dhomës së fjalosjes nga shërbyesi. Vlera mund të jetë:\n\n 60: + lënda do të ruhet për 60 sekonda. 60-n mund ta zëvendësoni me çfarëdo vlere + numër të plotë.\n 1d: lënda do të ruhet për 1 ditë. 1-shin + mund ta zëvendësoni me çfarëdo vlere numër të plotë.\n 1w: lënda + do të ruhet për 1 javë. 1-shin mund ta zëvendësoni me çfarëdo vlere numër + të plotë.\n 1m: lënda do të ruhet për 1 muaj. 1-shin mund + ta zëvendësoni me çfarëdo vlere numër të plotë.\n 1y: lënda do + të ruhet për 1 vit. 1-shin mund ta zëvendësoni me çfarëdo vlere numër të + plotë.\n never: lënda nuk skadon kurrë dhe do të mbahet përgjithnjë.\n\ + \n" +livechat_configuration_channel_forbidden_words_reason_label: Arsye +livechat_configuration_channel_forbidden_words_comments_label: Komente +livechat_configuration_channel_quote_label2: Mesazhe +livechat_configuration_channel_command_message_desc: Mesazhi për t’u dërguar. +livechat_configuration_channel_banned_jids_label: Përdorues dhe rregullsi të dëbuara +validation_error: Pati një gabim gjatë vleftësimit. +invalid_value: Vlerë e pavlefshme. +invalid_value_missing: Kjo vlerë është e domosdoshme. +invalid_value_wrong_type: Vlera është e një lloji të gabuar. +invalid_value_wrong_format: Vlera është në format të gabuar. +invalid_value_not_in_range: Vlerë jo brenda intervalit të autorizuar. +invalid_value_duplicate: Vlerë e përsëdytur +invalid_value_too_long: Vlerë shumë e gjatë +too_many_entries: Shumë zëra +chatroom_not_accessible: Kjo dhomë fjalosjeje s’ekziston, ose s’lejoheni të hyni në + të. +login_using_external_account: Bëni hyrjen duke përdorur një llogari të jashtme +login_remote_peertube_url: URL e instancës tuaj Peertube +login_remote_peertube_video_not_found: Kjo video s’është në këtë instancë Peertube. +login_external_auth_alert_message: Mirëfilltësimi dështoi +task_list_delete: Fshi listë punësh +task_create: Krijoni një punë të re +task_name: Emër pune +task_description: Përshkrim +task_delete_confirm: Jeni i sigurt se doni të fshihet kjo punë? +task_list_pick_title: Ju lutemi, zgjidhni një listë punësh +livechat_configuration_channel_emojis_title: Emoxhi kanali +livechat_emojis_shortname: Emër i shkurtër +livechat_emojis_file_desc: "Kartela e emoji-t.\n" +action_export: Eksportoje +action_remove_entry_confirm: Jeni i sigurt se doni të hiqet ky zë? +loading_error: Ndodhi gabim teksa ngarkoheshin të dhëna. +token_password: Token fjalëkalimi +token_date: Datë +token_action_revoke: Hiqe token-in +token_default_label: Token i prodhuar nga ndërfaqja web +token_action_create: Krijoni një token të ri +token_action_revoke_confirm: Jeni i sigurt se doni të hiqet ky token? +livechat_configuration_channel_mute_anonymous_label: Heshto përdorues anonimë +livechat_configuration_channel_terms_label: Terma & kushte kanali fjalosjeje +new_poll: Krijoni një pyetësor të ri +poll_title: Pyetësor i ri +poll_question: Pyetje +poll_duration: Kohëzgjatje pyetësori (në minuta) +poll_anonymous_results: Përfundime anonime +poll_is_over: Ky pyetësor tanimë ka përfunduar. +poll_choice_invalid: Kjo zgjedhje s’është e vlefshme. +chat_behaviour_description: Sjellje fjalosjeje +room_type_label: Lloj dhome +auto_display_description: Kur shihet një video, dritarja e fjalosjes do të hapet vetvetiu. +open_blank_label: Shfaq butonin “hape në dritare të re” +all_lives_label: Aktivizo fjalosje për krejt të drejtpërdrejtat +converse_theme_description: Ju lutemi, zgjidhni temën converseJS që doni të përdoret. +prosody_port_label: Portë Prosody +prosody_muc_log_by_default_label: Regjistro ç’bëhet në dhomë, si parazgjedhje +prosody_room_allow_s2s_label: Aktivizoni lidhje te dhoma duke përdorur llogari të + jashtme XMPP +prosody_certificates_dir_label: Dosje dëshmish +livechat_configuration_channel_forbidden_words_label_label: Etiketë +task_delete: Fshije punën +avatar_set_option_bird: Zogj +autocolors_label: Pikasje e vetvetishme ngjyrash +help_builtin_prosody_label: Shërbyes Prosody +prosody_c2s_port_label: Klient Prosody te portë shërbyesi +prosody_components_list_label: Përbërës të jashtëm +prosody_components_port_label: Portë përbërësish Prosody të jashtëm +livechat_configuration_channel_command_cmd_label: Urdhër +livechat_configuration_channel_title: Mundësi kanali +prosody_muc_expiration_label: Skadim regjistrash dhome +livechat_configuration_channel_quote_label: Kohëmatës +livechat_configuration_channel_bot_nickname: Nofkë roboti +federation_no_remote_chat_label: Mos shfaq fjalosje nga larg +federation_no_remote_chat_description: "Duke i vënë shenjë këtij rregullimi, instanca + juaj s’do të shfaqë kurrë fjalosje prej videosh të largëta.\n" +federation_dont_publish_remotely_label: Mos boto hollësifjalosjeje +auto_display_label: Hape vetvetiu fjalosjen +livechat_emojis_file: Kartelë +action_import: Importoje +share_url_option_owner: Shfaq të zotin e videos +avatar_set_option_cat: Mace +share_chat_embed: Trupëzojeni +share_chat_peertube_tips: Kjo lidhje do ta hapë fjalosjen brenda ndërfaqes së Peertube-it. +auth_description: "Mirëfilltësim\n" +external_auth_google_oidc_label: Përdor Google +save: Ruaje +chat_title: Fjalosje +action_add_entry: Shtoni një zë +action_remove_entry: Hiqe këtë zë +token_label: Etiketë +poll: Pyetësor +poll_choice_n: 'Zgjedhje {{N}}:' +poll_end: 'Pyetësori përfundon më:' +link_copied: Lidhja u kopjua +error: Gabim +open: Hape +web: Web +online_help: Ndihmë në Internet +federation_description: "Federim\nRregulliemt vijuese kanë të bëjnë me federimin + me instanca të tjera\nPeertube dhe programe të tjera fediversi.\n" +invalid_value_file_too_big: 'Madhësia e kartelës është shumë e madhe (madhësi maksimum: + %s).' +all_non_lives_description: Në iu vëntë shenjë, fjalosja do të jetë e aktivizuar për + krejt videot që s’janë transmetimet e drejtpërdrejta. +external_auth_custom_oidc_title: OpenID Connect +external_auth_custom_oidc_label: Përdorni një shërbim OpenID Connect +external_auth_custom_oidc_button_label_description: Kjo etiketë do t’ju shfaqet përdoruesve, + si etiketa e butonit për kryerje mirëfilltësimi me këtë shërbim OIDC. +per_live_video_description: Në iu vëntë shenjë, krejt videot e transmetuara drejtpërdrejt + do të kenë një kutizë te vetitë e tyre, për aktivizim fjalosjeje. +livechat_configuration_channel_moderation_delay_desc: "Vlerë parazgjedhjeje vonese + moderimi:\n\n 0: vonesë moderimi e çaktivizuar\n Çfarëdo numër + i plotë pozitiv: mesazhet do të vonohen për X sekonda për pjesëmarrës që s’janë + moderatorë, duke u lejuar moderatorëve të fshijnë mesazhe para se të mund t’i lexojë + cilido përdorues.\n\n" +all_lives_description: Në iu vëntë shenjë, fjalosja do të jetë e aktivizuar për krejt + transmetimet e drejtpërdrejta. +all_non_lives_label: Aktivizo fjalosje për krejt transmetimet jo të drejtpërdrejta +room_type_description: Këtu mund të zgjidhni të keni dhoma më vete për çdo video, + ose t’i gruponi sipas kanalesh. +share_url_option_owner_moderators: Shfaqe për të zotin e videos dhe moderatorët e + instancës +poll_vote_instructions: 'Që të votoni, klikoni mbi zgjedhjen tuaj, ose dërgoni një + mesazh me një pikëçuditje pasuar nga numri që keni zgjedhur (Shembull: !1).' +external_auth_custom_oidc_description: "Mund të formësoni një shërbim të jashtëm OpenID + që mund të përdoret për të bërë hyrjen te fjalosja.\nJu lutemi, shihni te dokumentimi:\n + Rregullime.\n" +external_auth_google_oidc_description: "Aktivizimi i kësaj shton një buton “hyrje + me Google”.\nJu duhet të formësoni një aplikacion Google OAuth.\n" +external_auth_facebook_oidc_description: "Aktivizimi i kësaj shton një buton “hyrje + me Facebook”.\nJu duhet të formësoni një aplikacion Facebook OAuth.\n" +share_url_description: Do të ketë një buton për dhënien e URL-së së fjalosjes (mund + të përdoret për integrim OBS, për shembull). +avatar_set_description: "Avatarët parazgjedhje që do të përdoren për përdoruesit i + fjalosjes mund t’i zgjidhni nga disa grupe të ndryshme.\nJu lutemi, shihni te dokumentimi:\n + Rregullime.\n" +livechat_configuration_channel_mute_anonymous_desc: "Vlerë parazgjedhje për dhoma + të reja fjalosjeje.\nPër dhoma ekzistuese, mund të ndryshoni veçorinë te formulari + i formësimit të dhomave.\nKur kjo veçori është e aktivizuar, përdoruesit anonimë + vetëm se do të lexojnë fjalosjen dhe s’mund të dërgojnë mesazhe.\n" +transparent_background: Sfond i tejdukshëm (për integrim transmetimi, me OBS për shembull) +livechat_configuration_channel_emojis_desc: "Mund të formësoni emoxhi vetjake për + kanalin tuaj.\nKëto emoxhi do të jenë të përdorshëm që nga zgjedhësi i tyre.\nPërdoruesit + mund t’i përdoren edhe përmes emrit të shkurtër përkatës (për shembull, duke shkruar + \":emrineshkurtër:\").\n" +important_note_text: "Dokumentimin e shtojcës mund ta gjeni këtu:\n\n Dokumentim Shtojce Peertube Livechat\n.\n" +action_import_emojis_info: Nëse të dhënat e importuara janë në rregull, mos harroni + të ruani formularin. +livechat_configuration_channel_terms_desc: "Mund të formësoni një ndarje “terma & + kushte” që do t’u shfaqet përdoruesve që hyjnë në dhomat tuaja.\n" +poll_vote_instructions_xmpp: 'Që të votoni, dërgoni një mesazh me një pikëçuditje + pasuar nga numri që keni zgjedhur. Shembull: !1' +poll_anonymous_vote_ok: Vota juaj është marrë parasysh. Votat janë anonime, s’do t’u + shfaqen pjesëmarrësve të tjerë. +poll_vote_ok: Vota juaj është marrë parasysh, numëratorët do të përditësohen në çast. +moderator_notes: Shënime moderimi +chat: Fjalosje diff --git a/languages/sv.yml b/languages/sv.yml index f78c464e..3ed89487 100644 --- a/languages/sv.yml +++ b/languages/sv.yml @@ -28,7 +28,7 @@ room_type_description: Här kan du välja om du vill ha separata rum för varje theming_advanced_description: Utseende converse_theme_option_peertube: PeerTubes tema converse_theme_option_default: ConverseJS standardtema -converse_theme_option_concord: ConverseJS-temat concord +converse_theme_option_cyberpunk: ConverseJS-temat cyberpunk autocolors_label: Automatisk färgigenkänning save: Spara cancel: Avbryt diff --git a/languages/zh-Hant.yml b/languages/zh-Hant.yml index e69de29b..6f2e048d 100644 --- a/languages/zh-Hant.yml +++ b/languages/zh-Hant.yml @@ -0,0 +1,174 @@ +no_anonymous_description: "若勾選,匿名 Peertube 用戶將看不到聊天資訊。\n此功能仍處於實驗階段。\n如果您啟用了它,強烈建議您也勾選「不發布聊天訊息」。\n\ + 否則,某些第三方工具可能會嘗試開啟聊天,並出現不可預測的行為。\n" +external_auth_custom_oidc_description: "您可以設定可用於登入聊天的外部 OpenID Connect 提供者。\n請參考檔案:\n + 設定。\n" +room_type_option_video: 每個影片都有自己的網路聊天室 +login_external_auth_alert_message: 驗證失敗 +task_list_pick_empty: 現在沒有任何任務,請創建第一個 +task_list_pick_message: "選擇任務清單後,將建立新任務。\n若要查看任務,請使用頂部選單開啟任務應用程式。\n即時聊天外掛檔案中的更多資訊。\n" +livechat_configuration_channel_enable_bot_label: 啟用審核機器人 +login_remote_peertube_video_not_found: 該影片在此 Peertube 實例上不可用。 +menu_configuration_label: 聊天室 +livechat_configuration_title: 您的直播聊天室設定 +livechat_configuration_desc: 您可以在此處為與直播相關的聊天室設定一些進階選項。 +system_prosody_description: "警告:如果您不確定自己在做什麼,請不要檢查此設定。\n透過檢查此設置,您的 Peertube 將使用系統附帶的 + Prosody 伺服器,\n而不是嵌入的 AppImage。\n僅當您遇到嵌入式 Prosody 問題時才使用此選項。\n" +configuration_description: "頻道進階組態\n以下設定涉及進階頻道選項:\n使用者將能夠在他們的頻道上新增客製化,\n\ + 啟動審核機器人,...\n" +disable_websocket_label: 停用 Websocket +save: 儲存 +successfully_saved: 存檔成功 +livechat_configuration_please_select: 請在下面選擇您的頻道之一,以設定其聊天選項。 +livechat_configuration_channel_title: 頻道選項 +livechat_configuration_channel_desc: 您可以在此處為此頻道設定一些選項(審核策略,...)。 +livechat_configuration_channel_forbidden_words_label: 禁止使用的單字或句子 +livechat_configuration_channel_forbidden_words_desc: "您可以組態一些由機器人自動審核的單字(包含此類單字的訊息將立即刪除)。\n\ + 您也可以新增一個可選原因,該原因將顯示在已刪除郵件的位置。\n檔案頁面上提供了幾個範例。\n" +livechat_configuration_channel_forbidden_words_desc2: "每行只能一個單字或句子。如果將多個單字放在一行上,它將只配取到包含整行的訊息。\n" +livechat_configuration_channel_command_cmd_label: 命令 +livechat_configuration_channel_for_more_info: "有關如何設定此功能的更多資訊,請按一下幫助按鈕參考檔案。\n" +livechat_configuration_channel_banned_jids_label: 禁止的使用者和模式 +validation_error: 驗證時出現錯誤。 +invalid_value: 無效的值。 +invalid_value_missing: 該欄必填。 +invalid_value_wrong_type: 該欄不接受這種型別。 +invalid_value_wrong_format: 錯誤的格式。 +invalid_value_not_in_range: 值不在符合範圍內。 +invalid_value_file_too_big: '檔案太大(max size: %s).' +invalid_value_duplicate: 重覆值 +too_many_entries: 太多項目 +chatroom_not_accessible: 這個聊天室不存在或者您無法訪問。 +login_using_external_account: 使用外部帳戶登入 +login_remote_peertube: 使用另一個 Peertube 實例上的帳號登入: +login_remote_peertube_url: 您的Peertube實例網址 +login_remote_peertube_searching: 在 Peertube 實例上搜尋影片... +login_remote_peertube_video_not_found_try_anyway: 在某些情況下,如果您連接到遠端實例,仍然可以檢索視訊。 +login_remote_peertube_video_not_found_try_anyway_button: 無論如何嘗試在 Peertube 實例上打開影片 +login_remote_peertube_video_open_failed: 您的瀏覽器阻止了遠端實例的啟動,請嘗試手動開啟此連結: +tasks: 任務 +task_list_create: '創建新任務清單:' +task_list_create_error: 儲存任務清單時發生錯誤 +task_list_name: 任務清單名稱 +task_create: 創建新任務 +task_name: 任務名稱 +task_description: 任務用途 +task_delete: 刪除任務 +task_delete_confirm: 您確定要刪除此任務? +task_list_pick_title: 請選擇任務清單 +promote: 成為版主 +livechat_configuration_channel_emojis_title: 頻道emojis +livechat_configuration_channel_bot_options_title: 審核機器人選項 +livechat_configuration_channel_forbidden_words_reason_label: 原因 +livechat_configuration_channel_command_message_desc: 發送訊息。 +slow_mode_info: 限速模式已啟用,使用者可以每 %1$s 秒發送一則訊息。 +disable_channel_configuration_label: 停用進階頻道設定和聊天機器人 +cancel: 取消 +livechat_configuration_channel_forbidden_words_reason_desc: 除已刪除訊息外還顯示的原因 +error: 錯誤 +use_current_theme_color: 使用當前主題顏色 +chat_title: 聊天 +federation_no_remote_chat_description: "透過檢查此設置,您的實例將永遠不會顯示來自遠端視訊的聊天。\n" +federation_dont_publish_remotely_label: 不公開聊天資訊 +all_lives_label: 所有直播啟動聊天室 +no_anonymous_label: 隱藏匿名使用者的聊天 +converse_theme_label: ConverseJS 主題 +converse_theme_description: 請選擇您要使用的 converseJS 主題。 +converse_theme_option_default: 預設ConverseJS主題 +chat_style_label: 聊天 iframe 風格屬性 +prosody_peertube_uri_label: Peertube API呼叫網址 +task_list_delete: 刪除任務清單 +task_list_delete_confirm: 您確定要刪除此任務清單? +federation_description: "聯盟\n以下設定涉及與其他 Peertube 實例的聯合,\n和其他聯邦多元化軟體。\n" +chat_terms_label: 條款及條件 +list_rooms_label: 列出現有聊天室 +federation_no_remote_chat_label: 不顯示遠端聊天 +external_auth_description: "外部身份驗證\n對於沒有 Peertube 帳戶的用戶,您可以基於遠端身份驗證提供者啟用各種身份驗證模式。\n" +prosody_advanced_description: 聊天伺服器進階設定 +invalid_value_too_long: 值超出範圍 +login_remote_peertube_no_livechat: 此 Peertube 實例上未安裝即時聊天外掛程式。 +login_remote_peertube_url_invalid: 無效的Peertube網址。 +theming_advanced_description: 主題 +link_copied: 連結已複製 +show_scrollbarr: 顯示聊天捲軸 +transparent_background: 透明背景(用於串流媒體整合,例如OBS) +room_type_option_channel: 網路聊天室按頻道分組 +auto_display_label: 自動開啟聊天 +share_url_option_everyone: 任何人皆可看到 +share_url_option_owner: 只對影片擁有者顯示 +livechat_configuration_channel_slow_mode_label: 限速模式 +livechat_configuration_channel_slow_mode_desc: "限速模式預設值:\n\n 0:停用限速模式\n + 任何正整數:使用者可以每 X 秒發送一條訊息(審核者不受限制)\n\n" +prosody_firewall_name: 名字 +prosody_firewall_file_enabled: 啟用 +livechat_configuration_channel_emojis_desc: "您可以為您的頻道組態自訂表情符號。\n這些表情符號將在表情符號選擇器中提供。\n\ + 使用者也可以透過其短名稱來使用它們(例如透過編寫“:shortname:”)。\n" +livechat_emojis_shortname: 簡稱 +livechat_emojis_file: 檔案 +action_import: 匯入 +action_export: 匯出 +list_rooms_description: "列出聊天室\n" +autocolors_description: "嘗試自動偵測使用者目前主題的顏色。\n啟用此設定後,外掛程式會嘗試自動偵測顏色以套用至聊天主題。\n如果這對於您的某些 + Peertube 主題無法正常運作,您可以停用此選項。\n您可以向官方報告該錯誤\n\n 問題追蹤器\n。不要忘記指定哪個主題不起作用。\n" +prosody_firewall_content: 檔案內容 +prosody_firewall_disabled_warning: "警告:mod_firewall 在即時聊天外掛設定中被停用,如果您希望此組態生效,則必須啟用它予以考慮。\n" +disable_websocket_description: "當 Peertube >= 5.0.0 時,外掛嘗試使用 Websocket 連線進行聊天。\n如果使用者的瀏覽器或連線不相容,瀏覽器將自動轉而使用 + BOSH 協定。\n\n但在極少數情況下,這可能會失敗。例如,如果您在 Peertube 前面有一個反向代理,但它不\n允許外掛的 Websocket + 連線。\n在這種情況下,您可以檢查此設定以停用 Websocket 連線。\n" +prosody_firewall_name_desc: "只能包含:字母數字字元、底線和連字元。\n指令將按字母順序讀取。\n" +open_chat: 開始聊天 +copy: 複製 +federation_dont_publish_remotely_description: "透過檢查此設置,您的實例將不會在 fediverse 上發布聊天訊息。\n + 遠端 Peertube 實例不會意識到它們是與您的視訊關聯的聊天室。\n請注意:如果您已經進行過聊天,則該資訊可能已經發布。\n您必須等待下一次視訊更新,訊息才會取消發布。\n + 此外,如果您停用此設置,則必須等待視訊更新後才能顯示資訊。\n再次發布。當現場活動恢復或停止時,會發生此更新。\n請注意:此設定僅影響透過 ActivityPub + 協定發布資訊。\n它不會阻止遠端應用程式檢測聊天的存在並嘗試連接到它。\n" +auto_ban_anonymous_ip_description: "透過啟用此選項,每次匿名用戶被禁止進入聊天室時,其 IP 也將被聊天伺服器禁止。\n警告:如果您的執行個體開放註冊,任何使用者都可以建立陷阱房間、邀請使用者加入,並自動禁止所有匿名使用者的 + IP。\n禁止的IP清單不會被存儲,它會在伺服器重新啟動或更改某些外掛的設定時被清除。\n被禁止的IP會記錄在Prosody伺服器日誌檔案中,因此伺服器管理員最終可以使用一些外部工具(如fail2ban)來更廣泛地禁止IP。\n" +avatar_set_description: "您可以從多個不同的設定中選擇將用於聊天使用者的預設頭像。\n請參考檔案:\n設定。\n" +avatar_set_option_sepia: 棕褐色(Peertube 吉祥物) +converse_theme_option_peertube: Peertube主題 +autocolors_label: 自動色彩檢測 +open_chat_new_window: 在新視窗開啟聊天 +close_chat: 關閉聯天 +tips_for_streamers: "給直播主的提示:例如,要使用 OBS 將聊天嵌入到視訊直播中,\n產生唯讀連結並將其供瀏覽器使用。\n" +chat_terms_description: "這些條款和條件將在所有使用者加入聊天室時顯示。\n直播主還可以為其頻道配置條款和條件,這些條款和條件將立即顯示在這些全域條款和條件之後。\n" +auto_display_description: 觀看影片時,聊天視窗會自動開啟。 +open_blank_label: 顯示“在新視窗中開啟”按鈕 +open_blank_description: 此按鈕可在新視窗中開啟網路聊天。 +share_url_label: 顯示「共享聊天連結」按鈕 +share_url_description: 用於共享聊天 URL 的按鈕(例如,可用於 OBS 整合)。 +share_url_option_nobody: 無人可見 +share_url_option_owner_moderators: 只有影片擁有者與管理者可見 +auto_ban_anonymous_ip_label: 當用戶被禁止進入聊天室時,禁止匿名用戶的 IP +copied: 已複製 +generate_iframe: 產生 iframe 以將聊天嵌入到網站中 +chat_for_live_stream: '直播聊天:' +use_chat: 使用聊天功能 +use_chat_help: 如果啟用,影片旁邊將會出現聊天視窗。 +share_chat_link: 分享聊天連結 +read_only: 唯讀 +external_auth_custom_oidc_button_label_label: 連接按鈕的標籤 +external_auth_custom_oidc_button_label_description: 此標籤將作為向該 OIDC 提供者進行身份驗證的按鈕標籤顯示給使用者。 +external_auth_custom_oidc_discovery_url_label: OIDC_custom_dicovery_url +room_type_label: 聊天室類型 +room_type_description: 您可以在此處選擇為每個影片設定單獨的聊天室,或按頻道對它們進行分組。 +video: 影片 +channel: 頻道 +last_activity: 最後異動 +important_note_title: 注意事項 +online_help: 線上幫助 +not_found: 未找到 +important_note_text: "您可以在這裡找到外掛文件:\n\n Peertube 外掛即時聊天檔案\n。\n" +diagnostic: "在尋求協助之前,請使用診斷工具:\n啟動診斷\n(如果此按鈕沒有開啟新視窗,請嘗試重新整理頁面)。\n" +room_description: 聊天室說明 +room_name: 聊天室名稱 +per_live_video_label: 使用者在直播時可啟用聊天 +per_live_video_warning_description: "\n 您已啟用「使用者可以自已啟用聊天室」設定。\n 它與“啟動所有直播都有聊天室”設定是互斥的。\n\n" +all_lives_description: 若勾選,則所有直播都啟用聊天室。 diff --git a/package-lock.json b/package-lock.json index 0f02b0db..1137c37c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "peertube-plugin-livechat", - "version": "10.3.3", + "version": "11.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "peertube-plugin-livechat", - "version": "10.3.3", + "version": "11.0.1", "license": "AGPL-3.0", "dependencies": { "@xmpp/jid": "^0.13.1", @@ -56,7 +56,7 @@ "stylelint-config-recommended-scss": "^5.0.1", "stylelint-config-standard-scss": "^2.0.1", "svgo": "^2.8.0", - "typescript": "^4.3.5", + "typescript": "^4.9.5", "yaml": "^2.2.1" }, "engines": { @@ -85,989 +85,1102 @@ } }, "node_modules/@aws-crypto/crc32": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", - "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "dev": true, "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, + "node_modules/@aws-crypto/crc32/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/@aws-crypto/crc32c": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz", - "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", "dev": true, "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", - "dev": true, - "dependencies": { - "tslib": "^1.11.1" - } + "node_modules/@aws-crypto/crc32c/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true }, "node_modules/@aws-crypto/sha1-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz", - "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", "dev": true, "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", "dev": true, "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", "dev": true, "dependencies": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" } }, + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", "dev": true, "dependencies": { - "tslib": "^1.11.1" + "tslib": "^2.6.2" } }, + "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", "dev": true, "dependencies": { "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" } }, - "node_modules/@aws-sdk/chunked-blob-reader": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/chunked-blob-reader/-/chunked-blob-reader-3.310.0.tgz", - "integrity": "sha512-CrJS3exo4mWaLnWxfCH+w88Ou0IcAZSIkk4QbmxiHl/5Dq705OLoxf4385MVyExpqpeVJYOYQ2WaD8i/pQZ2fg==", + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, - "node_modules/@aws-sdk/chunked-blob-reader/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/client-s3": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.363.0.tgz", - "integrity": "sha512-LNnfg/t8wG5Fqj6l+PSV/t+IXDq9r3Kj9jEHn84513+p7bewXYSSreSpmLjG8OcKuMfHc9EJGNQ3DkMyFaLoWg==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.637.0.tgz", + "integrity": "sha512-y6UC94fsMvhKbf0dzfnjVP1HePeGjplfcYfilZU1COIJLyTkMcUv4XcT4I407CGIrvgEafONHkiC09ygqUauNA==", "dev": true, "dependencies": { - "@aws-crypto/sha1-browser": "3.0.0", - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.363.0", - "@aws-sdk/credential-provider-node": "3.363.0", - "@aws-sdk/hash-blob-browser": "3.357.0", - "@aws-sdk/hash-stream-node": "3.357.0", - "@aws-sdk/md5-js": "3.357.0", - "@aws-sdk/middleware-bucket-endpoint": "3.363.0", - "@aws-sdk/middleware-expect-continue": "3.363.0", - "@aws-sdk/middleware-flexible-checksums": "3.363.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-location-constraint": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-sdk-s3": "3.363.0", - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/middleware-ssec": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/signature-v4-multi-region": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@aws-sdk/xml-builder": "3.310.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/eventstream-serde-browser": "^1.0.1", - "@smithy/eventstream-serde-config-resolver": "^1.0.1", - "@smithy/eventstream-serde-node": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-stream": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "@smithy/util-waiter": "^1.0.1", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/client-s3/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/client-sso": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.363.0.tgz", - "integrity": "sha512-PZ+HfKSgS4hlMnJzG+Ev8/mgHd/b/ETlJWPSWjC/f2NwVoBQkBnqHjdyEx7QjF6nksJozcVh5Q+kkYLKc/QwBQ==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz", + "integrity": "sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==", "dev": true, "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/client-sso-oidc": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.363.0.tgz", - "integrity": "sha512-V3Ebiq/zNtDS/O92HUWGBa7MY59RYSsqWd+E0XrXv6VYTA00RlMTbNcseivNgp2UghOgB9a20Nkz6EqAeIN+RQ==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.637.0.tgz", + "integrity": "sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==", "dev": true, "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" } }, "node_modules/@aws-sdk/client-sso-oidc/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/client-sso/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/client-sts": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.363.0.tgz", - "integrity": "sha512-0jj14WvBPJQ8xr72cL0mhlmQ90tF0O0wqXwSbtog6PsC8+KDE6Yf+WsxsumyI8E5O8u3eYijBL+KdqG07F/y/w==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz", + "integrity": "sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==", "dev": true, "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/credential-provider-node": "3.363.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-sdk-sts": "3.363.0", - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.1", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/smithy-client": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/client-sts/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@aws-sdk/core": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", + "integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==", + "dev": true, + "dependencies": { + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.363.0.tgz", - "integrity": "sha512-VAQ3zITT2Q0acht0HezouYnMFKZ2vIOa20X4zQA3WI0HfaP4D6ga6KaenbDcb/4VFiqfqiRHfdyXHP0ThcDRMA==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz", + "integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.363.0.tgz", - "integrity": "sha512-ZYN+INoqyX5FVC3rqUxB6O8nOWkr0gHRRBm1suoOlmuFJ/WSlW/uUGthRBY5x1AQQnBF8cpdlxZzGHd41lFVNw==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz", + "integrity": "sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==", "dev": true, "dependencies": { - "@aws-sdk/credential-provider-env": "3.363.0", - "@aws-sdk/credential-provider-process": "3.363.0", - "@aws-sdk/credential-provider-sso": "3.363.0", - "@aws-sdk/credential-provider-web-identity": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" } }, "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.363.0.tgz", - "integrity": "sha512-C1qXFIN2yMxD6pGgug0vR1UhScOki6VqdzuBHzXZAGu7MOjvgHNdscEcb3CpWnITHaPL2ztkiw75T1sZ7oIgQg==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.637.0.tgz", + "integrity": "sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==", "dev": true, "dependencies": { - "@aws-sdk/credential-provider-env": "3.363.0", - "@aws-sdk/credential-provider-ini": "3.363.0", - "@aws-sdk/credential-provider-process": "3.363.0", - "@aws-sdk/credential-provider-sso": "3.363.0", - "@aws-sdk/credential-provider-web-identity": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.363.0.tgz", - "integrity": "sha512-fOKAINU7Rtj2T8pP13GdCt+u0Ml3gYynp8ki+1jMZIQ+Ju/MdDOqZpKMFKicMn3Z1ttUOgqr+grUdus6z8ceBQ==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.363.0.tgz", - "integrity": "sha512-5RUZ5oM0lwZSo3EehT0dXggOjgtxFogpT3cZvoLGtIwrPBvm8jOQPXQUlaqCj10ThF1sYltEyukz/ovtDwYGew==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.637.0.tgz", + "integrity": "sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==", "dev": true, "dependencies": { - "@aws-sdk/client-sso": "3.363.0", - "@aws-sdk/token-providers": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/client-sso": "3.637.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.363.0.tgz", - "integrity": "sha512-Z6w7fjgy79pAax580wdixbStQw10xfyZ+hOYLcPudoYFKjoNx0NQBejg5SwBzCF/HQL23Ksm9kDfbXDX9fkPhA==", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" } }, "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/hash-blob-browser": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.357.0.tgz", - "integrity": "sha512-RDd6UgrGHDmleTnXM9LRSSVa69euSAG2mlNhZMEDWk3OFseXVYqBDaqroVbQ01rM2UAe8MeBFchlV9OmxuVgvw==", - "dev": true, - "dependencies": { - "@aws-sdk/chunked-blob-reader": "3.310.0", - "@aws-sdk/types": "3.357.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/hash-blob-browser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/hash-stream-node": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-stream-node/-/hash-stream-node-3.357.0.tgz", - "integrity": "sha512-KZjN1VAw1KHNp+xKVOWBGS+MpaYQTjZFD5f+7QQqW4TfbAkFFwIAEYIHq5Q8Gw+jVh0h61OrV/LyW3J2PVzc+w==", - "dev": true, - "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/hash-stream-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/is-array-buffer": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.310.0.tgz", - "integrity": "sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==", - "dev": true, - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/is-array-buffer/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/md5-js": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/md5-js/-/md5-js-3.357.0.tgz", - "integrity": "sha512-to42sFAL7KgV/X9X40LLfEaNMHMGQL6/7mPMVCL/W2BZf3zw5OTl3lAaNyjXA+gO5Uo4lFEiQKAQVKNbr8b8Nw==", - "dev": true, - "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/md5-js/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.363.0.tgz", - "integrity": "sha512-kR8+0X50zslpzRW29q4JbpPMadE1z39ZfGwPaBLKpoWvSGt4x+75FaoK71TH7urPPoFyD2Y+XKGA6YRYTUNHSQ==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", + "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "@smithy/util-config-provider": "^1.0.1", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.363.0.tgz", - "integrity": "sha512-I88xneZp6jRwySmIl9uI7eZCcTsqRVnTDfUr1JiXt7zonqNNm80PVYMs6pwaw7t97ec1AQJcsONjuXZyCMnu5g==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", + "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-expect-continue/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.363.0.tgz", - "integrity": "sha512-FBYmrMRX01uNximNN0WLgpf97GN4xNTLaKsDlkjYRWKJ+J97ICkvLG0FcSu7+SNCpCdJJBeQ5tRVOPVpUu6nmA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", + "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", "dev": true, "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.357.0", - "@smithy/is-array-buffer": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-sdk/types": "3.609.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.363.0.tgz", - "integrity": "sha512-FobpclDCf5Y1ueyJDmb9MqguAdPssNMlnqWQpujhYVABq69KHu73fSCWSauFPUrw7YOpV8kG1uagDF0POSxHzA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-host-header/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.363.0.tgz", - "integrity": "sha512-piNzpNNI/fChSGOZxcq/2msN2qFUSEAbhqs91zbcpv8CEPekVLc4W9laXCG764BEMyfG97ZU8MtzwHeMhELhBA==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-location-constraint/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.363.0.tgz", - "integrity": "sha512-SSGgthScYnFGTOw8EzbkvquqweFmvn7uJihkpFekbtBNGC/jGOGO+8ziHjTQ8t/iI/YKubEwv+LMi0f77HKSEg==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-logger/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.363.0.tgz", - "integrity": "sha512-MWD/57QgI/N7fG8rtzDTUdSqNpYohQfgj9XCFAoVeI/bU4usrkOrew43L4smJG4XrDxlNT8lSJlDtd64tuiUZA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.363.0.tgz", - "integrity": "sha512-npC8vLCero+vULizrK0QPjNanWbgH4A/2Llc1nO8N005uvUe7co6WglILF2W3guZrFk/0uGEdX67OnLxUD97pw==", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.635.0.tgz", + "integrity": "sha512-RLdYJPEV4JL/7NBoFUs7VlP90X++5FlJdxHz0DzCjmiD3qCviKy+Cym3qg1gBgHwucs5XisuClxDrGokhAdTQw==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.363.0.tgz", - "integrity": "sha512-1yy2Ac50FO8BrODaw5bPWvVrRhaVLqXTFH6iHB+dJLPUkwtY5zLM3Mp+9Ilm7kME+r7oIB1wuO6ZB1Lf4ZszIw==", - "dev": true, - "dependencies": { - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.363.0.tgz", - "integrity": "sha512-/7qia715pt9JKYIPDGu22WmdZxD8cfF/5xB+1kmILg7ZtjO0pPuTaCNJ7xiIuFd7Dn7JXp5lop08anX/GOhNRQ==", - "dev": true, - "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/signature-v4": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.363.0.tgz", - "integrity": "sha512-pN+QN1rMShYpJnTJSCIYnNRhD0S8xSZsTn6ThgcO559Xiwz5LMHFOfOXUCEyxtbVW5kMHLUh3w101AMUKae99A==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-ssec/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.363.0.tgz", - "integrity": "sha512-ri8YaQvXP6odteVTMfxPqFR26Q0h9ejtqhUDv47P34FaKXedEM4nC6ix6o+5FEYj6l8syGyktftZ5O70NoEhug==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz", + "integrity": "sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/middleware-user-agent/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.363.0.tgz", - "integrity": "sha512-iWamQSpaBKg88LKuiUq8xO/7iyxJ+ORkA3qDhAwUqyTJOg87ma47yFf4ycCKqINnflc3AIGLGzBHnkBc4cMF5g==", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.635.0.tgz", + "integrity": "sha512-J6QY4/invOkpogCHjSaDON1hF03viPpOnsrzVuCvJMmclS/iG62R4EY0wq1alYll0YmSdmKlpJwHMWwGtqK63Q==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/signature-v4": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@aws-sdk/signature-v4-crt": "^3.118.0" - }, - "peerDependenciesMeta": { - "@aws-sdk/signature-v4-crt": { - "optional": true - } + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/token-providers": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.363.0.tgz", - "integrity": "sha512-6+0aJ1zugNgsMmhTtW2LBWxOVSaXCUk2q3xyTchSXkNzallYaRiZMRkieW+pKNntnu0g5H1T0zyfCO0tbXwxEA==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", "dev": true, "dependencies": { - "@aws-sdk/client-sso-oidc": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" } }, "node_modules/@aws-sdk/token-providers/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/types": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.357.0.tgz", - "integrity": "sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/types/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.310.0.tgz", - "integrity": "sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==", + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", + "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/util-arn-parser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/util-buffer-from": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.310.0.tgz", - "integrity": "sha512-i6LVeXFtGih5Zs8enLrt+ExXY92QV25jtEnTKHsmlFqFAuL3VBeod6boeMXkN2p9lbSVVQ1sAOOYZOHYbYkntw==", - "dev": true, - "dependencies": { - "@aws-sdk/is-array-buffer": "3.310.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-buffer-from/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.357.0.tgz", - "integrity": "sha512-XHKyS5JClT9su9hDif715jpZiWHQF9gKZXER8tW0gOizU3R9cyWc9EsJ2BRhFNhi7nt/JF/CLUEc5qDx3ETbUw==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.637.0.tgz", + "integrity": "sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/util-endpoints/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz", - "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==", + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.363.0.tgz", - "integrity": "sha512-fk9ymBUIYbxiGm99Cn+kAAXmvMCWTf/cHAcB79oCXV4ELXdPa9lN5xQhZRFNxLUeXG4OAMEuCAUUuZEj8Fnc1Q==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-browser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.363.0.tgz", - "integrity": "sha512-Fli/dvgGA9hdnQUrYb1//wNSFlK2jAfdJcfNXA6SeBYzSeH5pVGYF4kXF0FCdnMA3Fef+Zn1zAP/hw9v8VJHWQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", "dev": true, "dependencies": { - "@aws-sdk/types": "3.357.0", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" }, "peerDependencies": { "aws-crt": ">=1.0.0" @@ -1079,61 +1192,28 @@ } }, "node_modules/@aws-sdk/util-user-agent-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - }, - "node_modules/@aws-sdk/util-utf8": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.310.0.tgz", - "integrity": "sha512-DnLfFT8uCO22uOJc0pt0DsSNau1GTisngBCDw8jQuWT5CqogMJu4b/uXmwEqfj8B3GX6Xsz8zOd6JpRlPftQoA==", - "dev": true, - "dependencies": { - "@aws-sdk/util-buffer-from": "3.310.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "dev": true, - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/util-utf8-browser/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", - "dev": true - }, - "node_modules/@aws-sdk/util-utf8/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.310.0.tgz", - "integrity": "sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@aws-sdk/xml-builder/node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@babel/cli": { @@ -3071,838 +3151,1000 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-1.0.1.tgz", - "integrity": "sha512-An6irzp9NCji2JtJHhrEFlDbxLwHd6c6Y9fq3ZeomyUR8BIXlGXVTxsemUSZVVgOq3166iYbYs/CrPAmgRSFLw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/abort-controller/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz", + "integrity": "sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==", + "dev": true, + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz", + "integrity": "sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==", + "dev": true, + "dependencies": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/chunked-blob-reader-native/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/chunked-blob-reader/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/config-resolver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-1.0.1.tgz", - "integrity": "sha512-quj0xUiEVG/UHfY82EtthR/+S5/17p3IxXArC3NFSNqryMobWbG9oWgJy2s2cgUSVZLzxevjKKvxrilK7JEDaA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "@smithy/util-config-provider": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/config-resolver/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", + "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", + "dev": true, + "dependencies": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/credential-provider-imds": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-1.0.1.tgz", - "integrity": "sha512-hkRJoxVCh4CEt1zYOBElE+G/MV6lyx3g68hSJpesM4pwMT/bzEVo5E5XzXY+6dVq8yszeatWKbFuqCCBQte8tg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", + "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", "dev": true, "dependencies": { - "@smithy/node-config-provider": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/credential-provider-imds/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/eventstream-codec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-1.0.1.tgz", - "integrity": "sha512-cpcTXQEOEs2wEvIyxW/iTHJ2m0RVqoEOTjjWEXD6SY8Gcs3FCFP6E8MXadC098tdH5ctMIUXc8POXyMpxzGnjw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", "dev": true, "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^1.1.0", - "@smithy/util-hex-encoding": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/eventstream-codec/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-1.0.1.tgz", - "integrity": "sha512-oc8vxe+AU2RzvXH/Ehh0TzM/Nsw3I3ywu7V3qaCzqdkBIntAwK9JGZqcSDsqTK0WxZKBRgFIEwopcuZ2slVnFQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.6.tgz", + "integrity": "sha512-2hM54UWQUOrki4BtsUI1WzmD13/SeaqT/AB3EUJKbcver/WgKNaiJ5y5F5XXuVe6UekffVzuUDrBZVAA3AWRpQ==", "dev": true, "dependencies": { - "@smithy/eventstream-serde-universal": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-browser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-1.0.1.tgz", - "integrity": "sha512-TJwaXima0djnNY819utO1j93qZHaheFH1bhHxBkMrImtEOuXY48Tjma/L2m8swkIq8dy8jFC9hrYOkD0eYHkFA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/eventstream-serde-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-1.0.1.tgz", - "integrity": "sha512-JEj8w7IRs4l+kcwKxbv3pNuu8n7ORC4pMFrIOrM4rERzrRnI7vMNTRzvAPGYA53rqm/Y9tBA9dw4C+H6hLXcsA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.5.tgz", + "integrity": "sha512-+upXvnHNyZP095s11jF5dhGw/Ihzqwl5G+/KtMnoQOpdfC3B5HYCcDVG9EmgkhJMXJlM64PyN5gjJl0uXFQehQ==", "dev": true, "dependencies": { - "@smithy/eventstream-serde-universal": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-1.0.1.tgz", - "integrity": "sha512-c6m9DH7m6D2S93dof4wSxysaGSQdauO20TNcSePzrgHd4rkTnz5pqZ1a7Pt22q2SKf09SvTugq5cV2Sy4r8zHw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.5.tgz", + "integrity": "sha512-5u/nXbyoh1s4QxrvNre9V6vfyoLWuiVvvd5TlZjGThIikc3G+uNiG9uOTCWweSRjv1asdDIWK7nOmN7le4RYHQ==", "dev": true, "dependencies": { - "@smithy/eventstream-codec": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/eventstream-serde-universal/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/fetch-http-handler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-1.0.1.tgz", - "integrity": "sha512-/e2A8eOMk4FVZBQ0o6uF/ttLtFZcmsK5MIwDu1UE3crM4pCAIP19Ul8U9rdLlHhIu81X4AcJmSw55RDSpVRL/w==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", "dev": true, "dependencies": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/querystring-builder": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-base64": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/fetch-http-handler/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", + "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", + "dev": true, + "dependencies": { + "@smithy/chunked-blob-reader": "^3.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-blob-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/hash-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-1.0.1.tgz", - "integrity": "sha512-eCz08BySBcOjVObjbRAS/XMKUGY4ujnuS+GoWeEpzpCSKDnO8/YQ0rStRt4C0llRmhApizYc1tK9DiJwfvXcBg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "@smithy/util-buffer-from": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/hash-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/hash-stream-node": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", + "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", + "dev": true, + "dependencies": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-stream-node/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/invalid-dependency": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-1.0.1.tgz", - "integrity": "sha512-kib63GFlAzRn/wf8M0cRWrZA1cyOy5IvpTkLavCY782DPFMP0EaEeD6VrlNIOvD6ncf7uCJ68HqckhwK1qLT3g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/invalid-dependency/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/is-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-1.0.1.tgz", - "integrity": "sha512-fHSTW70gANnzPYWNDcWkPXpp+QMbHhKozbQm/+Denkhp4gwSiPuAovWZRpJa9sXO+Q4dOnNzYN2max1vTCEroA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/is-array-buffer/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/md5-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz", + "integrity": "sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==", + "dev": true, + "dependencies": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/md5-js/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/middleware-content-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-1.0.1.tgz", - "integrity": "sha512-vWWigayk5i2cFp9xPX5vdzHyK+P0t/xZ3Ovp4Ss+c8JQ1Hlq2kpJZVWtTKsmdfND5rVo5lu0kD5wgAMUCcmuhw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", + "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", "dev": true, "dependencies": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-content-length/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/middleware-endpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-1.0.2.tgz", - "integrity": "sha512-F3CyXgjtDI4quGFkDmVNytt6KMwlzzeMxtopk6Edue4uKdKcMC1vUmoRS5xTbFzKDDp4XwpnEV7FshPaL3eCPw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", "dev": true, "dependencies": { - "@smithy/middleware-serde": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-endpoint/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/middleware-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-1.0.3.tgz", - "integrity": "sha512-ZRsjG8adtxQ456FULPqPFmWtrW44Fq8IgdQvQB+rC2RSho3OUzS+TiEIwb5Zs6rf2IoewITKtfdtsUZcxXO0ng==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", + "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", "dev": true, "dependencies": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/service-error-classification": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/util-middleware": "^1.0.1", - "@smithy/util-retry": "^1.0.3", - "tslib": "^2.5.0", - "uuid": "^8.3.2" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-retry/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, + "node_modules/@smithy/middleware-retry/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@smithy/middleware-serde": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-1.0.1.tgz", - "integrity": "sha512-bn5lWk8UUeXFCQfkrNErz5SbeNd+2hgYegHMLsOLPt4URDIsyREar6wMsdsR+8UCdgR5s8udG3Zalgc7puizIQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-serde/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/middleware-stack": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-1.0.1.tgz", - "integrity": "sha512-T6+gsAO1JYamOJqmORCrByDeQ/NB+ggjHb33UDOgdX4xIjXz/FB/3UqHgQu6PL1cSFrK+i4oteDIwqARDs/Szw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/middleware-stack/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/node-config-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-1.0.1.tgz", - "integrity": "sha512-FRxifH/J2SgOaVLihIqBFuGhiHR/NfzbZYp5nYO7BGgT/gc/f9nAuuRJcEy/hwO3aI6ThyG5apH4tGec6A2sCw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", "dev": true, "dependencies": { - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/node-config-provider/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/node-http-handler": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-1.0.2.tgz", - "integrity": "sha512-PzPrGRSt3kNuruLCeR4ffJp57ZLVnIukMXVL3Ppr65ZoxiE+HBsOVAa/Z/T+4HzjCM6RaXnnmB8YKfsDjlb0iA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", "dev": true, "dependencies": { - "@smithy/abort-controller": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/querystring-builder": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/node-http-handler/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/property-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-1.0.1.tgz", - "integrity": "sha512-3EG/61Ls1MrgEaafpltXBJHSqFPqmTzEX7QKO7lOEHuYGmGYzZ08t1SsTgd1vM74z0IihoZyGPynZ7WmXKvTeg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/property-provider/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/protocol-http": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.1.0.tgz", - "integrity": "sha512-H5y/kZOqfJSqRkwtcAoVbqONmhdXwSgYNJ1Glk5Ry8qlhVVy5qUzD9EklaCH8/XLnoCsLO/F/Giee8MIvaBRkg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/protocol-http/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/querystring-builder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-1.0.1.tgz", - "integrity": "sha512-J5Tzkw1PMtu01h6wl+tlN5vsyROmS6/z5lEfNlLo/L4ELHeVkQ4Q0PEIjDddPLfjVLCm8biQTESE5GCMixSRNQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "@smithy/util-uri-escape": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/querystring-builder/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/querystring-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-1.0.1.tgz", - "integrity": "sha512-zauxdMc3cwxoLitI5DZqH7xN6Fk0mwRxrUMAETbav2j6Se2U0UGak/55rZcDg2yGzOURaLYi5iOm1gHr98P+Bw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/querystring-parser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/service-error-classification": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-1.0.2.tgz", - "integrity": "sha512-Q5CCuzYL5FGo6Rr/O+lZxXHm2hrRgbmMn8MgyjqZUWZg20COg20DuNtIbho2iht6CoB7jOpmpBqhWizLlzUZgg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", "dev": true, + "dependencies": { + "@smithy/types": "^3.3.0" + }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-1.0.1.tgz", - "integrity": "sha512-EztziuIPoNronENGqh+MWVKJErA4rJpaPzJCPukzBeEoG2USka0/q4B5Mr/1zszOnrb49fPNh4u3u5LfiH7QzA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", "dev": true, "dependencies": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/shared-ini-file-loader/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/signature-v4": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-1.0.1.tgz", - "integrity": "sha512-2D69je14ou1vBTnAQeysSK4QVMm0j3WHS3MDg/DnHnFFcXRCzVl/xAARO7POD8+fpi4tMFPs8Z4hzo1Zw40L0Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", "dev": true, "dependencies": { - "@smithy/eventstream-codec": "^1.0.1", - "@smithy/is-array-buffer": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-hex-encoding": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "@smithy/util-uri-escape": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/signature-v4/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/smithy-client": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-1.0.3.tgz", - "integrity": "sha512-Wh1mNP/1yUZK0uYkgCQ6NMxpBT3Fmc45TMdUfOlH1xD2zGYL7U4yDHFOhEZdi/suyjaelFobXB2p9pPIw6LjRQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", "dev": true, "dependencies": { - "@smithy/middleware-stack": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-stream": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/smithy-client/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.1.0.tgz", - "integrity": "sha512-KzmvisMmuwD2jZXuC9e65JrgsZM97y5NpDU7g347oB+Q+xQLU6hQZ5zFNNbEfwwOJHoOvEVTna+dk1h/lW7alw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/types/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/url-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-1.0.1.tgz", - "integrity": "sha512-33vWEtE6HzmwjEcEb4I58XMLRAchwPS93YhfDyXAXr1jwDCzfXmMayQwwpyW847rpWj0XJimxqia8q0z+k/ybw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", "dev": true, "dependencies": { - "@smithy/querystring-parser": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" } }, "node_modules/@smithy/url-parser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-base64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-1.0.1.tgz", - "integrity": "sha512-rJcpRi/yUi6TyCEkjdTH86/ExBuKlfctEXhG9/4gMJ3/cnPcHJJnr0mQ9evSEO+3DbpT/Nxq90bcTBdTIAmCig==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", "dev": true, "dependencies": { - "@smithy/util-buffer-from": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-base64/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-body-length-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-1.0.1.tgz", - "integrity": "sha512-Pdp744fmF7E1NWoSb7256Anhm8eYoCubvosdMwXzOnHuPRVbDa15pKUz2027K3+jrfGpXo1r+MnDerajME1Osw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" } }, "node_modules/@smithy/util-body-length-browser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-body-length-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-1.0.1.tgz", - "integrity": "sha512-4PIHjDFwG07SNensAiVq/CJmubEVuwclWSYOTNtzBNTvxOeGLznvygkGYgPzS3erByT8C4S9JvnLYgtrsVV3nQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-body-length-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-buffer-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-1.0.1.tgz", - "integrity": "sha512-363N7Wq0ceUgE5lLe6kaR6GlJs2/m4r9V6bRMfIszb6P1FZbbRRM2FQYUWWPFSsRymm9mJL18b3fjiVsIvhDGg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "dev": true, "dependencies": { - "@smithy/is-array-buffer": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-buffer-from/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-config-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-1.0.1.tgz", - "integrity": "sha512-4Qy38Oy5/q43MpTwCLV1P+7NeaOp4W2etQDxMjgEeRlOyGGNlgttn0syi4g2rVSukFVqQ6FbeRs5xbnFmS6kaQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-config-provider/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-1.0.1.tgz", - "integrity": "sha512-/9ObwNch4Z/NJYfkO4AvqBWku60Ju+c2Ck32toPOLmWe/V6eI9FLn8C1abri+GxDRCkLIqvkaWU1lgZ3nWZIIw==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", + "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", "dev": true, "dependencies": { - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@smithy/util-defaults-mode-browser/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-1.0.1.tgz", - "integrity": "sha512-XQM3KvqRLgv7bwAzVkXTITkOmcOINoG9icJiGT8FA0zV35lY5UvyIsg5kHw01xigQS8ufa/33AwG3ZoXip+V5g==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", + "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", "dev": true, "dependencies": { - "@smithy/config-resolver": "^1.0.1", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/config-resolver": "^3.0.5", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@smithy/util-defaults-mode-node/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "node_modules/@smithy/util-endpoints": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", + "dev": true, + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-hex-encoding": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-1.0.1.tgz", - "integrity": "sha512-FPTtMz/t02/rbfq5Pdll/TWUYP+GVFLCQNr+DgifrLzVRU0g8rdRjyFpDh8nPTdkDDusTTo9P1bepAYj68s0eA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-hex-encoding/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-middleware": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-1.0.1.tgz", - "integrity": "sha512-u9akN3Zmbr0vZH4F+2iehG7cFg+3fvDfnvS/hhsXH4UHuhqiQ+ADefibnLzPoz1pooY7rvwaQ/TVHyJmZHdLdQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-middleware/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-1.0.3.tgz", - "integrity": "sha512-gYQnZDD8I2XJFspVwUISyukjPWVikTzKR0IdG8hCWYPTpeULFl1o6yzXlT5SL63TBkuEYl0R1/93cdNtMiNnoA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", "dev": true, "dependencies": { - "@smithy/service-error-classification": "^1.0.2", - "tslib": "^2.5.0" + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">= 14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-retry/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-1.0.1.tgz", - "integrity": "sha512-4aBCIz35aZAnt2Rbq341KrnUzGhWv2/Zu8HouJqYLvSWCzlrvsNCGlXP4e70Kjzcw8hSuuCNtdUICwQ5qUWLxg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", "dev": true, "dependencies": { - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-buffer-from": "^1.0.1", - "@smithy/util-hex-encoding": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-stream/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-uri-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-1.0.1.tgz", - "integrity": "sha512-IJUrRnXKEIc+PKnU1XzTsIENVR+60jUDPBP3iWX/EvuuT3Xfob47x1FGUe2c3yMXNuU6ax8VDk27hL5LKNoehQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "dev": true, "dependencies": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-uri-escape/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-utf8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-1.0.1.tgz", - "integrity": "sha512-iX6XHpjh4DFEUIBSKp2tjy3pYnLQMsJ62zYi1BVAC0kobE6p8AVpiZnxsU3ZkgQatAsUaEspFHUZ7CL7oSqaPQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "dev": true, "dependencies": { - "@smithy/util-buffer-from": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-utf8/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@smithy/util-waiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-1.0.1.tgz", - "integrity": "sha512-dsn8O0s3pFIgxFzySLe1dv0w4tEQizEP6UqbgZ4r/Kar4n8pSdrPi6DJg8BzXwkwEIZpMtV4/nhSeGZ7HksDXA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", + "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", "dev": true, "dependencies": { - "@smithy/abort-controller": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^3.1.1", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=16.0.0" } }, "node_modules/@smithy/util-waiter/node_modules/tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true }, "node_modules/@socket.io/component-emitter": { @@ -7818,18 +8060,18 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "dev": true, "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, { "type": "github", "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" } ], "dependencies": { @@ -9954,12 +10196,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -13455,9 +13697,9 @@ "dev": true }, "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -14174,1013 +14416,1065 @@ } }, "@aws-crypto/crc32": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", - "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", "dev": true, "requires": { - "@aws-crypto/util": "^3.0.0", + "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/crc32c": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz", - "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==", - "dev": true, - "requires": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - }, - "@aws-crypto/sha1-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz", - "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==", - "dev": true, - "requires": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", - "dev": true, - "requires": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", - "dev": true, - "requires": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", - "dev": true, - "requires": { - "tslib": "^1.11.1" - } - }, - "@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", - "dev": true, - "requires": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "@aws-sdk/chunked-blob-reader": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/chunked-blob-reader/-/chunked-blob-reader-3.310.0.tgz", - "integrity": "sha512-CrJS3exo4mWaLnWxfCH+w88Ou0IcAZSIkk4QbmxiHl/5Dq705OLoxf4385MVyExpqpeVJYOYQ2WaD8i/pQZ2fg==", - "dev": true, - "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "dev": true, + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dev": true, + "requires": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dev": true, + "requires": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dev": true, + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dev": true, + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dev": true, + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dev": true, + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/client-s3": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.363.0.tgz", - "integrity": "sha512-LNnfg/t8wG5Fqj6l+PSV/t+IXDq9r3Kj9jEHn84513+p7bewXYSSreSpmLjG8OcKuMfHc9EJGNQ3DkMyFaLoWg==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.637.0.tgz", + "integrity": "sha512-y6UC94fsMvhKbf0dzfnjVP1HePeGjplfcYfilZU1COIJLyTkMcUv4XcT4I407CGIrvgEafONHkiC09ygqUauNA==", "dev": true, "requires": { - "@aws-crypto/sha1-browser": "3.0.0", - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.363.0", - "@aws-sdk/credential-provider-node": "3.363.0", - "@aws-sdk/hash-blob-browser": "3.357.0", - "@aws-sdk/hash-stream-node": "3.357.0", - "@aws-sdk/md5-js": "3.357.0", - "@aws-sdk/middleware-bucket-endpoint": "3.363.0", - "@aws-sdk/middleware-expect-continue": "3.363.0", - "@aws-sdk/middleware-flexible-checksums": "3.363.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-location-constraint": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-sdk-s3": "3.363.0", - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/middleware-ssec": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/signature-v4-multi-region": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@aws-sdk/xml-builder": "3.310.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/eventstream-serde-browser": "^1.0.1", - "@smithy/eventstream-serde-config-resolver": "^1.0.1", - "@smithy/eventstream-serde-node": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-stream": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "@smithy/util-waiter": "^1.0.1", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/client-sso": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.363.0.tgz", - "integrity": "sha512-PZ+HfKSgS4hlMnJzG+Ev8/mgHd/b/ETlJWPSWjC/f2NwVoBQkBnqHjdyEx7QjF6nksJozcVh5Q+kkYLKc/QwBQ==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz", + "integrity": "sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==", "dev": true, "requires": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/client-sso-oidc": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.363.0.tgz", - "integrity": "sha512-V3Ebiq/zNtDS/O92HUWGBa7MY59RYSsqWd+E0XrXv6VYTA00RlMTbNcseivNgp2UghOgB9a20Nkz6EqAeIN+RQ==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.637.0.tgz", + "integrity": "sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==", "dev": true, "requires": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.2", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/protocol-http": "^1.0.1", - "@smithy/smithy-client": "^1.0.3", - "@smithy/types": "^1.0.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.2", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/client-sts": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.363.0.tgz", - "integrity": "sha512-0jj14WvBPJQ8xr72cL0mhlmQ90tF0O0wqXwSbtog6PsC8+KDE6Yf+WsxsumyI8E5O8u3eYijBL+KdqG07F/y/w==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz", + "integrity": "sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==", "dev": true, "requires": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/credential-provider-node": "3.363.0", - "@aws-sdk/middleware-host-header": "3.363.0", - "@aws-sdk/middleware-logger": "3.363.0", - "@aws-sdk/middleware-recursion-detection": "3.363.0", - "@aws-sdk/middleware-sdk-sts": "3.363.0", - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/middleware-user-agent": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@aws-sdk/util-user-agent-browser": "3.363.0", - "@aws-sdk/util-user-agent-node": "3.363.0", - "@smithy/config-resolver": "^1.0.1", - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/hash-node": "^1.0.1", - "@smithy/invalid-dependency": "^1.0.1", - "@smithy/middleware-content-length": "^1.0.1", - "@smithy/middleware-endpoint": "^1.0.1", - "@smithy/middleware-retry": "^1.0.1", - "@smithy/middleware-serde": "^1.0.1", - "@smithy/middleware-stack": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/node-http-handler": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/smithy-client": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-body-length-browser": "^1.0.1", - "@smithy/util-body-length-node": "^1.0.1", - "@smithy/util-defaults-mode-browser": "^1.0.1", - "@smithy/util-defaults-mode-node": "^1.0.1", - "@smithy/util-retry": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-sdk/core": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", + "integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==", + "dev": true, + "requires": { + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-env": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.363.0.tgz", - "integrity": "sha512-VAQ3zITT2Q0acht0HezouYnMFKZ2vIOa20X4zQA3WI0HfaP4D6ga6KaenbDcb/4VFiqfqiRHfdyXHP0ThcDRMA==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-sdk/credential-provider-http": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz", + "integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-ini": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.363.0.tgz", - "integrity": "sha512-ZYN+INoqyX5FVC3rqUxB6O8nOWkr0gHRRBm1suoOlmuFJ/WSlW/uUGthRBY5x1AQQnBF8cpdlxZzGHd41lFVNw==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz", + "integrity": "sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==", "dev": true, "requires": { - "@aws-sdk/credential-provider-env": "3.363.0", - "@aws-sdk/credential-provider-process": "3.363.0", - "@aws-sdk/credential-provider-sso": "3.363.0", - "@aws-sdk/credential-provider-web-identity": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-node": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.363.0.tgz", - "integrity": "sha512-C1qXFIN2yMxD6pGgug0vR1UhScOki6VqdzuBHzXZAGu7MOjvgHNdscEcb3CpWnITHaPL2ztkiw75T1sZ7oIgQg==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.637.0.tgz", + "integrity": "sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==", "dev": true, "requires": { - "@aws-sdk/credential-provider-env": "3.363.0", - "@aws-sdk/credential-provider-ini": "3.363.0", - "@aws-sdk/credential-provider-process": "3.363.0", - "@aws-sdk/credential-provider-sso": "3.363.0", - "@aws-sdk/credential-provider-web-identity": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-process": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.363.0.tgz", - "integrity": "sha512-fOKAINU7Rtj2T8pP13GdCt+u0Ml3gYynp8ki+1jMZIQ+Ju/MdDOqZpKMFKicMn3Z1ttUOgqr+grUdus6z8ceBQ==", + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-sso": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.363.0.tgz", - "integrity": "sha512-5RUZ5oM0lwZSo3EehT0dXggOjgtxFogpT3cZvoLGtIwrPBvm8jOQPXQUlaqCj10ThF1sYltEyukz/ovtDwYGew==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.637.0.tgz", + "integrity": "sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==", "dev": true, "requires": { - "@aws-sdk/client-sso": "3.363.0", - "@aws-sdk/token-providers": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/client-sso": "3.637.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/credential-provider-web-identity": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.363.0.tgz", - "integrity": "sha512-Z6w7fjgy79pAax580wdixbStQw10xfyZ+hOYLcPudoYFKjoNx0NQBejg5SwBzCF/HQL23Ksm9kDfbXDX9fkPhA==", + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/hash-blob-browser": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-blob-browser/-/hash-blob-browser-3.357.0.tgz", - "integrity": "sha512-RDd6UgrGHDmleTnXM9LRSSVa69euSAG2mlNhZMEDWk3OFseXVYqBDaqroVbQ01rM2UAe8MeBFchlV9OmxuVgvw==", - "dev": true, - "requires": { - "@aws-sdk/chunked-blob-reader": "3.310.0", - "@aws-sdk/types": "3.357.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/hash-stream-node": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/hash-stream-node/-/hash-stream-node-3.357.0.tgz", - "integrity": "sha512-KZjN1VAw1KHNp+xKVOWBGS+MpaYQTjZFD5f+7QQqW4TfbAkFFwIAEYIHq5Q8Gw+jVh0h61OrV/LyW3J2PVzc+w==", - "dev": true, - "requires": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/is-array-buffer": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.310.0.tgz", - "integrity": "sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==", - "dev": true, - "requires": { - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/md5-js": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/md5-js/-/md5-js-3.357.0.tgz", - "integrity": "sha512-to42sFAL7KgV/X9X40LLfEaNMHMGQL6/7mPMVCL/W2BZf3zw5OTl3lAaNyjXA+gO5Uo4lFEiQKAQVKNbr8b8Nw==", - "dev": true, - "requires": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-utf8": "3.310.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-bucket-endpoint": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.363.0.tgz", - "integrity": "sha512-kR8+0X50zslpzRW29q4JbpPMadE1z39ZfGwPaBLKpoWvSGt4x+75FaoK71TH7urPPoFyD2Y+XKGA6YRYTUNHSQ==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", + "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "@smithy/util-config-provider": "^1.0.1", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-expect-continue": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.363.0.tgz", - "integrity": "sha512-I88xneZp6jRwySmIl9uI7eZCcTsqRVnTDfUr1JiXt7zonqNNm80PVYMs6pwaw7t97ec1AQJcsONjuXZyCMnu5g==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", + "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-flexible-checksums": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.363.0.tgz", - "integrity": "sha512-FBYmrMRX01uNximNN0WLgpf97GN4xNTLaKsDlkjYRWKJ+J97ICkvLG0FcSu7+SNCpCdJJBeQ5tRVOPVpUu6nmA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", + "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", "dev": true, "requires": { - "@aws-crypto/crc32": "3.0.0", - "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.357.0", - "@smithy/is-array-buffer": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-sdk/types": "3.609.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-host-header": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.363.0.tgz", - "integrity": "sha512-FobpclDCf5Y1ueyJDmb9MqguAdPssNMlnqWQpujhYVABq69KHu73fSCWSauFPUrw7YOpV8kG1uagDF0POSxHzA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-location-constraint": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.363.0.tgz", - "integrity": "sha512-piNzpNNI/fChSGOZxcq/2msN2qFUSEAbhqs91zbcpv8CEPekVLc4W9laXCG764BEMyfG97ZU8MtzwHeMhELhBA==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-logger": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.363.0.tgz", - "integrity": "sha512-SSGgthScYnFGTOw8EzbkvquqweFmvn7uJihkpFekbtBNGC/jGOGO+8ziHjTQ8t/iI/YKubEwv+LMi0f77HKSEg==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-recursion-detection": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.363.0.tgz", - "integrity": "sha512-MWD/57QgI/N7fG8rtzDTUdSqNpYohQfgj9XCFAoVeI/bU4usrkOrew43L4smJG4XrDxlNT8lSJlDtd64tuiUZA==", + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-sdk-s3": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.363.0.tgz", - "integrity": "sha512-npC8vLCero+vULizrK0QPjNanWbgH4A/2Llc1nO8N005uvUe7co6WglILF2W3guZrFk/0uGEdX67OnLxUD97pw==", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.635.0.tgz", + "integrity": "sha512-RLdYJPEV4JL/7NBoFUs7VlP90X++5FlJdxHz0DzCjmiD3qCviKy+Cym3qg1gBgHwucs5XisuClxDrGokhAdTQw==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-arn-parser": "3.310.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/core": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/middleware-sdk-sts": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.363.0.tgz", - "integrity": "sha512-1yy2Ac50FO8BrODaw5bPWvVrRhaVLqXTFH6iHB+dJLPUkwtY5zLM3Mp+9Ilm7kME+r7oIB1wuO6ZB1Lf4ZszIw==", - "dev": true, - "requires": { - "@aws-sdk/middleware-signing": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/middleware-signing": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.363.0.tgz", - "integrity": "sha512-/7qia715pt9JKYIPDGu22WmdZxD8cfF/5xB+1kmILg7ZtjO0pPuTaCNJ7xiIuFd7Dn7JXp5lop08anX/GOhNRQ==", - "dev": true, - "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/signature-v4": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-ssec": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.363.0.tgz", - "integrity": "sha512-pN+QN1rMShYpJnTJSCIYnNRhD0S8xSZsTn6ThgcO559Xiwz5LMHFOfOXUCEyxtbVW5kMHLUh3w101AMUKae99A==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/middleware-user-agent": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.363.0.tgz", - "integrity": "sha512-ri8YaQvXP6odteVTMfxPqFR26Q0h9ejtqhUDv47P34FaKXedEM4nC6ix6o+5FEYj6l8syGyktftZ5O70NoEhug==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz", + "integrity": "sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@aws-sdk/util-endpoints": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/signature-v4-multi-region": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.363.0.tgz", - "integrity": "sha512-iWamQSpaBKg88LKuiUq8xO/7iyxJ+ORkA3qDhAwUqyTJOg87ma47yFf4ycCKqINnflc3AIGLGzBHnkBc4cMF5g==", + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.635.0.tgz", + "integrity": "sha512-J6QY4/invOkpogCHjSaDON1hF03viPpOnsrzVuCvJMmclS/iG62R4EY0wq1alYll0YmSdmKlpJwHMWwGtqK63Q==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/protocol-http": "^1.1.0", - "@smithy/signature-v4": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/token-providers": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.363.0.tgz", - "integrity": "sha512-6+0aJ1zugNgsMmhTtW2LBWxOVSaXCUk2q3xyTchSXkNzallYaRiZMRkieW+pKNntnu0g5H1T0zyfCO0tbXwxEA==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", "dev": true, "requires": { - "@aws-sdk/client-sso-oidc": "3.363.0", - "@aws-sdk/types": "3.357.0", - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/types": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.357.0.tgz", - "integrity": "sha512-/riCRaXg3p71BeWnShrai0y0QTdXcouPSM0Cn1olZbzTf7s71aLEewrc96qFrL70XhY4XvnxMpqQh+r43XIL3g==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", "dev": true, "requires": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/util-arn-parser": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.310.0.tgz", - "integrity": "sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==", + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", + "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/util-buffer-from": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.310.0.tgz", - "integrity": "sha512-i6LVeXFtGih5Zs8enLrt+ExXY92QV25jtEnTKHsmlFqFAuL3VBeod6boeMXkN2p9lbSVVQ1sAOOYZOHYbYkntw==", - "dev": true, - "requires": { - "@aws-sdk/is-array-buffer": "3.310.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/util-endpoints": { - "version": "3.357.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.357.0.tgz", - "integrity": "sha512-XHKyS5JClT9su9hDif715jpZiWHQF9gKZXER8tW0gOizU3R9cyWc9EsJ2BRhFNhi7nt/JF/CLUEc5qDx3ETbUw==", + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.637.0.tgz", + "integrity": "sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/util-locate-window": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz", - "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==", + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/util-user-agent-browser": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.363.0.tgz", - "integrity": "sha512-fk9ymBUIYbxiGm99Cn+kAAXmvMCWTf/cHAcB79oCXV4ELXdPa9lN5xQhZRFNxLUeXG4OAMEuCAUUuZEj8Fnc1Q==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/types": "^1.1.0", + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/util-user-agent-node": { - "version": "3.363.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.363.0.tgz", - "integrity": "sha512-Fli/dvgGA9hdnQUrYb1//wNSFlK2jAfdJcfNXA6SeBYzSeH5pVGYF4kXF0FCdnMA3Fef+Zn1zAP/hw9v8VJHWQ==", + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", "dev": true, "requires": { - "@aws-sdk/types": "3.357.0", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/util-utf8": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.310.0.tgz", - "integrity": "sha512-DnLfFT8uCO22uOJc0pt0DsSNau1GTisngBCDw8jQuWT5CqogMJu4b/uXmwEqfj8B3GX6Xsz8zOd6JpRlPftQoA==", - "dev": true, - "requires": { - "@aws-sdk/util-buffer-from": "3.310.0", - "tslib": "^2.5.0" - }, - "dependencies": { - "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", - "dev": true - } - } - }, - "@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "dev": true, - "requires": { - "tslib": "^2.3.1" - }, - "dependencies": { - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@aws-sdk/xml-builder": { - "version": "3.310.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.310.0.tgz", - "integrity": "sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==", + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", "dev": true, "requires": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } @@ -16350,806 +16644,966 @@ "integrity": "sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g==" }, "@smithy/abort-controller": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-1.0.1.tgz", - "integrity": "sha512-An6irzp9NCji2JtJHhrEFlDbxLwHd6c6Y9fq3ZeomyUR8BIXlGXVTxsemUSZVVgOq3166iYbYs/CrPAmgRSFLw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/chunked-blob-reader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz", + "integrity": "sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==", + "dev": true, + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/chunked-blob-reader-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz", + "integrity": "sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==", + "dev": true, + "requires": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/config-resolver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-1.0.1.tgz", - "integrity": "sha512-quj0xUiEVG/UHfY82EtthR/+S5/17p3IxXArC3NFSNqryMobWbG9oWgJy2s2cgUSVZLzxevjKKvxrilK7JEDaA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "@smithy/util-config-provider": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", + "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", + "dev": true, + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/credential-provider-imds": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-1.0.1.tgz", - "integrity": "sha512-hkRJoxVCh4CEt1zYOBElE+G/MV6lyx3g68hSJpesM4pwMT/bzEVo5E5XzXY+6dVq8yszeatWKbFuqCCBQte8tg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", + "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", "dev": true, "requires": { - "@smithy/node-config-provider": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/eventstream-codec": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-1.0.1.tgz", - "integrity": "sha512-cpcTXQEOEs2wEvIyxW/iTHJ2m0RVqoEOTjjWEXD6SY8Gcs3FCFP6E8MXadC098tdH5ctMIUXc8POXyMpxzGnjw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", "dev": true, "requires": { - "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^1.1.0", - "@smithy/util-hex-encoding": "^1.0.1", - "tslib": "^2.5.0" + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/eventstream-serde-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-1.0.1.tgz", - "integrity": "sha512-oc8vxe+AU2RzvXH/Ehh0TzM/Nsw3I3ywu7V3qaCzqdkBIntAwK9JGZqcSDsqTK0WxZKBRgFIEwopcuZ2slVnFQ==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.6.tgz", + "integrity": "sha512-2hM54UWQUOrki4BtsUI1WzmD13/SeaqT/AB3EUJKbcver/WgKNaiJ5y5F5XXuVe6UekffVzuUDrBZVAA3AWRpQ==", "dev": true, "requires": { - "@smithy/eventstream-serde-universal": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/eventstream-serde-config-resolver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-1.0.1.tgz", - "integrity": "sha512-TJwaXima0djnNY819utO1j93qZHaheFH1bhHxBkMrImtEOuXY48Tjma/L2m8swkIq8dy8jFC9hrYOkD0eYHkFA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/eventstream-serde-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-1.0.1.tgz", - "integrity": "sha512-JEj8w7IRs4l+kcwKxbv3pNuu8n7ORC4pMFrIOrM4rERzrRnI7vMNTRzvAPGYA53rqm/Y9tBA9dw4C+H6hLXcsA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.5.tgz", + "integrity": "sha512-+upXvnHNyZP095s11jF5dhGw/Ihzqwl5G+/KtMnoQOpdfC3B5HYCcDVG9EmgkhJMXJlM64PyN5gjJl0uXFQehQ==", "dev": true, "requires": { - "@smithy/eventstream-serde-universal": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/eventstream-serde-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-1.0.1.tgz", - "integrity": "sha512-c6m9DH7m6D2S93dof4wSxysaGSQdauO20TNcSePzrgHd4rkTnz5pqZ1a7Pt22q2SKf09SvTugq5cV2Sy4r8zHw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.5.tgz", + "integrity": "sha512-5u/nXbyoh1s4QxrvNre9V6vfyoLWuiVvvd5TlZjGThIikc3G+uNiG9uOTCWweSRjv1asdDIWK7nOmN7le4RYHQ==", "dev": true, "requires": { - "@smithy/eventstream-codec": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/fetch-http-handler": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-1.0.1.tgz", - "integrity": "sha512-/e2A8eOMk4FVZBQ0o6uF/ttLtFZcmsK5MIwDu1UE3crM4pCAIP19Ul8U9rdLlHhIu81X4AcJmSw55RDSpVRL/w==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", "dev": true, "requires": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/querystring-builder": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-base64": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/hash-blob-browser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", + "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", + "dev": true, + "requires": { + "@smithy/chunked-blob-reader": "^3.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/hash-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-1.0.1.tgz", - "integrity": "sha512-eCz08BySBcOjVObjbRAS/XMKUGY4ujnuS+GoWeEpzpCSKDnO8/YQ0rStRt4C0llRmhApizYc1tK9DiJwfvXcBg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "@smithy/util-buffer-from": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/hash-stream-node": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", + "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", + "dev": true, + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/invalid-dependency": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-1.0.1.tgz", - "integrity": "sha512-kib63GFlAzRn/wf8M0cRWrZA1cyOy5IvpTkLavCY782DPFMP0EaEeD6VrlNIOvD6ncf7uCJ68HqckhwK1qLT3g==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/is-array-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-1.0.1.tgz", - "integrity": "sha512-fHSTW70gANnzPYWNDcWkPXpp+QMbHhKozbQm/+Denkhp4gwSiPuAovWZRpJa9sXO+Q4dOnNzYN2max1vTCEroA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/md5-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz", + "integrity": "sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==", + "dev": true, + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/middleware-content-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-1.0.1.tgz", - "integrity": "sha512-vWWigayk5i2cFp9xPX5vdzHyK+P0t/xZ3Ovp4Ss+c8JQ1Hlq2kpJZVWtTKsmdfND5rVo5lu0kD5wgAMUCcmuhw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", + "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", "dev": true, "requires": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/middleware-endpoint": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-1.0.2.tgz", - "integrity": "sha512-F3CyXgjtDI4quGFkDmVNytt6KMwlzzeMxtopk6Edue4uKdKcMC1vUmoRS5xTbFzKDDp4XwpnEV7FshPaL3eCPw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", "dev": true, "requires": { - "@smithy/middleware-serde": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/url-parser": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/middleware-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-1.0.3.tgz", - "integrity": "sha512-ZRsjG8adtxQ456FULPqPFmWtrW44Fq8IgdQvQB+rC2RSho3OUzS+TiEIwb5Zs6rf2IoewITKtfdtsUZcxXO0ng==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", + "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", "dev": true, "requires": { - "@smithy/protocol-http": "^1.1.0", - "@smithy/service-error-classification": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/util-middleware": "^1.0.1", - "@smithy/util-retry": "^1.0.3", - "tslib": "^2.5.0", - "uuid": "^8.3.2" + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true } } }, "@smithy/middleware-serde": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-1.0.1.tgz", - "integrity": "sha512-bn5lWk8UUeXFCQfkrNErz5SbeNd+2hgYegHMLsOLPt4URDIsyREar6wMsdsR+8UCdgR5s8udG3Zalgc7puizIQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/middleware-stack": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-1.0.1.tgz", - "integrity": "sha512-T6+gsAO1JYamOJqmORCrByDeQ/NB+ggjHb33UDOgdX4xIjXz/FB/3UqHgQu6PL1cSFrK+i4oteDIwqARDs/Szw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", "dev": true, "requires": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/node-config-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-1.0.1.tgz", - "integrity": "sha512-FRxifH/J2SgOaVLihIqBFuGhiHR/NfzbZYp5nYO7BGgT/gc/f9nAuuRJcEy/hwO3aI6ThyG5apH4tGec6A2sCw==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", "dev": true, "requires": { - "@smithy/property-provider": "^1.0.1", - "@smithy/shared-ini-file-loader": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/node-http-handler": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-1.0.2.tgz", - "integrity": "sha512-PzPrGRSt3kNuruLCeR4ffJp57ZLVnIukMXVL3Ppr65ZoxiE+HBsOVAa/Z/T+4HzjCM6RaXnnmB8YKfsDjlb0iA==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", "dev": true, "requires": { - "@smithy/abort-controller": "^1.0.1", - "@smithy/protocol-http": "^1.1.0", - "@smithy/querystring-builder": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/property-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-1.0.1.tgz", - "integrity": "sha512-3EG/61Ls1MrgEaafpltXBJHSqFPqmTzEX7QKO7lOEHuYGmGYzZ08t1SsTgd1vM74z0IihoZyGPynZ7WmXKvTeg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/protocol-http": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.1.0.tgz", - "integrity": "sha512-H5y/kZOqfJSqRkwtcAoVbqONmhdXwSgYNJ1Glk5Ry8qlhVVy5qUzD9EklaCH8/XLnoCsLO/F/Giee8MIvaBRkg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/querystring-builder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-1.0.1.tgz", - "integrity": "sha512-J5Tzkw1PMtu01h6wl+tlN5vsyROmS6/z5lEfNlLo/L4ELHeVkQ4Q0PEIjDddPLfjVLCm8biQTESE5GCMixSRNQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "@smithy/util-uri-escape": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/querystring-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-1.0.1.tgz", - "integrity": "sha512-zauxdMc3cwxoLitI5DZqH7xN6Fk0mwRxrUMAETbav2j6Se2U0UGak/55rZcDg2yGzOURaLYi5iOm1gHr98P+Bw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/service-error-classification": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-1.0.2.tgz", - "integrity": "sha512-Q5CCuzYL5FGo6Rr/O+lZxXHm2hrRgbmMn8MgyjqZUWZg20COg20DuNtIbho2iht6CoB7jOpmpBqhWizLlzUZgg==", - "dev": true - }, - "@smithy/shared-ini-file-loader": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-1.0.1.tgz", - "integrity": "sha512-EztziuIPoNronENGqh+MWVKJErA4rJpaPzJCPukzBeEoG2USka0/q4B5Mr/1zszOnrb49fPNh4u3u5LfiH7QzA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", "dev": true, "requires": { - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0" + } + }, + "@smithy/shared-ini-file-loader": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", + "dev": true, + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/signature-v4": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-1.0.1.tgz", - "integrity": "sha512-2D69je14ou1vBTnAQeysSK4QVMm0j3WHS3MDg/DnHnFFcXRCzVl/xAARO7POD8+fpi4tMFPs8Z4hzo1Zw40L0Q==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", "dev": true, "requires": { - "@smithy/eventstream-codec": "^1.0.1", - "@smithy/is-array-buffer": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-hex-encoding": "^1.0.1", - "@smithy/util-middleware": "^1.0.1", - "@smithy/util-uri-escape": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/smithy-client": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-1.0.3.tgz", - "integrity": "sha512-Wh1mNP/1yUZK0uYkgCQ6NMxpBT3Fmc45TMdUfOlH1xD2zGYL7U4yDHFOhEZdi/suyjaelFobXB2p9pPIw6LjRQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", "dev": true, "requires": { - "@smithy/middleware-stack": "^1.0.1", - "@smithy/types": "^1.1.0", - "@smithy/util-stream": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.1.0.tgz", - "integrity": "sha512-KzmvisMmuwD2jZXuC9e65JrgsZM97y5NpDU7g347oB+Q+xQLU6hQZ5zFNNbEfwwOJHoOvEVTna+dk1h/lW7alw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/url-parser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-1.0.1.tgz", - "integrity": "sha512-33vWEtE6HzmwjEcEb4I58XMLRAchwPS93YhfDyXAXr1jwDCzfXmMayQwwpyW847rpWj0XJimxqia8q0z+k/ybw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", "dev": true, "requires": { - "@smithy/querystring-parser": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-base64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-1.0.1.tgz", - "integrity": "sha512-rJcpRi/yUi6TyCEkjdTH86/ExBuKlfctEXhG9/4gMJ3/cnPcHJJnr0mQ9evSEO+3DbpT/Nxq90bcTBdTIAmCig==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", "dev": true, "requires": { - "@smithy/util-buffer-from": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-body-length-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-1.0.1.tgz", - "integrity": "sha512-Pdp744fmF7E1NWoSb7256Anhm8eYoCubvosdMwXzOnHuPRVbDa15pKUz2027K3+jrfGpXo1r+MnDerajME1Osw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-body-length-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-1.0.1.tgz", - "integrity": "sha512-4PIHjDFwG07SNensAiVq/CJmubEVuwclWSYOTNtzBNTvxOeGLznvygkGYgPzS3erByT8C4S9JvnLYgtrsVV3nQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-buffer-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-1.0.1.tgz", - "integrity": "sha512-363N7Wq0ceUgE5lLe6kaR6GlJs2/m4r9V6bRMfIszb6P1FZbbRRM2FQYUWWPFSsRymm9mJL18b3fjiVsIvhDGg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", "dev": true, "requires": { - "@smithy/is-array-buffer": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-config-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-1.0.1.tgz", - "integrity": "sha512-4Qy38Oy5/q43MpTwCLV1P+7NeaOp4W2etQDxMjgEeRlOyGGNlgttn0syi4g2rVSukFVqQ6FbeRs5xbnFmS6kaQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-defaults-mode-browser": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-1.0.1.tgz", - "integrity": "sha512-/9ObwNch4Z/NJYfkO4AvqBWku60Ju+c2Ck32toPOLmWe/V6eI9FLn8C1abri+GxDRCkLIqvkaWU1lgZ3nWZIIw==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", + "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", "dev": true, "requires": { - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", "bowser": "^2.11.0", - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-defaults-mode-node": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-1.0.1.tgz", - "integrity": "sha512-XQM3KvqRLgv7bwAzVkXTITkOmcOINoG9icJiGT8FA0zV35lY5UvyIsg5kHw01xigQS8ufa/33AwG3ZoXip+V5g==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", + "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", "dev": true, "requires": { - "@smithy/config-resolver": "^1.0.1", - "@smithy/credential-provider-imds": "^1.0.1", - "@smithy/node-config-provider": "^1.0.1", - "@smithy/property-provider": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/config-resolver": "^3.0.5", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + } + } + }, + "@smithy/util-endpoints": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", + "dev": true, + "requires": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-hex-encoding": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-1.0.1.tgz", - "integrity": "sha512-FPTtMz/t02/rbfq5Pdll/TWUYP+GVFLCQNr+DgifrLzVRU0g8rdRjyFpDh8nPTdkDDusTTo9P1bepAYj68s0eA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-middleware": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-1.0.1.tgz", - "integrity": "sha512-u9akN3Zmbr0vZH4F+2iehG7cFg+3fvDfnvS/hhsXH4UHuhqiQ+ADefibnLzPoz1pooY7rvwaQ/TVHyJmZHdLdQ==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", "dev": true, "requires": { - "tslib": "^2.5.0" + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-retry": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-1.0.3.tgz", - "integrity": "sha512-gYQnZDD8I2XJFspVwUISyukjPWVikTzKR0IdG8hCWYPTpeULFl1o6yzXlT5SL63TBkuEYl0R1/93cdNtMiNnoA==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", "dev": true, "requires": { - "@smithy/service-error-classification": "^1.0.2", - "tslib": "^2.5.0" + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-1.0.1.tgz", - "integrity": "sha512-4aBCIz35aZAnt2Rbq341KrnUzGhWv2/Zu8HouJqYLvSWCzlrvsNCGlXP4e70Kjzcw8hSuuCNtdUICwQ5qUWLxg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", "dev": true, "requires": { - "@smithy/fetch-http-handler": "^1.0.1", - "@smithy/node-http-handler": "^1.0.2", - "@smithy/types": "^1.1.0", - "@smithy/util-base64": "^1.0.1", - "@smithy/util-buffer-from": "^1.0.1", - "@smithy/util-hex-encoding": "^1.0.1", - "@smithy/util-utf8": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-uri-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-1.0.1.tgz", - "integrity": "sha512-IJUrRnXKEIc+PKnU1XzTsIENVR+60jUDPBP3iWX/EvuuT3Xfob47x1FGUe2c3yMXNuU6ax8VDk27hL5LKNoehQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", "dev": true, "requires": { - "tslib": "^2.5.0" + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-utf8": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-1.0.1.tgz", - "integrity": "sha512-iX6XHpjh4DFEUIBSKp2tjy3pYnLQMsJ62zYi1BVAC0kobE6p8AVpiZnxsU3ZkgQatAsUaEspFHUZ7CL7oSqaPQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", "dev": true, "requires": { - "@smithy/util-buffer-from": "^1.0.1", - "tslib": "^2.5.0" + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } }, "@smithy/util-waiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-1.0.1.tgz", - "integrity": "sha512-dsn8O0s3pFIgxFzySLe1dv0w4tEQizEP6UqbgZ4r/Kar4n8pSdrPi6DJg8BzXwkwEIZpMtV4/nhSeGZ7HksDXA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", + "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", "dev": true, "requires": { - "@smithy/abort-controller": "^1.0.1", - "@smithy/types": "^1.1.0", - "tslib": "^2.5.0" + "@smithy/abort-controller": "^3.1.1", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" }, "dependencies": { "tslib": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", - "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true } } @@ -20148,9 +20602,9 @@ "dev": true }, "fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "dev": true, "requires": { "strnum": "^1.0.5" @@ -21743,12 +22197,12 @@ "dev": true }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -24334,9 +24788,9 @@ "dev": true }, "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true }, "uid-safe": { diff --git a/package.json b/package.json index b90f9adb..7eecc0c3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "peertube-plugin-livechat", "description": "PeerTube plugin livechat: create chat rooms for your Peertube lives! Comes with many features: federation, moderation tools, chat bot, chat persistence, OBS integration, ...", - "version": "10.3.3", + "version": "11.0.1", "license": "AGPL-3.0", "author": { "name": "John Livingston", @@ -73,7 +73,7 @@ "stylelint-config-recommended-scss": "^5.0.1", "stylelint-config-standard-scss": "^2.0.1", "svgo": "^2.8.0", - "typescript": "^4.3.5", + "typescript": "^4.9.5", "yaml": "^2.2.1" }, "engine": { diff --git a/prosody-modules/mod_firewall/README.markdown b/prosody-modules/mod_firewall/README.markdown new file mode 100644 index 00000000..2c9048f2 --- /dev/null +++ b/prosody-modules/mod_firewall/README.markdown @@ -0,0 +1,820 @@ +--- +labels: +- 'Stage-Alpha' +summary: 'A rule-based stanza filtering module' +rockspec: + build: + modules: + mod_firewall.actions: actions.lib.lua + mod_firewall.conditions: conditions.lib.lua + mod_firewall.definitions: definitions.lib.lua + mod_firewall.marks: marks.lib.lua + mod_firewall.test: test.lib.lua + copy_directories: + - scripts +--- + +------------------------------------------------------------------------ + +**Note:** mod\_firewall is in its very early stages. This documentation +is liable to change, and some described functionality may be missing, +incomplete or contain bugs. + +------------------------------------------------------------------------ + +Introduction +============ + +A firewall is an invaluable tool in the sysadmin's toolbox. However +while low-level firewalls such as iptables and pf are incredibly good at +what they do, they are generally not able to handle application-layer +rules. + +The goal of mod\_firewall is to provide similar services at the XMPP +layer. Based on rule scripts it can efficiently block, bounce, drop, +forward, copy, redirect stanzas and more! Furthermore all rules can be +applied and updated dynamically at runtime without restarting the +server. + +Details +======= + +mod\_firewall loads one or more scripts, and compiles these to Lua code +that reacts to stanzas flowing through Prosody. The firewall script +syntax is unusual, but straightforward. + +A firewall script is dominated by rules. Each rule has two parts: +conditions, and actions. When a stanza matches all of the conditions, +all of the actions are executed in order. + +Here is a simple example to block stanzas from spammer@example.com: + + FROM: spammer@example.com + DROP. + +FROM is a condition, and DROP is an action. This is about as simple as +it gets. How about heading to the other extreme? Let's demonstrate +something more complex that mod\_firewall can do for you: + + %ZONE myorganisation: staff.myorg.example, support.myorg.example + + ENTERING: myorganisation + KIND: message + TIME: 12am-9am, 5pm-12am, Saturday, Sunday + REPLY=Sorry, I am afraid our office is closed at the moment. If you need assistance, please call our 24-hour support line on 123-456-789. + +This rule will reply with a short message whenever someone tries to send +a message to someone at any of the hosts defined in the 'myorganisation' +outside of office hours. + +Specifying rule sets +-------------------- + +Firewall rules should be written into text files, e.g. `ruleset.pfw` file. +One or more rule files can be specified in the configuration using: + + firewall_scripts = { "path/to/ruleset.pfw", "path/to/ruleset2.pfw" } + +If multiple files are specified and they both add rules to the same [chains](#chains), +each file's rules will be processed in order, but the order of files is undefined. + +Reloading Prosody's configuration also reloads firewall rules. + +Make sure that `firewall_scripts` is in the global section of the configuration file +and not below a virtual host or a component - unless you want per-vhost +firewall rules. + +Conditions +---------- + +All conditions must come before any action in a rule block. The +condition name is followed by a colon (':'), and the value to test for. + +A condition can be preceded or followed by `NOT` to negate its match. +For example: + + NOT FROM: user@example.com + KIND NOT: message + +Some conditions do not take parameters, and these should end with just a +question mark, like: + + IN ROSTER? + +### Zones + +A 'zone' is one or more hosts or JIDs. It is possible to match when a +stanza is entering or leaving a zone, while at the same time not +matching traffic passing between JIDs in the same zone. + +Zones are defined at the top of a script with the following syntax (they +are not part of a rule block): + + %ZONE myzone: host1, host2, user@host3, foo.bar.example + +There is an automatic zone named `$local`, which automatically includes +all of the current server's active hosts (including components). It can +be used to match stanzas entering or leaving the current server. + +A host listed in a zone also matches all users on that host (but not +subdomains). + +The following zone-matching conditions are supported: + + Condition Matches + ------------ ------------------------------------------ + `ENTERING` When a stanza is entering the named zone + `LEAVING` When a stanza is leaving the named zone + +### Lists + +It is possible to create or load lists of strings for use in scripts. For +example, you might load a list of blocked JIDs, malware URLs or simple words +that you want to filter messages on. + + List type Example + ----------- ----------------------- + memory %LIST spammers: memory + file %LIST spammers: file:/etc/spammers.txt + http %LIST spammers: http://example.com/spammers.txt + +#### List types +##### memory + +``` +%LIST name: memory (limit: number) +``` + +A memory-only list, with an optional limit. Supports addition and removal of items by scripts. + +If a limit is provided, the oldest item will be discarded to make room for a new item if the +list is full. The limit is useful to prevent infinite memory growth on busy servers. + +##### file + +``` +%LIST name: file:/path/to/file (missing: string) +``` + +Reads a list from a file. The list can be added to and removed from by scripts, but +these changes do not persist between restarts. + +If the file is missing, an error will be raised. The optional 'missing' parameter can be set +to 'ignore' (e.g. `(missing: ignore)`) to ignore a missing file. + +##### http + +``` +%LIST name: http://example.com/ (ttl: number, pattern: pat, hash: sha1, checkcerts: when-sni) +``` + +Fetches a list from a HTTP or HTTPS URL. The following options are accepted: + + Option Description + ------- ----------- + ttl Seconds to cache the list for. After expiry, it will be refetched. Default 3600 (1 hour). + pattern Optional pattern used to extract list entries from the response. Default is to treat each line as a single item. + hash Optional hash to be applied to items before looking them up in the list, e.g. sha1 or sha256. + checkcert Whether to verify HTTPS certificates. May be "always", "never" or "when-sni". Default "when-sni". + +The "when-sni" default disables certificate verification when Prosody's HTTP client API doesn't support SNI, +as in Prosody 0.11.6 and earlier. + +#### CHECK LIST + +Checks whether a simple [expression](#expressions) is found in a given list. + +Example: + + %LIST blocked_jids: file:/etc/prosody/blocked_jids.txt + + # Rule to block presence subscription requests from blocked JIDs + KIND: presence + TYPE: subscribe + CHECK LIST: blocked_jids contains $<@from> + BOUNCE=policy-violation (Your JID is blocked) + +#### SCAN + +SCAN allows you to search inside a stanza for a given pattern, and check each result against a list. For example, +you could scan a message body for words and check if any of the words are found in a given list. + +Before using SCAN, you need to define a search location and a pattern. The search location uses the same 'path' +format as documented under the 'INSPECT' condition. Patterns can be any valid Lua pattern. + +To use the above example: + + # Define a search location called 'body' which fetches the text of the 'body' element + %SEARCH body: body# + # Define a pattern called 'word' which matches any sequence of letters + %PATTERN word: [A-Za-z]+ + # Finally, we also need our list of "bad" words: + %LIST badwords: file:/etc/prosody/bad_words.txt + + # Now we can use these to SCAN incoming stanzas + # If it finds a match, bounce the stanza + SCAN: body for word in badwords + BOUNCE=policy-violation (This word is not allowed!) + +#### COUNT + +COUNT is similar to SCAN, in that it uses a defined SEARCH and breaks it up according to a PATTERN. Then it +counts the number of results. + +For example, to block every message with more than one URL: + + # Define a search location called 'body' which fetches the text of the 'body' element + %SEARCH body: body# + # Define a pattern called 'url' which matches HTTP links + %PATTERN url: https?://%S+ + + COUNT: url in body > 1 + BOUNCE=policy-violation (Up to one HTTP URL is allowed in messages) + +### Stanza matching + + Condition Matches + ----------- ------------------------------------------------------------------------------------------------------------------------------------------------------------ + `KIND` The kind of stanza. May be 'message', 'presence' or 'iq' + `TYPE` The type of stanza. This varies depending on the kind of stanza. See 'Stanza types' below for more information. + `PAYLOAD` The stanza contains a child with the given namespace. Useful for determining the type of an iq request, or whether a message contains a certain extension. + `INSPECT` The node at the specified path exists or matches a given string. This allows you to look anywhere inside a stanza. See below for examples and more. + +#### Stanza types + + Stanza Valid types + ---------- ------------------------------------------------------------------------------------------ + iq get, set, result, error + presence *available*, unavailable, probe, subscribe, subscribed, unsubscribe, unsubscribed, error + message normal, chat, groupchat, headline, error + +**Note:** The type 'available' for presence does not actually appear in +the protocol. Available presence is signalled by the omission of a type. +Similarly, a message stanza with no type is equivalent to one of type +'normal'. mod\_firewall handles these cases for you automatically. + +#### Sender/recipient matching + + Condition Matches + --------------- ------------------------------------------------------- + `FROM` The JID in the 'from' attribute matches the given JID. + `TO` The JID in the 'to' attribute matches the given JID. + `TO SELF` The stanza is sent by any of a user's resources to their own bare JID. + `TO FULL JID` The stanza is addressed to a **valid** full JID on the local server (full JIDs include a resource at the end, and only exist for the lifetime of a single session, therefore the recipient **must be online**, or this check will not match). + `FROM FULL JID` The stanza is from a full JID (unlike `TO FULL JID` this check is on the format of the JID only). + +The TO and FROM conditions both accept wildcards in the JID when it is +enclosed in angle brackets ('\<...\>'). For example: + + # All users at example.com + FROM: <*>@example.com + + # The user 'admin' on any subdomain of example.com + FROM: admin@<*.example.com> + +You can also use [Lua's pattern +matching](http://www.lua.org/manual/5.1/manual.html#5.4.1) for more +powerful matching abilities. Patterns are a lightweight +regular-expression alternative. Simply contain the pattern in double +angle brackets. The pattern is automatically anchored at the start and +end (so it must match the entire portion of the JID). + + # Match admin@example.com, and admin1@example.com, etc. + FROM: <>@example.com + +**Note:** It is important to know that 'example.com' is a valid JID on +its own, and does **not** match 'user@example.com'. To efficiently match +domains we recommend defining them as [Zones](#zones). + + Condition Matches + ---------------- --------------------------------------------------------------- + `FROM_EXACTLY` The JID in the 'from' attribute exactly matches the given JID + `TO_EXACTLY` The JID in the 'to' attribute exactly matches the given JID + +These additional conditions do not support pattern matching, but are +useful to match the exact to/from address on a stanza. For example, if +no resource is specified then only bare JIDs will be matched. TO and FROM +match all resources if no resource is specified to match. + +**Note:** Some chains execute before Prosody has performed any +normalisation or validity checks on the to/from JIDs on an incoming +stanza. It is not advisable to perform access control or similar rules +on JIDs in these chains (see the [chain documentation](#chains) for more info). + +#### GeoIP matching + + Condition Matches + ---------------- -------------------------------------------------------------- + `FROM COUNTRY` Two or three letter country code looked up in GeoIP database + +This condition uses a GeoIP database to look up the origin country of +the IP attached to the current session. + +For example: + + # 3 letter country code + FROM COUNTRY: SWE + + # or 2 letter + FROM COUNTRY: SE + + # Explicit + FROM COUNTRY: code=SE + FROM COUNTRY: code3=SWE + +**Note:** This requires that the `lua-geoip` and `geoip-database` +packages are installed (on Debian, package names may differ on other +operating systems). + +#### INSPECT + +INSPECT takes a 'path' through the stanza to get a string (an attribute +value or text content). An example is the best way to explain. Let's +check that a user is not trying to register an account with the username +'admin'. This stanza comes from [XEP-0077: In-band +Registration](http://xmpp.org/extensions/xep-0077.html#example-4): + +``` xml + + + bill + Calliope + bard@shakespeare.lit + + +``` + + KIND: iq + TYPE: set + PAYLOAD: jabber:iq:register + INSPECT: {jabber:iq:register}query/username#=admin + BOUNCE=not-allowed (The username 'admin' is reserved.) + +That weird string deserves some explanation. It is a path, divided into +segments by '/'. Each segment describes an element by its name, +optionally prefixed by its namespace in curly braces ('{...}'). If the +path ends with a '\#' then the text content of the last element will be +returned. If the path ends with '@name' then the value of the attribute +'name' will be returned. + +You can use INSPECT to test for the existence of an element or attribute, +or you can check if it matches a specific value, e.g. by appending `=VALUE` +(like in the example above, that checks if the content of username is 'admin'). + +#### INSPECT comparison operators + +As well as checking for an exact string match, there are some other modifiers +you can apply to the comparison: + + Comparison Matches when + ------------- ------------------------------------------------------- + `=` The value is exactly the given string. + `/=` The value is or *contains* the given string (e.g. `/=admin` would match `administrator` or `myadmin`). + `~=` The value matches the given [Lua pattern](https://www.lua.org/manual/5.2/manual.html#6.4.1). + +Finally, if the comparison operator is preceded by a `$` character, [expressions](#expressions) +will be interpreted in the string following the comparison operator. + +e.g. `INSPECT: {jabber:iq:register}query/username}$/=$(session.host)` would match +if the username of an account registration contained the session's current hostname +somewhere in it. + +#### INSPECT performance + +INSPECT can be somewhat slower than the other stanza matching conditions. To +minimise performance impact, always place it below other faster +condition checks where possible (e.g. in the example above we first checked KIND, +TYPE and PAYLOAD matched what we wanted before reaching the INSPECT rule). + +### Roster + +These conditions access the roster of the recipient (only). Therefore they cannot (currently) +be used in some [chains](#chains), such as for outgoing messages (the recipient may be on another server). + +Performance note: these checks can potentially cause storage access (especially if the recipient +is currently offline), so you may want to limit their use in high-traffic situations, and place rules +containing a roster check below other rules (such as a rate limiter). The storage access is +performed unconditionally just before evaluation of the first rule that contains a roster-based +condition, even if earlier conditions in that rule do not match. + +#### IN ROSTER + +Tests whether the sender is in the recipient's roster. + + IN ROSTER? + +#### IN ROSTER GROUP + +Tests whether the sender is in the recipient's roster, and in the named group. + + IN ROSTER GROUP: Friends + +#### SUBSCRIBED + +Tests whether the recipient is subscribed to the sender, ie will receive +presence updates from them. + +Note that this *does* work, regardless of direction and which [chain](#chain) is +used, since both the sender and the recipient will have mirrored roster +entries. + +### Groups + +Using Prosody's mod\_groups it is possible to define groups of users on the server. You can +match based on these groups in firewall rules. + + Condition Matches + ----------------- ---------------------------- + `FROM GROUP` When the stanza is being sent from a member of the named group + `TO GROUP` When the stanza is being sent to a member of the named group + `CROSSING GROUPS` When the stanza is being sent between users of different named groups + +#### CROSSING GROUPS + +The `CROSSING GROUPS` condition takes a comma-separated list of groups to check. If the +sender and recipient are not in the same group (only the listed groups are checked), then the +this condition matches and the stanza is deemed to be crossing between groups. + +For example, if you had three groups: Engineering, Marketing and Employees. All users are +members of the 'Employees' group, and the others are for employees of the named department only. + +To prevent employees in the marketing department from communicating with engineers, you could use +the following rule: + +``` +CROSSING GROUPS: Marketing, Engineering +BOUNCE=policy-violation (no communication between these groups is allowed!) +``` + +This works, even though both the users are in the 'Employees' group, because that group is not listed +in the condition. + +In the above example, a user who is member of both groups is not restricted. + +#### SENT DIRECTED PRESENCE TO SENDER + +This condition matches if the recipient of a stanza has previously sent directed presence to the sender of the stanza. This +is often done in XMPP to exchange presence information with JIDs that are not on your roster, such as MUC rooms. + +This condition does not take a parameter - end the condition name with a question mark: + + # Rule to bounce messages from senders not in the roster who haven't been sent directed presence + NOT IN ROSTER? + NOT SENT DIRECTED PRESENCE TO SENDER? + BOUNCE=service-unavailable + +### Permissions + +Rules can consult Prosody's internal role and permissions system to check whether a certain action may +be performed. The acting entity, their role, and appropriate context is automatically inferred. All you +need to do is provide the identifier of the permission that should be checked. + + Condition Description + ----------------------- -------------------------------------------------------------------- + `MAY=permission` Checks whether 'permission' is allowed in the current context. + +As with all other conditions, `MAY` can be combined with `NOT` to negate the result of the check. + +Example, blocking outgoing stanzas from users with roles that do not allow the 'xmpp:federate' permission: + +``` +::deliver_remote +MAY NOT: xmpp:federate +BOUNCE=policy-violation (You are not allowed access to the federation) +``` + +### Roles + + Condition Matches + ---------------- ------------------------------------------------------------------------------------- + `TO ROLE` When the recipient JID of the stanza has the named role + `FROM ROLE` When the sender JID of the stanza has the named role + +**Note:** In most cases, you should avoid checking for specific roles, and instead check for +permissions granted by those roles (using the 'MAY' condition). + +### Admins + +**Deprecated:** These conditions should no longer be used. Prefer 'MAY', 'TO ROLE' or 'FROM ROLE'. + +Prosody allows certain JIDs to be declared as administrators of a host, component or the whole server. + + Condition Matches + ---------------- ------------------------------------------------------------------------------------- + `TO ADMIN` When the recipient of the stanza is admin of the current host + `FROM ADMIN` When the sender of the stanza is admin of the current host + `FROM ADMIN OF` When the sender of the stanza is an admin of the named host on the current server + `TO ADMIN OF` When the recipient of the stanza is an admin of the named host on the current server + +### Time and date + +#### TIME + +Matches stanzas sent during certain time periods. + + Condition Matches + ----------- ------------------------------------------------------------------------------------------- + TIME When the current server local time is within one of the comma-separated time ranges given + + TIME: 10pm-6am, 14:00-15:00 + REPLY=Zzzz. + +#### DAY + +It is also possible to match only on certain days of the week. + + Condition Matches + ----------- ----------------------------------------------------------------------------------------------------- + DAY When the current day matches one, or falls within a rage, in the given comma-separated list of days + +Example: + + DAY: Sat-Sun, Wednesday + REPLY=Sorry, I'm out enjoying life! + +All times and dates are handled in the server's local time. + +### Rate-limiting + +It is possible to selectively rate-limit stanzas, and use rules to +decide what to do with stanzas when over the limit. + +First, you must define any rate limits that you are going to use in your +script. Here we create a limiter called 'normal' that will allow 2 +stanzas per second, and then we define a rule to bounce messages when +over this limit. Note that the `RATE` definition is not part of a rule +(multiple rules can share the same limiter). + + %RATE normal: 2 (burst 3) + + KIND: message + LIMIT: normal + BOUNCE=policy-violation (Sending too fast!) + +The 'burst' parameter on the rate limit allows you to spread the limit +check over a given time period. For example the definition shown above +will allow the limit to be temporarily surpassed, as long as it is +within the limit after 3 seconds. You will almost always want to specify +a burst factor. + +Both the rate and the burst can be fractional values. For example a rate +of 0.1 means only one event is allowed every 10 seconds. + +The LIMIT condition actually does two things; first it counts against +the given limiter, and then it checks to see if the limiter over its +limit yet. If it is, the condition matches, otherwise it will not. + + Condition Matches + ----------- -------------------------------------------------------------------------------------------------- + `LIMIT` When the named limit is 'used up'. Using this condition automatically counts against that limit. + +**Note:** Reloading mod\_firewall resets the current state of any +limiters. + +#### Dynamic limits + +Sometimes you may want to have multiple throttles in a single condition, using some property of the session or stanza +to determine which throttle to use. For example, you might have a limit for incoming stanzas, but you want to limit by +sending JID, instead of all incoming stanzas sharing the same limit. + +You can use the 'on' keyword for this, like so: + + LIMIT: normal on EXPRESSION + +For more information on [expressions](#expressions), see the section later in this document. + +Each value of 'EXPRESSION' has to be tracked individually in a table, which uses a small amount of memory. To prevent +memory exhaustion, the number of tracked values is limited to 1000 by default. You can override this by setting the +maximum number of table entries when you define the rate: + + %RATE normal: 2 (burst 3) (entries 4096) + +Old values are automatically removed from the tracking table. However if the tracking table becomes full, new entries +will be rejected - it will behave as if the rate limit was reached, even for values that have not been seen before. Since +this opens up a potential denial of service (innocent users may be affected if malicious users can fill up the tracking +table within the limit period). You can choose to instead "fail open", and allow the rate limit to be temporarily bypassed +when the table is full. To choose this behaviour, add `(allow overflow)` to the RATE definition. + +### Session marking + +It is possible to 'mark' sessions (see the MARK_ORIGIN action below). To match stanzas from marked sessions, use the +`ORIGIN_MARKED` condition. + + Condition Description + ------------------------------- --------------------------------------------------------------- + ORIGIN MARKED: markname Matches if the origin has been marked with 'markname'. + ORIGIN MARKED: markname (Xs) Matches if the origin has been marked with 'markname' within the past X seconds. + +Example usage: + + # This rule drops messages from sessions that have been marked as spammers in the past hour + ORIGIN MARKED: spammer (3600s) + DROP. + + # This rule marks the origin session as a spammer if they send a message to a honeypot JID + KIND: message + TO: honeypot@example.com + MARK ORIGIN=spammer + +Actions +------- + +Actions come after all conditions in a rule block. There must be at +least one action, though conditions are optional. + +An action without parameters ends with a full-stop/period ('.'), and one +with parameters uses an equals sign ('='): + + # An action with no parameters: + DROP. + + # An action with a parameter: + REPLY=Hello, this is a reply. + +### Route modification + +The following common actions modify the stanza's route in some way. These +rules will halt further processing of the stanza - no further actions will be +executed, and no further rules will be checked. + + Action Description + ----------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------- + `PASS.` Stop executing actions and rules on this stanza, and let it through this chain and any calling chains. + `DROP.` Stop executing actions and rules on this stanza, and discard it. + `DEFAULT.` Stop executing actions and rules on this stanza, prevent any other scripts/modules from handling it, to trigger the appropriate default "unhandled stanza" behaviour. Do not use in custom chains (it is treated as PASS). + `REDIRECT=jid` Redirect the stanza to the given JID. + `BOUNCE.` Bounce the stanza with the default error (usually service-unavailable) + `BOUNCE=error` Bounce the stanza with the given error (MUST be a defined XMPP stanza error, see [RFC6120](http://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions). + `BOUNCE=error (text)` As above, but include the supplied human-readable text with a description of the error + +**Note:** It is incorrect behaviour to reply to an 'error' stanza with another error, so BOUNCE will simply act the same as 'DROP' for stanzas that should not be bounced (error stanzas and iq results). + +### Replying and forwarding + +These actions cause a new stanza to be generated and sent somewhere. +Processing of the original stanza will continue beyond these actions. + + Action Description + ------------------------ --------------------------------------------------------------------------------------------------------------------------------------------------------- + `REPLY=text` Reply to the stanza (assumed to be a message) with the given text. + `COPY=jid` Make a copy of the stanza and send the copy to the specified JID. The copied stanza flows through Prosody's routing code, and as such is affected by firewall rules. Be careful to avoid loops. + `FORWARD=jid` Forward a copy of the stanza to the given JID (using XEP-0297). The stanza will be sent from the current host's JID. + +### Reporting + + Action Description + ------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------- + `REPORT TO=jid [reason] [text]` Forwards the full stanza to `jid` with a XEP-0377 abuse report attached. + +Only the `jid` is mandatory. The `reason` parameter should be either `abuse`, `spam` or a custom URI. If not specified, it defaults to `abuse`. +After the reason, some human-readable text may be included to explain the report. + +Example: + +``` +KIND: message +TO: honeypot@example.com +REPORT TO=antispam.example.com spam Caught by the honeypot! +DROP. +``` + +### Stanza modification + +These actions make it possible to modify the content and structure of a +stanza. + + Action Description + ------------------------ ------------------------------------------------------------------------ + `STRIP=name` Remove any child elements with the given name in the default namespace + `STRIP=name namespace` Remove any child elements with the given name and the given namespace + `INJECT=xml` Inject the given XML into the stanza as a child element + +### Sessions + +It is possible to mark sessions, and then use these marks to match rules later on. + + Action Description + ------------------------ -------------------------------------------------------------------------- + `MARK ORIGIN=mark` Marks the originating session with the given flag. + `UNMARK ORIGIN=mark` Removes the given mark from the origin session (if it is set). + +**Note:** Marks apply to sessions, not JIDs. E.g. if marking in a rule that matches a stanza received +over s2s, it is the s2s session that is marked. + +It is possible to have multiple marks on an origin at any given time. + +### Informational + + Action Description + --------------- ------------------------------------------------------------------------------------------------------------------------ + `LOG=message` Logs the given message to Prosody's log file. Optionally prefix it with a log level in square brackets, e.g. `[debug]` + +You can include [expressions](#expressions) in log messages, using `$(...)` syntax. For example, to log the stanza that matched the rule, +you can use `$(stanza)`, or to log just the top tag of the stanza, use `$(stanza:top_tag())`. To fetch the sender JID, use `$(stanza.attr.from)`. + +Example: + + # Log all stanzas to user@example.com: + TO: user@example.com + LOG=[debug] User received: $(stanza) + +More info about expressions can be found below. + +Chains +------ + +Rules are grouped into "chains", which are injected at particular points in Prosody's routing code. + +Available built-in chains are: + + Chain Description + -------------- ------------------------------------------------------------------------------------------- + deliver Applies to stanzas delivered to local recipients (regardless of the stanza's origin) + deliver_remote Applies to stanzas delivered to remote recipients (just before they leave the local server) + preroute Applies to incoming stanzas from local users, before any routing rules are applied + +A chain is begun by a line `::name` where 'name' is the name of the chain you want the following rules to be +inserted into. If no chain is specified, rules are put into the 'deliver' chain. + +It is possible to create custom chains (useful with the `JUMP CHAIN` action described below). User-created +chains must begin with "user/", e.g. "user/spam_filtering". + +Example of chain use: + + # example.com's firewall script + + # This line is optional, because 'deliver' is the default chain anyway: + ::deliver + + # This rule matches any stanzas delivered to our local user bob: + TO: bob@example.com + DROP. + + # Oops! This rule will never match, because alice is not a local user, + # and only stanzas to local users go through the 'deliver' chain: + TO: alice@remote.example.com + DROP. + + # Create a 'preroute' chain of rules (matched for incoming stanzas from local clients): + ::preroute + # These rules are matched for outgoing stanzas from local clients + + # This will match any stanzas sent to alice from a local user: + TO: alice@remote.example.com + DROP. + + Action Description + ------------------------ ------------------------------------------------------------------------ + `JUMP CHAIN=name` Switches chains, and passes the stanza through the rules in chain 'name'. If the new chain causes the stanza to be dropped/redirected, the current chain halts further processing. + `RETURN.` Stops executing the current chain and returns to the parent chain. For built-in chains, equivalent to PASS. RETURN is implicit at the end of every chain. + +It is possible to jump to chains defined by other scripts and modules. + +Expressions +----------- + +Some conditions and actions in rules support "expressions" in their parameters (their documentation will indicate if this is the case). Most parameters +are static once the firewall script is loaded and compiled internally, however parameters that allow expressions can be dynamically calculated when a +rule is being run. + +There are two kinds of expression that you can use: stanza expressions, and code expressions. + +### Stanza expressions + +Stanza expressions are of the form `$<...>`, where `...` is a stanza path. For syntax of stanza paths, see the documentation for the 'INSPECT' condition +above. + +Example: + + LOG=Matched a stanza from $<@from> to $<@to> + +There are built in functions which can be applied to the output of a stanza expression, by appending the pipe ('|') operator, followed by the function +name. These functions are: + + Function Description + ------------ --------------------------------------- + bare Given a JID, strip any resource + node Return the node ('user part') of a JID + host Return the host ('domain') part of a JID + resource Return the resource part of a JID + +For example, to apply a rate limit to stanzas per sender domain: + + LIMIT normal on $<@from|host> + +If the path does not match (e.g. the element isn't found, or the attribute doesn't exist) or any of the functions fail to produce an output (e.g. an invalid +JID was passed to a function that only handles valid JIDs) the expression will return the text ``. You can override this by ending the expression +with a double pipe ('||') followed by a quoted string to use as a default instead. E.g. to default to the string "normal" when there is no 'type' attribute: + + LOG=Stanza type is $<@type||"normal"> + +### Code expressions + +Code expressions use `$(...)` syntax. Code expressions are powerful, and allow unconstrained access to Prosody's internal environment. Therefore +code expressions are typically for advanced use-cases only. You may want to refer to Prosody's [developer documentation](https://prosody.im/doc/developers) +for more information. In particular, within code expressions you may access the 'session' object, which is the session object of the origin of the stanza, +and the 'stanza' object, which is the stanza being considered within the current rule. Whatever value the expression returns will be converted to a string. + +Example to limit stanzas per session type: + + LIMIT: normal on $(session.type) diff --git a/prosody-modules/mod_firewall/actions.lib.lua b/prosody-modules/mod_firewall/actions.lib.lua new file mode 100644 index 00000000..961a5c0e --- /dev/null +++ b/prosody-modules/mod_firewall/actions.lib.lua @@ -0,0 +1,280 @@ +local unpack = table.unpack or unpack; + +local interpolation = require "util.interpolation"; +local template = interpolation.new("%b$$", function (s) return ("%q"):format(s) end); + +--luacheck: globals meta idsafe +local action_handlers = {}; + + +-- Takes an XML string and returns a code string that builds that stanza +-- using st.stanza() +local function compile_xml(data) + local code = {}; + local first, short_close = true, nil; + for tagline, text in data:gmatch("<([^>]+)>([^<]*)") do + if tagline:sub(-1,-1) == "/" then + tagline = tagline:sub(1, -2); + short_close = true; + end + if tagline:sub(1,1) == "/" then + code[#code+1] = (":up()"); + else + local name, attr = tagline:match("^(%S*)%s*(.*)$"); + local attr_str = {}; + for k, _, v in attr:gmatch("(%S+)=([\"'])([^%2]-)%2") do + if #attr_str == 0 then + table.insert(attr_str, ", { "); + else + table.insert(attr_str, ", "); + end + if k:find("^%a%w*$") then + table.insert(attr_str, string.format("%s = %q", k, v)); + else + table.insert(attr_str, string.format("[%q] = %q", k, v)); + end + end + if #attr_str > 0 then + table.insert(attr_str, " }"); + end + if first then + code[#code+1] = (string.format("st.stanza(%q %s)", name, #attr_str>0 and table.concat(attr_str) or ", nil")); + first = nil; + else + code[#code+1] = (string.format(":tag(%q%s)", name, table.concat(attr_str))); + end + end + if text and text:find("%S") then + code[#code+1] = (string.format(":text(%q)", text)); + elseif short_close then + short_close = nil; + code[#code+1] = (":up()"); + end + end + return table.concat(code, ""); +end + +function action_handlers.PASS() + return "do return pass_return end" +end + +function action_handlers.DROP() + return "do return true end"; +end + +function action_handlers.DEFAULT() + return "do return false end"; +end + +function action_handlers.RETURN() + return "do return end" +end + +function action_handlers.STRIP(tag_desc) + local code = {}; + local name, xmlns = tag_desc:match("^(%S+) (.+)$"); + if not name then + name, xmlns = tag_desc, nil; + end + if name == "*" then + name = nil; + end + code[#code+1] = ("local stanza_xmlns = stanza.attr.xmlns; "); + code[#code+1] = "stanza:maptags(function (tag) if "; + if name then + code[#code+1] = ("tag.name == %q and "):format(name); + end + if xmlns then + code[#code+1] = ("(tag.attr.xmlns or stanza_xmlns) == %q "):format(xmlns); + else + code[#code+1] = ("tag.attr.xmlns == stanza_xmlns "); + end + code[#code+1] = "then return nil; end return tag; end );"; + return table.concat(code); +end + +function action_handlers.INJECT(tag) + return "stanza:add_child("..compile_xml(tag)..")", { "st" }; +end + +local error_types = { + ["bad-request"] = "modify"; + ["conflict"] = "cancel"; + ["feature-not-implemented"] = "cancel"; + ["forbidden"] = "auth"; + ["gone"] = "cancel"; + ["internal-server-error"] = "cancel"; + ["item-not-found"] = "cancel"; + ["jid-malformed"] = "modify"; + ["not-acceptable"] = "modify"; + ["not-allowed"] = "cancel"; + ["not-authorized"] = "auth"; + ["payment-required"] = "auth"; + ["policy-violation"] = "modify"; + ["recipient-unavailable"] = "wait"; + ["redirect"] = "modify"; + ["registration-required"] = "auth"; + ["remote-server-not-found"] = "cancel"; + ["remote-server-timeout"] = "wait"; + ["resource-constraint"] = "wait"; + ["service-unavailable"] = "cancel"; + ["subscription-required"] = "auth"; + ["undefined-condition"] = "cancel"; + ["unexpected-request"] = "wait"; +}; + + +local function route_modify(make_new, to, drop) + local reroute, deps = "session.send(newstanza)", { "st" }; + if to then + reroute = ("newstanza.attr.to = %q; core_post_stanza(session, newstanza)"):format(to); + deps[#deps+1] = "core_post_stanza"; + end + return ([[do local newstanza = st.%s; %s;%s end]]) + :format(make_new, reroute, drop and " return true" or ""), deps; +end + +function action_handlers.BOUNCE(with) + local error = with and with:match("^%S+") or "service-unavailable"; + local error_type = error:match(":(%S+)"); + if not error_type then + error_type = error_types[error] or "cancel"; + else + error = error:match("^[^:]+"); + end + error, error_type = string.format("%q", error), string.format("%q", error_type); + local text = with and with:match(" %((.+)%)$"); + if text then + text = string.format("%q", text); + else + text = "nil"; + end + local route_modify_code, deps = route_modify(("error_reply(stanza, %s, %s, %s)"):format(error_type, error, text), nil, true); + deps[#deps+1] = "type"; + deps[#deps+1] = "name"; + return [[if type == "error" or (name == "iq" and type == "result") then return true; end -- Don't reply to 'error' stanzas, or iq results + ]]..route_modify_code, deps; +end + +function action_handlers.REDIRECT(where) + return route_modify("clone(stanza)", where, true); +end + +function action_handlers.COPY(where) + return route_modify("clone(stanza)", where, false); +end + +function action_handlers.REPLY(with) + return route_modify(("reply(stanza):body(%q)"):format(with)); +end + +function action_handlers.FORWARD(where) + local code = [[ + local newstanza = st.stanza("message", { to = %q, from = current_host }):tag("forwarded", { xmlns = "urn:xmpp:forward:0" }); + local tmp_stanza = st.clone(stanza); tmp_stanza.attr.xmlns = "jabber:client"; newstanza:add_child(tmp_stanza); + core_post_stanza(session, newstanza); + ]]; + return code:format(where), { "core_post_stanza", "current_host" }; +end + +function action_handlers.LOG(string) + local level = string:match("^%[(%a+)%]") or "info"; + string = string:gsub("^%[%a+%] ?", ""); + local meta_deps = {}; + local code = meta(("(session.log or log)(%q, '%%s', %q);"):format(level, string), meta_deps); + return code, meta_deps; +end + +function action_handlers.RULEDEP(dep) + return "", { dep }; +end + +function action_handlers.EVENT(name) + return ("fire_event(%q, event)"):format(name); +end + +function action_handlers.JUMP_EVENT(name) + return ("do return fire_event(%q, event); end"):format(name); +end + +function action_handlers.JUMP_CHAIN(name) + return template([[do + local ret = fire_event($chain_event$, event); + if ret ~= nil then + if ret == false then + log("debug", "Chain %q accepted stanza (ret %s)", $chain_name$, tostring(ret)); + return pass_return; + end + log("debug", "Chain %q rejected stanza (ret %s)", $chain_name$, tostring(ret)); + return ret; + end + end]], { chain_event = "firewall/chains/"..name, chain_name = name }); +end + +function action_handlers.MARK_ORIGIN(name) + return [[session.firewall_marked_]]..idsafe(name)..[[ = current_timestamp;]], { "timestamp" }; +end + +function action_handlers.UNMARK_ORIGIN(name) + return [[session.firewall_marked_]]..idsafe(name)..[[ = nil;]] +end + +function action_handlers.MARK_USER(name) + return ([[if session.username and session.host == current_host then + fire_event("firewall/marked/user", { + username = session.username; + mark = %q; + timestamp = current_timestamp; + }); + else + log("warn", "Attempt to MARK a remote user - only local users may be marked"); + end]]):format(assert(idsafe(name), "Invalid characters in mark name: "..name)), { + "current_host"; + "timestamp"; + }; +end + +function action_handlers.UNMARK_USER(name) + return ([[if session.username and session.host == current_host then + fire_event("firewall/unmarked/user", { + username = session.username; + mark = %q; + }); + else + log("warn", "Attempt to UNMARK a remote user - only local users may be marked"); + end]]):format(assert(idsafe(name), "Invalid characters in mark name: "..name)); +end + +function action_handlers.ADD_TO(spec) + local list_name, value = spec:match("(%S+) (.+)"); + local meta_deps = {}; + value = meta(("%q"):format(value), meta_deps); + return ("list_%s:add(%s);"):format(list_name, value), { "list:"..list_name, unpack(meta_deps) }; +end + +function action_handlers.UNSUBSCRIBE_SENDER() + return "rostermanager.unsubscribed(to_node, to_host, bare_from);\ + rostermanager.roster_push(to_node, to_host, bare_from);\ + core_post_stanza(session, st.presence({ from = bare_to, to = bare_from, type = \"unsubscribed\" }));", + { "rostermanager", "core_post_stanza", "st", "split_to", "bare_to", "bare_from" }; +end + +function action_handlers.REPORT_TO(spec) + local where, reason, text = spec:match("^%s*(%S+) *(%S*) *(.*)$"); + if reason == "spam" then + reason = "urn:xmpp:reporting:spam"; + elseif reason == "abuse" or not reason or reason == "" then + reason = "urn:xmpp:reporting:abuse"; + end + local code = [[ + local newstanza = st.stanza("message", { to = %q, from = current_host, id = new_short_id() }):tag("forwarded", { xmlns = "urn:xmpp:forward:0" }); + local tmp_stanza = st.clone(stanza); tmp_stanza.attr.xmlns = "jabber:client"; newstanza:add_child(tmp_stanza):up(); + newstanza:tag("report", { xmlns = "urn:xmpp:reporting:1", reason = %q }) + do local text = %q; if text ~= "" then newstanza:text_tag("text", text); end end + newstanza:up(); + core_post_stanza(session, newstanza); + ]]; + return code:format(where, reason, text), { "core_post_stanza", "current_host", "st", "new_short_id" }; +end + +return action_handlers; diff --git a/prosody-modules/mod_firewall/conditions.lib.lua b/prosody-modules/mod_firewall/conditions.lib.lua new file mode 100644 index 00000000..718089de --- /dev/null +++ b/prosody-modules/mod_firewall/conditions.lib.lua @@ -0,0 +1,410 @@ +--luacheck: globals meta idsafe +local condition_handlers = {}; + +local jid = require "util.jid"; +local unpack = table.unpack or unpack; + +-- Helper to convert user-input strings (yes/true//no/false) to a bool +local function string_to_boolean(s) + s = s:lower(); + return s == "yes" or s == "true"; +end + +-- Return a code string for a condition that checks whether the contents +-- of variable with the name 'name' matches any of the values in the +-- comma/space/pipe delimited list 'values'. +local function compile_comparison_list(name, values) + local conditions = {}; + for value in values:gmatch("[^%s,|]+") do + table.insert(conditions, ("%s == %q"):format(name, value)); + end + return table.concat(conditions, " or "); +end + +function condition_handlers.KIND(kind) + assert(kind, "Expected stanza kind to match against"); + return compile_comparison_list("name", kind), { "name" }; +end + +local wildcard_equivs = { ["*"] = ".*", ["?"] = "." }; + +local function compile_jid_match_part(part, match) + if not match then + return part.." == nil"; + end + local pattern = match:match("^<(.*)>$"); + if pattern then + if pattern == "*" then + return part; + end + if pattern:find("^<.*>$") then + pattern = pattern:match("^<(.*)>$"); + else + pattern = pattern:gsub("%p", "%%%0"):gsub("%%(%p)", wildcard_equivs); + end + return ("(%s and %s:find(%q))"):format(part, part, "^"..pattern.."$"); + else + return ("%s == %q"):format(part, match); + end +end + +local function compile_jid_match(which, match_jid) + local match_node, match_host, match_resource = jid.split(match_jid); + local conditions = {}; + conditions[#conditions+1] = compile_jid_match_part(which.."_node", match_node); + conditions[#conditions+1] = compile_jid_match_part(which.."_host", match_host); + if match_resource then + conditions[#conditions+1] = compile_jid_match_part(which.."_resource", match_resource); + end + return table.concat(conditions, " and "); +end + +function condition_handlers.TO(to) + return compile_jid_match("to", to), { "split_to" }; +end + +function condition_handlers.FROM(from) + return compile_jid_match("from", from), { "split_from" }; +end + +function condition_handlers.FROM_FULL_JID() + return "not "..compile_jid_match_part("from_resource", nil), { "split_from" }; +end + +function condition_handlers.FROM_EXACTLY(from) + local metadeps = {}; + return ("from == %s"):format(metaq(from, metadeps)), { "from", unpack(metadeps) }; +end + +function condition_handlers.TO_EXACTLY(to) + local metadeps = {}; + return ("to == %s"):format(metaq(to, metadeps)), { "to", unpack(metadeps) }; +end + +function condition_handlers.TO_SELF() + -- Intentionally not using 'to' here, as that defaults to bare JID when nil + return ("stanza.attr.to == nil"); +end + +function condition_handlers.TYPE(type) + assert(type, "Expected 'type' value to match against"); + return compile_comparison_list("(type or (name == 'message' and 'normal') or (name == 'presence' and 'available'))", type), { "type", "name" }; +end + +local function zone_check(zone, which) + local zone_var = zone; + if zone == "$local" then zone_var = "_local" end + local which_not = which == "from" and "to" or "from"; + return ("(zone_%s[%s_host] or zone_%s[%s] or zone_%s[bare_%s]) " + .."and not(zone_%s[%s_host] or zone_%s[%s] or zone_%s[bare_%s])" + ) + :format(zone_var, which, zone_var, which, zone_var, which, + zone_var, which_not, zone_var, which_not, zone_var, which_not), { + "split_to", "split_from", "bare_to", "bare_from", "zone:"..zone + }; +end + +function condition_handlers.ENTERING(zone) + return zone_check(zone, "to"); +end + +function condition_handlers.LEAVING(zone) + return zone_check(zone, "from"); +end + +-- IN ROSTER? (parameter is deprecated) +function condition_handlers.IN_ROSTER(yes_no) + local in_roster_requirement = string_to_boolean(yes_no or "yes"); -- COMPAT w/ older scripts + return "not "..(in_roster_requirement and "not" or "").." roster_entry", { "roster_entry" }; +end + +function condition_handlers.IN_ROSTER_GROUP(group) + return ("not not (roster_entry and roster_entry.groups[%q])"):format(group), { "roster_entry" }; +end + +function condition_handlers.SUBSCRIBED() + return "(bare_to == bare_from or to_node and rostermanager.is_contact_subscribed(to_node, to_host, bare_from))", + { "rostermanager", "split_to", "bare_to", "bare_from" }; +end + +function condition_handlers.PENDING_SUBSCRIPTION_FROM_SENDER() + return "(bare_to == bare_from or to_node and rostermanager.is_contact_pending_in(to_node, to_host, bare_from))", + { "rostermanager", "split_to", "bare_to", "bare_from" }; +end + +function condition_handlers.PAYLOAD(payload_ns) + return ("stanza:get_child(nil, %q)"):format(payload_ns); +end + +function condition_handlers.INSPECT(path) + if path:find("=") then + local query, match_type, value = path:match("(.-)([~/$]*)=(.*)"); + if not(query:match("#$") or query:match("@[^/]+")) then + error("Stanza path does not return a string (append # for text content or @name for value of named attribute)", 0); + end + local meta_deps = {}; + local quoted_value = ("%q"):format(value); + if match_type:find("$", 1, true) then + match_type = match_type:gsub("%$", ""); + quoted_value = meta(quoted_value, meta_deps); + end + if match_type == "~" then -- Lua pattern match + return ("(stanza:find(%q) or ''):match(%s)"):format(query, quoted_value), meta_deps; + elseif match_type == "/" then -- find literal substring + return ("(stanza:find(%q) or ''):find(%s, 1, true)"):format(query, quoted_value), meta_deps; + elseif match_type == "" then -- exact match + return ("stanza:find(%q) == %s"):format(query, quoted_value), meta_deps; + else + error("Unrecognised comparison '"..match_type.."='", 0); + end + end + return ("stanza:find(%q)"):format(path); +end + +function condition_handlers.FROM_GROUP(group_name) + return ("group_contains(%q, bare_from)"):format(group_name), { "group_contains", "bare_from" }; +end + +function condition_handlers.TO_GROUP(group_name) + return ("group_contains(%q, bare_to)"):format(group_name), { "group_contains", "bare_to" }; +end + +function condition_handlers.CROSSING_GROUPS(group_names) + local code = {}; + for group_name in group_names:gmatch("([^, ][^,]+)") do + group_name = group_name:match("^%s*(.-)%s*$"); -- Trim leading/trailing whitespace + -- Just check that's it is crossing from outside group to inside group + table.insert(code, ("(group_contains(%q, bare_to) and group_contains(%q, bare_from))"):format(group_name, group_name)) + end + return "not "..table.concat(code, " or "), { "group_contains", "bare_to", "bare_from" }; +end + +-- COMPAT w/0.12: Deprecated +function condition_handlers.FROM_ADMIN_OF(host) + return ("is_admin(bare_from, %s)"):format(host ~= "*" and metaq(host) or nil), { "is_admin", "bare_from" }; +end + +-- COMPAT w/0.12: Deprecated +function condition_handlers.TO_ADMIN_OF(host) + return ("is_admin(bare_to, %s)"):format(host ~= "*" and metaq(host) or nil), { "is_admin", "bare_to" }; +end + +-- COMPAT w/0.12: Deprecated +function condition_handlers.FROM_ADMIN() + return ("is_admin(bare_from, current_host)"), { "is_admin", "bare_from", "current_host" }; +end + +-- COMPAT w/0.12: Deprecated +function condition_handlers.TO_ADMIN() + return ("is_admin(bare_to, current_host)"), { "is_admin", "bare_to", "current_host" }; +end + +-- MAY: permission_to_check +function condition_handlers.MAY(permission_to_check) + return ("module:may(%q, event)"):format(permission_to_check); +end + +function condition_handlers.TO_ROLE(role_name) + return ("recipient_role and recipient_role.name == %q"):format(role_name), { "recipient_role" }; +end + +function condition_handlers.FROM_ROLE(role_name) + return ("sender_role and sender_role.name == %q"):format(role_name), { "sender_role" }; +end + +local day_numbers = { sun = 0, mon = 2, tue = 3, wed = 4, thu = 5, fri = 6, sat = 7 }; + +local function current_time_check(op, hour, minute) + hour, minute = tonumber(hour), tonumber(minute); + local adj_op = op == "<" and "<" or ">="; -- Start time inclusive, end time exclusive + if minute == 0 then + return "(current_hour"..adj_op..hour..")"; + else + return "((current_hour"..op..hour..") or (current_hour == "..hour.." and current_minute"..adj_op..minute.."))"; + end +end + +local function resolve_day_number(day_name) + return assert(day_numbers[day_name:sub(1,3):lower()], "Unknown day name: "..day_name); +end + +function condition_handlers.DAY(days) + local conditions = {}; + for day_range in days:gmatch("[^,]+") do + local day_start, day_end = day_range:match("(%a+)%s*%-%s*(%a+)"); + if day_start and day_end then + local day_start_num, day_end_num = resolve_day_number(day_start), resolve_day_number(day_end); + local op = "and"; + if day_end_num < day_start_num then + op = "or"; + end + table.insert(conditions, ("current_day >= %d %s current_day <= %d"):format(day_start_num, op, day_end_num)); + elseif day_range:find("%a") then + local day = resolve_day_number(day_range:match("%a+")); + table.insert(conditions, "current_day == "..day); + else + error("Unable to parse day/day range: "..day_range); + end + end + assert(#conditions>0, "Expected a list of days or day ranges"); + return "("..table.concat(conditions, ") or (")..")", { "time:day" }; +end + +function condition_handlers.TIME(ranges) + local conditions = {}; + for range in ranges:gmatch("([^,]+)") do + local clause = {}; + range = range:lower() + :gsub("(%d+):?(%d*) *am", function (h, m) return tostring(tonumber(h)%12)..":"..(tonumber(m) or "00"); end) + :gsub("(%d+):?(%d*) *pm", function (h, m) return tostring(tonumber(h)%12+12)..":"..(tonumber(m) or "00"); end); + local start_hour, start_minute = range:match("(%d+):(%d+) *%-"); + local end_hour, end_minute = range:match("%- *(%d+):(%d+)"); + local op = tonumber(start_hour) > tonumber(end_hour) and " or " or " and "; + if start_hour and end_hour then + table.insert(clause, current_time_check(">", start_hour, start_minute)); + table.insert(clause, current_time_check("<", end_hour, end_minute)); + end + if #clause == 0 then + error("Unable to parse time range: "..range); + end + table.insert(conditions, "("..table.concat(clause, " "..op.." ")..")"); + end + return table.concat(conditions, " or "), { "time:hour,min" }; +end + +function condition_handlers.LIMIT(spec) + local name, param = spec:match("^(%w+) on (.+)$"); + local meta_deps = {}; + + if not name then + name = spec:match("^%w+$"); + if not name then + error("Unable to parse LIMIT specification"); + end + else + param = meta(("%q"):format(param), meta_deps); + end + + if not param then + return ("not global_throttle_%s:poll(1)"):format(name), { "globalthrottle:"..name, unpack(meta_deps) }; + end + return ("not multi_throttle_%s:poll_on(%s, 1)"):format(name, param), { "multithrottle:"..name, unpack(meta_deps) }; +end + +function condition_handlers.ORIGIN_MARKED(name_and_time) + local name, time = name_and_time:match("^%s*([%w_]+)%s+%(([^)]+)s%)%s*$"); + if not name then + name = name_and_time:match("^%s*([%w_]+)%s*$"); + end + if not name then + error("Error parsing mark name, see documentation for usage examples"); + end + if time then + return ("(current_timestamp - (session.firewall_marked_%s or 0)) < %d"):format(idsafe(name), tonumber(time)), { "timestamp" }; + end + return ("not not session.firewall_marked_"..idsafe(name)); +end + +function condition_handlers.USER_MARKED(name_and_time) + local name, time = name_and_time:match("^%s*([%w_]+)%s+%(([^)]+)s%)%s*$"); + if not name then + name = name_and_time:match("^%s*([%w_]+)%s*$"); + end + if not name then + error("Error parsing mark name, see documentation for usage examples"); + end + if time then + return ([[( + current_timestamp - (session.firewall_marks and session.firewall_marks.%s or 0) + ) < %d]]):format(idsafe(name), tonumber(time)), { "timestamp" }; + end + return ("not not (session.firewall_marks and session.firewall_marks."..idsafe(name)..")"); +end + +function condition_handlers.SENT_DIRECTED_PRESENCE_TO_SENDER() + return "not not (session.directed and session.directed[from])", { "from" }; +end + +-- TO FULL JID? +function condition_handlers.TO_FULL_JID() + return "not not full_sessions[to]", { "to", "full_sessions" }; +end + +-- CHECK LIST: spammers contains $<@from> +function condition_handlers.CHECK_LIST(list_condition) + local list_name, expr = list_condition:match("(%S+) contains (.+)$"); + if not (list_name and expr) then + error("Error parsing list check, syntax: LISTNAME contains EXPRESSION"); + end + local meta_deps = {}; + expr = meta(("%q"):format(expr), meta_deps); + return ("list_%s:contains(%s) == true"):format(list_name, expr), { "list:"..list_name, unpack(meta_deps) }; +end + +-- SCAN: body for word in badwords +function condition_handlers.SCAN(scan_expression) + local search_name, pattern_name, list_name = scan_expression:match("(%S+) for (%S+) in (%S+)$"); + if not (search_name) then + error("Error parsing SCAN expression, syntax: SEARCH for PATTERN in LIST"); + end + return ("scan_list(list_%s, %s)"):format( + list_name, + "tokens_"..search_name.."_"..pattern_name + ), { + "scan_list", + "tokens:"..search_name.."-"..pattern_name, "list:"..list_name + }; +end + +-- COUNT: lines in body < 10 +local valid_comp_ops = { [">"] = ">", ["<"] = "<", ["="] = "==", ["=="] = "==", ["<="] = "<=", [">="] = ">=" }; +function condition_handlers.COUNT(count_expression) + local pattern_name, search_name, comparator_expression = count_expression:match("(%S+) in (%S+) (.+)$"); + if not (pattern_name) then + error("Error parsing COUNT expression, syntax: PATTERN in SEARCH COMPARATOR"); + end + local value; + comparator_expression = comparator_expression:gsub("%d+", function (value_string) + value = tonumber(value_string); + return ""; + end); + if not value then + error("Error parsing COUNT expression, expected value"); + end + local comp_op = comparator_expression:gsub("%s+", ""); + assert(valid_comp_ops[comp_op], "Error parsing COUNT expression, unknown comparison operator: "..comp_op); + return ("it_count(search_%s:gmatch(pattern_%s)) %s %d"):format( + search_name, pattern_name, comp_op, value + ), { + "it_count", + "search:"..search_name, "pattern:"..pattern_name + }; +end + +-- FROM COUNTRY: SE +-- FROM COUNTRY: code=SE +-- FROM COUNTRY: SWE +-- FROM COUNTRY: code3=SWE +-- FROM COUNTRY: continent=EU +-- FROM COUNTRY? --> NOT FROM COUNTRY: -- (for unknown/invalid) +-- TODO list support? +function condition_handlers.FROM_COUNTRY(geoip_spec) + local condition = "=="; + if not geoip_spec then + geoip_spec = "--"; + condition = "~="; + end + local field, country = geoip_spec:match("(%w+)=(%w+)"); + if not field then + if #geoip_spec == 3 then + field, country = "code3", geoip_spec; + elseif #geoip_spec == 2 then + field, country = "code", geoip_spec; + else + error("Unknown country code type"); + end + end + return ("get_geoip(session.ip, %q) %s %q"):format(field:lower(), condition, country:upper()), { "geoip_country" }; +end + +return condition_handlers; diff --git a/prosody-modules/mod_firewall/definitions.lib.lua b/prosody-modules/mod_firewall/definitions.lib.lua new file mode 100644 index 00000000..a35ba804 --- /dev/null +++ b/prosody-modules/mod_firewall/definitions.lib.lua @@ -0,0 +1,335 @@ + +-- Name arguments are unused here +-- luacheck: ignore 212 + +local definition_handlers = {}; + +local http = require "net.http"; +local timer = require "util.timer"; +local set = require"util.set"; +local new_throttle = require "util.throttle".create; +local hashes = require "util.hashes"; +local jid = require "util.jid"; +local lfs = require "lfs"; + +local multirate_cache_size = module:get_option_number("firewall_multirate_cache_limit", 1000); + +function definition_handlers.ZONE(zone_name, zone_members) + local zone_member_list = {}; + for member in zone_members:gmatch("[^, ]+") do + zone_member_list[#zone_member_list+1] = member; + end + return set.new(zone_member_list)._items; +end + +-- Helper function used by RATE handler +local function evict_only_unthrottled(name, throttle) + throttle:update(); + -- Check whether the throttle is at max balance (i.e. totally safe to forget about it) + if throttle.balance < throttle.max then + -- Not safe to forget + return false; + end +end + +function definition_handlers.RATE(name, line) + local rate = assert(tonumber(line:match("([%d.]+)")), "Unable to parse rate"); + local burst = tonumber(line:match("%(%s*burst%s+([%d.]+)%s*%)")) or 1; + local max_throttles = tonumber(line:match("%(%s*entries%s+([%d]+)%s*%)")) or multirate_cache_size; + local deny_when_full = not line:match("%(allow overflow%)"); + return { + single = function () + return new_throttle(rate*burst, burst); + end; + + multi = function () + local cache = require "util.cache".new(max_throttles, deny_when_full and evict_only_unthrottled or nil); + return { + poll_on = function (_, key, amount) + assert(key, "no key"); + local throttle = cache:get(key); + if not throttle then + throttle = new_throttle(rate*burst, burst); + if not cache:set(key, throttle) then + module:log("warn", "Multirate '%s' has hit its maximum number of active throttles (%d), denying new events", name, max_throttles); + return false; + end + end + return throttle:poll(amount); + end; + } + end; + }; +end + +local list_backends = { + -- %LIST name: memory (limit: number) + memory = { + init = function (self, type, opts) + if opts.limit then + local have_cache_lib, cache_lib = pcall(require, "util.cache"); + if not have_cache_lib then + error("In-memory lists with a size limit require Prosody 0.10"); + end + self.cache = cache_lib.new((assert(tonumber(opts.limit), "Invalid list limit"))); + if not self.cache.table then + error("In-memory lists with a size limit require a newer version of Prosody 0.10"); + end + self.items = self.cache:table(); + else + self.items = {}; + end + end; + add = function (self, item) + self.items[item] = true; + end; + remove = function (self, item) + self.items[item] = nil; + end; + contains = function (self, item) + return self.items[item] == true; + end; + }; + + -- %LIST name: http://example.com/ (ttl: number, pattern: pat, hash: sha1) + http = { + init = function (self, url, opts) + local poll_interval = assert(tonumber(opts.ttl or "3600"), "invalid ttl for <"..url.."> (expected number of seconds)"); + local pattern = opts.pattern or "([^\r\n]+)\r?\n"; + assert(pcall(string.match, "", pattern), "invalid pattern for <"..url..">"); + if opts.hash then + assert(opts.hash:match("^%w+$") and type(hashes[opts.hash]) == "function", "invalid hash function: "..opts.hash); + self.hash_function = hashes[opts.hash]; + end + local etag; + local failure_count = 0; + local retry_intervals = { 60, 120, 300 }; + -- By default only check the certificate if net.http supports SNI + local sni_supported = http.feature and http.features.sni; + local insecure = false; + if opts.checkcert == "never" then + insecure = true; + elseif (opts.checkcert == nil or opts.checkcert == "when-sni") and not sni_supported then + insecure = false; + end + local function update_list() + http.request(url, { + insecure = insecure; + headers = { + ["If-None-Match"] = etag; + }; + }, function (body, code, response) + local next_poll = poll_interval; + if code == 200 and body then + etag = response.headers.etag; + local items = {}; + for entry in body:gmatch(pattern) do + items[entry] = true; + end + self.items = items; + module:log("debug", "Fetched updated list from <%s>", url); + elseif code == 304 then + module:log("debug", "List at <%s> is unchanged", url); + elseif code == 0 or (code >= 400 and code <=599) then + module:log("warn", "Failed to fetch list from <%s>: %d %s", url, code, tostring(body)); + failure_count = failure_count + 1; + next_poll = retry_intervals[failure_count] or retry_intervals[#retry_intervals]; + end + if next_poll > 0 then + timer.add_task(next_poll+math.random(0, 60), update_list); + end + end); + end + update_list(); + end; + add = function () + end; + remove = function () + end; + contains = function (self, item) + if self.hash_function then + item = self.hash_function(item); + end + return self.items and self.items[item] == true; + end; + }; + + -- %LIST: file:/path/to/file + file = { + init = function (self, file_spec, opts) + local n, items = 0, {}; + self.items = items; + local filename = file_spec:gsub("^file:", ""); + if opts.missing == "ignore" and not lfs.attributes(filename, "mode") then + module:log("debug", "Ignoring missing list file: %s", filename); + return; + end + local file, err = io.open(filename); + if not file then + module:log("warn", "Failed to open list from %s: %s", filename, err); + return; + else + for line in file:lines() do + if not items[line] then + n = n + 1; + items[line] = true; + end + end + end + module:log("debug", "Loaded %d items from %s", n, filename); + end; + add = function (self, item) + self.items[item] = true; + end; + remove = function (self, item) + self.items[item] = nil; + end; + contains = function (self, item) + return self.items and self.items[item] == true; + end; + }; + + -- %LIST: pubsub:pubsub.example.com/node + -- TODO or the actual URI scheme? Bit overkill maybe? + -- TODO Publish items back to the service? + -- Step 1: Receiving pubsub events and storing them in the list + -- We'll start by using only the item id. + -- TODO Invent some custom schema for this? Needed for just a set of strings? + pubsubitemid = { + init = function(self, pubsub_spec, opts) + local service_addr, node = pubsub_spec:match("^pubsubitemid:([^/]*)/(.*)"); + if not service_addr then + module:log("warn", "Invalid list specification (expected 'pubsubitemid:/', got: '%s')", pubsub_spec); + return; + end + module:depends("pubsub_subscription"); + module:add_item("pubsub-subscription", { + service = service_addr; + node = node; + on_subscribed = function () + self.items = {}; + end; + on_item = function (event) + self:add(event.item.attr.id); + end; + on_retract = function (event) + self:remove(event.item.attr.id); + end; + on_purge = function () + self.items = {}; + end; + on_unsubscribed = function () + self.items = nil; + end; + on_delete= function () + self.items = nil; + end; + }); + -- TODO Initial fetch? Or should mod_pubsub_subscription do this? + end; + add = function (self, item) + if self.items then + self.items[item] = true; + end + end; + remove = function (self, item) + if self.items then + self.items[item] = nil; + end + end; + contains = function (self, item) + return self.items and self.items[item] == true; + end; + }; +}; +list_backends.https = list_backends.http; + +local normalize_functions = { + upper = string.upper, lower = string.lower; + md5 = hashes.md5, sha1 = hashes.sha1, sha256 = hashes.sha256; + prep = jid.prep, bare = jid.bare; +}; + +local function wrap_list_method(list_method, filter) + return function (self, item) + return list_method(self, filter(item)); + end +end + +local function create_list(list_backend, list_def, opts) + if not list_backends[list_backend] then + error("Unknown list type '"..list_backend.."'", 0); + end + local list = setmetatable({}, { __index = list_backends[list_backend] }); + if list.init then + list:init(list_def, opts); + end + if opts.filter then + local filters = {}; + for func_name in opts.filter:gmatch("[%w_]+") do + if func_name == "log" then + table.insert(filters, function (s) + --print("&&&&&", s); + module:log("debug", "Checking list <%s> for: %s", list_def, s); + return s; + end); + else + assert(normalize_functions[func_name], "Unknown list filter: "..func_name); + table.insert(filters, normalize_functions[func_name]); + end + end + + local filter; + local n = #filters; + if n == 1 then + filter = filters[1]; + else + function filter(s) + for i = 1, n do + s = filters[i](s or ""); + end + return s; + end + end + + list.add = wrap_list_method(list.add, filter); + list.remove = wrap_list_method(list.remove, filter); + list.contains = wrap_list_method(list.contains, filter); + end + return list; +end + +--[[ +%LIST spammers: memory (source: /etc/spammers.txt) + +%LIST spammers: memory (source: /etc/spammers.txt) + + +%LIST spammers: http://example.com/blacklist.txt +]] + +function definition_handlers.LIST(list_name, list_definition) + local list_backend = list_definition:match("^%w+"); + local opts = {}; + local opt_string = list_definition:match("^%S+%s+%((.+)%)"); + if opt_string then + for opt_k, opt_v in opt_string:gmatch("(%w+): ?([^,]+)") do + opts[opt_k] = opt_v; + end + end + return create_list(list_backend, list_definition:match("^%S+"), opts); +end + +function definition_handlers.PATTERN(name, pattern) + local ok, err = pcall(string.match, "", pattern); + if not ok then + error("Invalid pattern '"..name.."': "..err); + end + return pattern; +end + +function definition_handlers.SEARCH(name, pattern) + return pattern; +end + +return definition_handlers; diff --git a/prosody-modules/mod_firewall/marks.lib.lua b/prosody-modules/mod_firewall/marks.lib.lua new file mode 100644 index 00000000..3c9bbb06 --- /dev/null +++ b/prosody-modules/mod_firewall/marks.lib.lua @@ -0,0 +1,35 @@ +local mark_storage = module:open_store("firewall_marks"); +local mark_map_storage = module:open_store("firewall_marks", "map"); + +local user_sessions = prosody.hosts[module.host].sessions; + +module:hook("firewall/marked/user", function (event) + local user = user_sessions[event.username]; + local marks = user and user.firewall_marks; + if user and not marks then + -- Load marks from storage to cache on the user object + marks = mark_storage:get(event.username) or {}; + user.firewall_marks = marks; --luacheck: ignore 122 + end + if marks then + marks[event.mark] = event.timestamp; + end + local ok, err = mark_map_storage:set(event.username, event.mark, event.timestamp); + if not ok then + module:log("error", "Failed to mark user %q with %q: %s", event.username, event.mark, err); + end + return true; +end, -1); + +module:hook("firewall/unmarked/user", function (event) + local user = user_sessions[event.username]; + local marks = user and user.firewall_marks; + if marks then + marks[event.mark] = nil; + end + local ok, err = mark_map_storage:set(event.username, event.mark, nil); + if not ok then + module:log("error", "Failed to unmark user %q with %q: %s", event.username, event.mark, err); + end + return true; +end, -1); diff --git a/prosody-modules/mod_firewall/mod_firewall.lua b/prosody-modules/mod_firewall/mod_firewall.lua new file mode 100644 index 00000000..62cfdbd7 --- /dev/null +++ b/prosody-modules/mod_firewall/mod_firewall.lua @@ -0,0 +1,863 @@ + +local lfs = require "lfs"; +local resolve_relative_path = require "core.configmanager".resolve_relative_path; +local envload = require "util.envload".envload; +local logger = require "util.logger".init; +local it = require "util.iterators"; +local set = require "util.set"; + +local have_features, features = pcall(require, "core.features"); +features = have_features and features.available or set.new(); + +-- [definition_type] = definition_factory(param) +local definitions = module:shared("definitions"); + +-- When a definition instance has been instantiated, it lives here +-- [definition_type][definition_name] = definition_object +local active_definitions = { + ZONE = { + -- Default zone that includes all local hosts + ["$local"] = setmetatable({}, { __index = prosody.hosts }); + }; +}; + +local default_chains = { + preroute = { + type = "event"; + priority = 0.1; + "pre-message/bare", "pre-message/full", "pre-message/host"; + "pre-presence/bare", "pre-presence/full", "pre-presence/host"; + "pre-iq/bare", "pre-iq/full", "pre-iq/host"; + }; + deliver = { + type = "event"; + priority = 0.1; + "message/bare", "message/full", "message/host"; + "presence/bare", "presence/full", "presence/host"; + "iq/bare", "iq/full", "iq/host"; + }; + deliver_remote = { + type = "event"; "route/remote"; + priority = 0.1; + }; +}; + +local extra_chains = module:get_option("firewall_extra_chains", {}); + +local chains = {}; +for k,v in pairs(default_chains) do + chains[k] = v; +end +for k,v in pairs(extra_chains) do + chains[k] = v; +end + +-- Returns the input if it is safe to be used as a variable name, otherwise nil +function idsafe(name) + return name:match("^%a[%w_]*$"); +end + +local meta_funcs = { + bare = function (code) + return "jid_bare("..code..")", {"jid_bare"}; + end; + node = function (code) + return "(jid_split("..code.."))", {"jid_split"}; + end; + host = function (code) + return "(select(2, jid_split("..code..")))", {"jid_split"}; + end; + resource = function (code) + return "(select(3, jid_split("..code..")))", {"jid_split"}; + end; +}; + +-- Run quoted (%q) strings through this to allow them to contain code. e.g.: LOG=Received: $(stanza:top_tag()) +function meta(s, deps, extra) + return (s:gsub("$(%b())", function (expr) + expr = expr:gsub("\\(.)", "%1"); + return [["..tostring(]]..expr..[[).."]]; + end) + :gsub("$(%b<>)", function (expr) + expr = expr:sub(2,-2); + local default = ""; + expr = expr:gsub("||(%b\"\")$", function (default_string) + default = stripslashes(default_string:sub(2,-2)); + return ""; + end); + local func_chain = expr:match("|[%w|]+$"); + if func_chain then + expr = expr:sub(1, -1-#func_chain); + end + local code; + if expr:match("^@") then + -- Skip stanza:find() for simple attribute lookup + local attr_name = expr:sub(2); + if deps and (attr_name == "to" or attr_name == "from" or attr_name == "type") then + -- These attributes may be cached in locals + code = attr_name; + table.insert(deps, attr_name); + else + code = "stanza.attr["..("%q"):format(attr_name).."]"; + end + elseif expr:match("^%w+#$") then + code = ("stanza:get_child_text(%q)"):format(expr:sub(1, -2)); + else + code = ("stanza:find(%q)"):format(expr); + end + if func_chain then + for func_name in func_chain:gmatch("|(%w+)") do + -- to/from are already available in local variables, use those if possible + if (code == "to" or code == "from") and func_name == "bare" then + code = "bare_"..code; + table.insert(deps, code); + elseif (code == "to" or code == "from") and (func_name == "node" or func_name == "host" or func_name == "resource") then + table.insert(deps, "split_"..code); + code = code.."_"..func_name; + else + assert(meta_funcs[func_name], "unknown function: "..func_name); + local new_code, new_deps = meta_funcs[func_name](code); + code = new_code; + if new_deps and #new_deps > 0 then + assert(deps, "function not supported here: "..func_name); + for _, dep in ipairs(new_deps) do + table.insert(deps, dep); + end + end + end + end + end + return "\"..tostring("..code.." or "..("%q"):format(default)..")..\""; + end) + :gsub("$$(%a+)", extra or {}) + :gsub([[^""%.%.]], "") + :gsub([[%.%.""$]], "")); +end + +function metaq(s, ...) + return meta(("%q"):format(s), ...); +end + +local escape_chars = { + a = "\a", b = "\b", f = "\f", n = "\n", r = "\r", t = "\t", + v = "\v", ["\\"] = "\\", ["\""] = "\"", ["\'"] = "\'" +}; +function stripslashes(s) + return (s:gsub("\\(.)", escape_chars)); +end + +-- Dependency locations: +-- +-- +-- function handler() +-- +-- if then +-- +-- end +-- end + +local available_deps = { + st = { global_code = [[local st = require "util.stanza";]]}; + it = { global_code = [[local it = require "util.iterators";]]}; + it_count = { global_code = [[local it_count = it.count;]], depends = { "it" } }; + current_host = { global_code = [[local current_host = module.host;]] }; + jid_split = { + global_code = [[local jid_split = require "util.jid".split;]]; + }; + jid_bare = { + global_code = [[local jid_bare = require "util.jid".bare;]]; + }; + to = { local_code = [[local to = stanza.attr.to or jid_bare(session.full_jid);]]; depends = { "jid_bare" } }; + from = { local_code = [[local from = stanza.attr.from;]] }; + type = { local_code = [[local type = stanza.attr.type;]] }; + name = { local_code = [[local name = stanza.name;]] }; + split_to = { -- The stanza's split to address + depends = { "jid_split", "to" }; + local_code = [[local to_node, to_host, to_resource = jid_split(to);]]; + }; + split_from = { -- The stanza's split from address + depends = { "jid_split", "from" }; + local_code = [[local from_node, from_host, from_resource = jid_split(from);]]; + }; + bare_to = { depends = { "jid_bare", "to" }, local_code = "local bare_to = jid_bare(to)"}; + bare_from = { depends = { "jid_bare", "from" }, local_code = "local bare_from = jid_bare(from)"}; + group_contains = { + global_code = [[local group_contains = module:depends("groups").group_contains]]; + }; + is_admin = require"core.usermanager".is_admin and { global_code = [[local is_admin = require "core.usermanager".is_admin;]]} or nil; + get_jid_role = require "core.usermanager".get_jid_role and { global_code = [[local get_jid_role = require "core.usermanager".get_jid_role;]] } or nil; + core_post_stanza = { global_code = [[local core_post_stanza = prosody.core_post_stanza;]] }; + zone = { global_code = function (zone) + local var = zone; + if var == "$local" then + var = "_local"; -- See #1090 + else + assert(idsafe(var), "Invalid zone name: "..zone); + end + return ("local zone_%s = zones[%q] or {};"):format(var, zone); + end }; + date_time = { global_code = [[local os_date = os.date]]; local_code = [[local current_date_time = os_date("*t");]] }; + time = { local_code = function (what) + local defs = {}; + for field in what:gmatch("%a+") do + table.insert(defs, ("local current_%s = current_date_time.%s;"):format(field, field)); + end + return table.concat(defs, " "); + end, depends = { "date_time" }; }; + timestamp = { global_code = [[local get_time = require "socket".gettime;]]; local_code = [[local current_timestamp = get_time();]]; }; + globalthrottle = { + global_code = function (throttle) + assert(idsafe(throttle), "Invalid rate limit name: "..throttle); + assert(active_definitions.RATE[throttle], "Unknown rate limit: "..throttle); + return ("local global_throttle_%s = rates.%s:single();"):format(throttle, throttle); + end; + }; + multithrottle = { + global_code = function (throttle) + assert(pcall(require, "util.cache"), "Using LIMIT with 'on' requires Prosody 0.10 or higher"); + assert(idsafe(throttle), "Invalid rate limit name: "..throttle); + assert(active_definitions.RATE[throttle], "Unknown rate limit: "..throttle); + return ("local multi_throttle_%s = rates.%s:multi();"):format(throttle, throttle); + end; + }; + full_sessions = { + global_code = [[local full_sessions = prosody.full_sessions;]]; + }; + rostermanager = { + global_code = [[local rostermanager = require "core.rostermanager";]]; + }; + roster_entry = { + local_code = [[local roster_entry = (to_node and rostermanager.load_roster(to_node, to_host) or {})[bare_from];]]; + depends = { "rostermanager", "split_to", "bare_from" }; + }; + list = { global_code = function (list) + assert(idsafe(list), "Invalid list name: "..list); + assert(active_definitions.LIST[list], "Unknown list: "..list); + return ("local list_%s = lists[%q];"):format(list, list); + end + }; + search = { + local_code = function (search_name) + local search_path = assert(active_definitions.SEARCH[search_name], "Undefined search path: "..search_name); + return ("local search_%s = tostring(stanza:find(%q) or \"\")"):format(search_name, search_path); + end; + }; + pattern = { + local_code = function (pattern_name) + local pattern = assert(active_definitions.PATTERN[pattern_name], "Undefined pattern: "..pattern_name); + return ("local pattern_%s = %q"):format(pattern_name, pattern); + end; + }; + tokens = { + local_code = function (search_and_pattern) + local search_name, pattern_name = search_and_pattern:match("^([^%-]+)-(.+)$"); + local code = ([[local tokens_%s_%s = {}; + if search_%s then + for s in search_%s:gmatch(pattern_%s) do + tokens_%s_%s[s] = true; + end + end + ]]):format(search_name, pattern_name, search_name, search_name, pattern_name, search_name, pattern_name); + return code, { "search:"..search_name, "pattern:"..pattern_name }; + end; + }; + sender_role = { + local_code = [[local sender_role = get_jid_role(bare_from, current_host)]]; + depends = { "bare_from", "current_host", "get_jid_role" }; + }; + recipient_role = { + local_code = [[local recipient_role = get_jid_role(bare_to, current_host)]]; + depends = { "bare_to", "current_host", "get_jid_role" }; + }; + scan_list = { + global_code = [[local function scan_list(list, items) for item in pairs(items) do if list:contains(item) then return true; end end end]]; + }; + iplib = { + global_code = [[local iplib = require "util.ip";]]; + }; + geoip_country = { + global_code = [[ +local geoip_country = require "geoip.country"; +local geov4 = geoip_country.open(module:get_option_string("geoip_ipv4_country", "/usr/share/GeoIP/GeoIP.dat")); +local geov6 = geoip_country.open(module:get_option_string("geoip_ipv6_country", "/usr/share/GeoIP/GeoIPv6.dat")); +local function get_geoip(ips, what) + if not ips then + return "--"; + end + local ip = iplib.new_ip(ips); + if not ip then + return "--"; + end + if ip.proto == "IPv6" and geov6 then + local geoinfo = geoinfo:query_by_addr6(ip.addr); + if geoinfo then + return geoinfo[what or "code"]; + end + elseif ip.proto == "IPv4" and geov4 then + local geoinfo = geoinfo:query_by_addr(ip.addr); + if geoinfo then + return geoinfo[what or "code"]; + end + end + return "--"; +end + ]]; + depends = { + "iplib" + } + }; + new_short_id = { + global_code = [[local new_short_id = require "util.id".short;]]; + }; + new_medium_id = { + global_code = [[local new_medium_id = require "util.id".medium;]]; + }; + new_long_id = { + global_code = [[local new_long_id = require "util.id".long;]]; + }; + + trace = { + global_code = [[local trace_init = module:require("trace").init;]]; + }; +}; + +local function include_dep(dependency, code) + local dep, dep_param = dependency:match("^([^:]+):?(.*)$"); + local dep_info = available_deps[dep]; + if not dep_info then + module:log("error", "Dependency not found: %s", dep); + return; + end + if code.included_deps[dependency] ~= nil then + if code.included_deps[dependency] ~= true then + module:log("error", "Circular dependency on %s", dep); + end + return; + end + code.included_deps[dependency] = false; -- Pending flag (used to detect circular references) + for _, dep_dep in ipairs(dep_info.depends or {}) do + include_dep(dep_dep, code); + end + if dep_info.global_code then + if dep_param ~= "" then + local global_code, deps = dep_info.global_code(dep_param); + if deps then + for _, dep_dep in ipairs(deps) do + include_dep(dep_dep, code); + end + end + table.insert(code.global_header, global_code); + else + table.insert(code.global_header, dep_info.global_code); + end + end + if dep_info.local_code then + if dep_param ~= "" then + local local_code, deps = dep_info.local_code(dep_param); + if deps then + for _, dep_dep in ipairs(deps) do + include_dep(dep_dep, code); + end + end + table.insert(code, "\n\t\t-- "..dep.."\n\t\t"..local_code.."\n"); + else + table.insert(code, "\n\t\t-- "..dep.."\n\t\t"..dep_info.local_code.."\n"); + end + end + code.included_deps[dependency] = true; +end + +local definition_handlers = module:require("definitions"); +local condition_handlers = module:require("conditions"); +local action_handlers = module:require("actions"); + +if module:get_option_boolean("firewall_experimental_user_marks", true) then + module:require"marks"; +end + +local function new_rule(ruleset, chain, line_no) + assert(chain, "no chain specified"); + local rule = { conditions = {}, actions = {}, deps = {}, line_no = line_no }; + table.insert(ruleset[chain], rule); + return rule; +end + +local function parse_firewall_rules(filename) + local line_no = 0; + + local function errmsg(err) + return "Error compiling "..filename.." on line "..line_no..": "..err; + end + + local metadata = { debug = {} }; + local ruleset = { + deliver = {}; + }; + + local chain = "deliver"; -- Default chain + local rule; + + local file, err = io.open(filename); + if not file then return nil, err; end + + local state; -- nil -> "rules" -> "actions" -> nil -> ... + + local line_hold; + for line in file:lines() do + line = line:match("^%s*(.-)%s*$"); + if line_hold and line:sub(-1,-1) ~= "\\" then + line = line_hold..line; + line_hold = nil; + elseif line:sub(-1,-1) == "\\" then + line_hold = (line_hold or "")..line:sub(1,-2); + end + line_no = line_no + 1; + + if line_hold or line:find("^[#;]") then -- luacheck: ignore 542 + -- No action; comment or partial line + elseif line == "" then + if state == "rules" then + return nil, ("Expected an action on line %d for preceding criteria") + :format(line_no); + end + state = nil; + elseif not(state) and line:sub(1, 2) == "::" then + chain = line:gsub("^::%s*", ""); + local chain_info = chains[chain]; + if not chain_info then + if chain:match("^user/") then + chains[chain] = { type = "event", priority = 1, pass_return = false }; + else + return nil, errmsg("Unknown chain: "..chain); + end + elseif chain_info.type ~= "event" then + return nil, errmsg("Only event chains supported at the moment"); + end + ruleset[chain] = ruleset[chain] or {}; + elseif not(state) and line:sub(1, 2) == "@@" then + local k, v = line:match("^@@%s*([^%s=]+)%s*=%s*(.+)$"); + if not k then + return nil, errmsg("Unable to parse metadata assignment (expected '@@ key = value')"); + end + metadata[k] = v; + elseif not(state) and line:sub(1,1) == "%" then -- Definition (zone, limit, etc.) + local what, name = line:match("^%%%s*([%w_]+) +([^ :]+)"); + if not definition_handlers[what] then + return nil, errmsg("Definition of unknown object: "..what); + elseif not name or not idsafe(name) then + return nil, errmsg("Invalid "..what.." name"); + end + + local val = line:match(": ?(.*)$"); + if not val and line:find(":<") then -- Read from file + local fn = line:match(":< ?(.-)%s*$"); + if not fn then + return nil, errmsg("Unable to parse filename"); + end + local f, err = io.open(fn); + if not f then return nil, errmsg(err); end + val = f:read("*a"):gsub("\r?\n", " "):gsub("%s+$", ""); + end + if not val then + return nil, errmsg("No value given for definition"); + end + val = stripslashes(val); + local ok, ret = pcall(definition_handlers[what], name, val); + if not ok then + return nil, errmsg(ret); + end + + if not active_definitions[what] then + active_definitions[what] = {}; + end + active_definitions[what][name] = ret; + elseif line:find("^[%w_ ]+[%.=]") then + -- Action + if state == nil then + -- This is a standalone action with no conditions + rule = new_rule(ruleset, chain); + end + state = "actions"; + -- Action handlers? + local action = line:match("^[%w_ ]+"):upper():gsub(" ", "_"); + if not action_handlers[action] then + return nil, ("Unknown action on line %d: %s"):format(line_no, action or ""); + end + table.insert(rule.actions, "-- "..line) + local ok, action_string, action_deps = pcall(action_handlers[action], line:match("=(.+)$")); + if not ok then + return nil, errmsg(action_string); + end + table.insert(rule.actions, action_string); + for _, dep in ipairs(action_deps or {}) do + table.insert(rule.deps, dep); + end + elseif state == "actions" then -- state is actions but action pattern did not match + state = nil; -- Awaiting next rule, etc. + table.insert(ruleset[chain], rule); -- FIXME: Is this a bug? Rule should have already been inserted by new_rule()? + rule = nil; + else + -- Condition + if not state then -- Starting a new rule block? + state = "rules"; + rule = new_rule(ruleset, chain, line_no); + end + -- Check standard modifiers for the condition (e.g. NOT) + local negated; + local condition = line:match("^[^:=%.?]*"); + if condition:find("%f[%w]NOT%f[^%w]") then + local s, e = condition:match("%f[%w]()NOT()%f[^%w]"); + condition = (condition:sub(1,s-1)..condition:sub(e+1, -1)):match("^%s*(.-)%s*$"); + negated = true; + end + condition = condition:gsub(" ", "_"); + if not condition_handlers[condition] then + return nil, ("Unknown condition on line %d: %s"):format(line_no, (condition:gsub("_", " "))); + end + -- Get the code for this condition + local ok, condition_code, condition_deps = pcall(condition_handlers[condition], line:match(":%s?(.+)$")); + if not ok then + return nil, errmsg(condition_code); + end + if negated then condition_code = "not("..condition_code..")"; end + table.insert(rule.conditions, condition_code); + for _, dep in ipairs(condition_deps or {}) do + table.insert(rule.deps, dep); + end + end + end + return ruleset, metadata; +end + +local function process_firewall_rules(ruleset, metadata) + -- Compile ruleset and return complete code + + local chain_handlers = {}; + + -- Loop through the chains in the parsed ruleset (e.g. incoming, outgoing) + for chain_name, rules in pairs(ruleset) do + local code = { included_deps = {}, global_header = {} }; + local condition_uses = {}; + -- This inner loop assumes chain is an event-based, not a filter-based + -- chain (filter-based will be added later) + for _, rule in ipairs(rules) do + for _, condition in ipairs(rule.conditions) do + if condition:find("^not%(.+%)$") then + condition = condition:match("^not%((.+)%)$"); + end + condition_uses[condition] = (condition_uses[condition] or 0) + 1; + end + end + + if metadata.trace then + include_dep("trace", code); + table.insert(code, ("local trace = trace_init(%q, %q);"):format(metadata.filename, chain_name)) + end + + local condition_cache, n_conditions = {}, 0; + for rule_n, rule in ipairs(rules) do + for _, dep in ipairs(rule.deps) do + include_dep(dep, code); + end + table.insert(code, "\n\t\t"); + local rule_code; + if #rule.conditions > 0 then + for i, condition in ipairs(rule.conditions) do + local negated = condition:match("^not%(.+%)$"); + if negated then + condition = condition:match("^not%((.+)%)$"); + end + if condition_uses[condition] > 1 then + local name = condition_cache[condition]; + if not name then + n_conditions = n_conditions + 1; + name = "condition"..n_conditions; + condition_cache[condition] = name; + table.insert(code, "local "..name.." = "..condition..";\n\t\t"); + end + rule.conditions[i] = (negated and "not(" or "")..name..(negated and ")" or ""); + else + rule.conditions[i] = (negated and "not(" or "(")..condition..")"; + end + + if metadata.trace then + -- Wrap each condition in a tracer + rule.conditions[i] = ("trace(%d, %d, %s)"):format(rule_n, i, rule.conditions[i]); + end + end + + if metadata.trace then + -- Trace overall action + table.insert(rule.actions, 1, ("trace(%d, nil, true)"):format(rule_n)); + table.insert(rule.actions, ("else trace(%d, nil, false)"):format(rule_n)); + end + + rule_code = "if "..table.concat(rule.conditions, " and ").." then\n\t\t\t" + ..table.concat(rule.actions, "\n\t\t\t") + .."\n\t\tend\n"; + else + rule_code = table.concat(rule.actions, "\n\t\t"); + end + table.insert(code, rule_code); + end + + for name in pairs(definition_handlers) do + table.insert(code.global_header, 1, "local "..name:lower().."s = definitions."..name..";"); + end + + local code_string = "return function (definitions, fire_event, log, module, pass_return)\n\t" + ..table.concat(code.global_header, "\n\t") + .."\n\tlocal db = require 'util.debug';\n\n\t" + .."return function (event)\n\t\t" + .."local stanza, session = event.stanza, event.origin;\n" + ..table.concat(code, "") + .."\n\tend;\nend"; + + chain_handlers[chain_name] = code_string; + end + + return chain_handlers; +end + +local function compile_firewall_rules(filename) + local ruleset, metadata = parse_firewall_rules(filename); + if not ruleset then return nil, metadata; end + local chain_handlers = process_firewall_rules(ruleset, metadata); + return chain_handlers; +end + +-- Compile handler code into a factory that produces a valid event handler. Factory accepts +-- a value to be returned on PASS +local function compile_handler(code_string, filename) + -- Prepare event handler function + local chunk, err = envload(code_string, "="..filename, _G); + if not chunk then + return nil, "Error compiling (probably a compiler bug, please report): "..err; + end + local function fire_event(name, data) + return module:fire_event(name, data); + end + local init_ok, initialized_chunk = pcall(chunk); + if not init_ok then + return nil, "Error initializing compiled rules: "..initialized_chunk; + end + return function (pass_return) + return initialized_chunk(active_definitions, fire_event, logger(filename), module, pass_return); -- Returns event handler with upvalues + end +end + +local function resolve_script_path(script_path) + local relative_to = prosody.paths.config; + if script_path:match("^module:") then + relative_to = module:get_directory(); + script_path = script_path:match("^module:(.+)$"); + end + return resolve_relative_path(relative_to, script_path); +end + +-- [filename] = { last_modified = ..., events_hooked = { [name] = handler } } +local loaded_scripts = {}; + +function load_script(script) + script = resolve_script_path(script); + local last_modified = (lfs.attributes(script) or {}).modification or os.time(); + if loaded_scripts[script] then + if loaded_scripts[script].last_modified == last_modified then + return; -- Already loaded, and source file hasn't changed + end + module:log("debug", "Reloading %s", script); + -- Already loaded, but the source file has changed + -- unload it now, and we'll load the new version below + unload_script(script, true); + end + local chain_functions, err = compile_firewall_rules(script); + + if not chain_functions then + module:log("error", "Error compiling %s: %s", script, err or "unknown error"); + return; + end + + -- Loop through the chains in the script, and for each chain attach the compiled code to the + -- relevant events, keeping track in events_hooked so we can cleanly unload later + local events_hooked = {}; + for chain, handler_code in pairs(chain_functions) do + local new_handler, err = compile_handler(handler_code, "mod_firewall::"..chain); + if not new_handler then + module:log("error", "Compilation error for %s: %s", script, err); + else + local chain_definition = chains[chain]; + if chain_definition and chain_definition.type == "event" then + local handler = new_handler(chain_definition.pass_return); + for _, event_name in ipairs(chain_definition) do + events_hooked[event_name] = handler; + module:hook(event_name, handler, chain_definition.priority); + end + elseif not chain:sub(1, 5) == "user/" then + module:log("warn", "Unknown chain %q", chain); + end + local event_name, handler = "firewall/chains/"..chain, new_handler(false); + events_hooked[event_name] = handler; + module:hook(event_name, handler); + end + end + loaded_scripts[script] = { last_modified = last_modified, events_hooked = events_hooked }; + module:log("debug", "Loaded %s", script); +end + +--COMPAT w/0.9 (no module:unhook()!) +local function module_unhook(event, handler) + return module:unhook_object_event((hosts[module.host] or prosody).events, event, handler); +end + +function unload_script(script, is_reload) + script = resolve_script_path(script); + local script_info = loaded_scripts[script]; + if not script_info then + return; -- Script not loaded + end + local events_hooked = script_info.events_hooked; + for event_name, event_handler in pairs(events_hooked) do + module_unhook(event_name, event_handler); + events_hooked[event_name] = nil; + end + loaded_scripts[script] = nil; + if not is_reload then + module:log("debug", "Unloaded %s", script); + end +end + +-- Given a set of scripts (e.g. from config) figure out which ones need to +-- be loaded, which are already loaded but need unloading, and which to reload +function load_unload_scripts(script_list) + local wanted_scripts = script_list / resolve_script_path; + local currently_loaded = set.new(it.to_array(it.keys(loaded_scripts))); + local scripts_to_unload = currently_loaded - wanted_scripts; + for script in wanted_scripts do + -- If the script is already loaded, this is fine - it will + -- reload the script for us if the file has changed + load_script(script); + end + for script in scripts_to_unload do + unload_script(script); + end +end + +function module.load() + if not prosody.arg then return end -- Don't run in prosodyctl + local firewall_scripts = module:get_option_set("firewall_scripts", {}); + load_unload_scripts(firewall_scripts); + -- Replace contents of definitions table (shared) with active definitions + for k in it.keys(definitions) do definitions[k] = nil; end + for k,v in pairs(active_definitions) do definitions[k] = v; end +end + +function module.save() + return { active_definitions = active_definitions, loaded_scripts = loaded_scripts }; +end + +function module.restore(state) + active_definitions = state.active_definitions; + loaded_scripts = state.loaded_scripts; +end + +module:hook_global("config-reloaded", function () + load_unload_scripts(module:get_option_set("firewall_scripts", {})); +end); + +function module.command(arg) + if not arg[1] or arg[1] == "--help" then + require"util.prosodyctl".show_usage([[mod_firewall ]], [[Compile files with firewall rules to Lua code]]); + return 1; + end + local verbose = arg[1] == "-v"; + if verbose then table.remove(arg, 1); end + + if arg[1] == "test" then + table.remove(arg, 1); + return module:require("test")(arg); + end + + local serialize = require "util.serialization".serialize; + if verbose then + print("local logger = require \"util.logger\".init;"); + print(); + print("local function fire_event(name, data)\n\tmodule:fire_event(name, data)\nend"); + print(); + end + + for _, filename in ipairs(arg) do + filename = resolve_script_path(filename); + print("do -- File "..filename); + local chain_functions = assert(compile_firewall_rules(filename)); + if verbose then + print(); + print("local active_definitions = "..serialize(active_definitions)..";"); + print(); + end + local c = 0; + for chain, handler_code in pairs(chain_functions) do + c = c + 1; + print("---- Chain "..chain:gsub("_", " ")); + local chain_func_name = "chain_"..tostring(c).."_"..chain:gsub("%p", "_"); + if not verbose then + print(("%s = %s;"):format(chain_func_name, handler_code:sub(8))); + else + + print(("local %s = (%s)(active_definitions, fire_event, logger(%q));"):format(chain_func_name, handler_code:sub(8), filename)); + print(); + + local chain_definition = chains[chain]; + if chain_definition and chain_definition.type == "event" then + for _, event_name in ipairs(chain_definition) do + print(("module:hook(%q, %s, %d);"):format(event_name, chain_func_name, chain_definition.priority or 0)); + end + end + print(("module:hook(%q, %s, %d);"):format("firewall/chains/"..chain, chain_func_name, chain_definition.priority or 0)); + end + + print("---- End of chain "..chain); + print(); + end + print("end -- End of file "..filename); + end +end + + +-- Console + +local console_env = module:shared("/*/admin_shell/env"); + +console_env.firewall = {}; + +function console_env.firewall:mark(user_jid, mark_name) + local username, host = jid.split(user_jid); + if not username or not hosts[host] then + return nil, "Invalid JID supplied"; + elseif not idsafe(mark_name) then + return nil, "Invalid characters in mark name"; + end + if not module:context(host):fire_event("firewall/marked/user", { + username = session.username; + mark = mark_name; + timestamp = os.time(); + }) then + return nil, "Mark not set - is mod_firewall loaded on that host?"; + end + return true, "User marked"; +end + +function console_env.firewall:unmark(jid, mark_name) + local username, host = jid.split(user_jid); + if not username or not hosts[host] then + return nil, "Invalid JID supplied"; + elseif not idsafe(mark_name) then + return nil, "Invalid characters in mark name"; + end + if not module:context(host):fire_event("firewall/unmarked/user", { + username = session.username; + mark = mark_name; + }) then + return nil, "Mark not removed - is mod_firewall loaded on that host?"; + end + return true, "User unmarked"; +end diff --git a/prosody-modules/mod_firewall/scripts/jabberspam-simple-blocklist.pfw b/prosody-modules/mod_firewall/scripts/jabberspam-simple-blocklist.pfw new file mode 100644 index 00000000..bbc1c8f8 --- /dev/null +++ b/prosody-modules/mod_firewall/scripts/jabberspam-simple-blocklist.pfw @@ -0,0 +1,17 @@ +# This is a simple ruleset to block all traffic from servers +# on the JabberSPAM blocklist. Even traffic from existing user +# contacts will be blocked. +# +# Example config (make sure "firewall" is in modules_enabled): +# +# firewall_scripts = { "module:scripts/jabberspam-simple-blocklist.pfw" } +# +# For a more advanced ruleset, consider using spam-blocking.pfw +# and spam-blocklists.pfw. + +%LIST blocklist: https://cdn.jsdelivr.net/gh/jabberspam/blacklist/blacklist.txt + +::deliver + +CHECK LIST: blocklist contains $<@from|host> +BOUNCE=policy-violation (Your server is blocked due to spam) diff --git a/prosody-modules/mod_firewall/scripts/spam-blocking.pfw b/prosody-modules/mod_firewall/scripts/spam-blocking.pfw new file mode 100644 index 00000000..ed6d8e30 --- /dev/null +++ b/prosody-modules/mod_firewall/scripts/spam-blocking.pfw @@ -0,0 +1,182 @@ +#### Anti-spam ruleset ########################################### +# +# This script provides some foundational anti-spam rules. It aims +# to PASS stanzas that are definitely not spam, and DROP stanzas +# that are very likely spam. +# +# It does not do any form of content filtering, +# but this can be implemented by other scripts and +# modules as desired using the chains documented below. +# +# +# The following chains are available as extension +# points: +# +# ::user/spam_check_custom +# Apply additional rules to all stanzas before they are checked. +# Mainly useful to PASS stanzas that you do not want to be +# filtered. +# +# ::user/spam_check_message_custom +# Apply additional rules to messages from strangers, aiming to +# PASS stanzas that are not spam and jump to ::user/spam_reject +# for stanzas that are considered spam. +# +# ::user/spam_check_message_content_custom +# Apply additional rules to messages that may be spam, based on +# message content rules. These may contain more intensive rules, +# so are executed after all other checks. Rules should jump to +# ::user/spam_reject if a message is considered spam. +# +# ::user/spam_check_presence_custom +# Apply additional rules to presence that may be spam. +# +# ::user/spam_check_subscription_request_custom +# Apply additional rules to subscription requests. +# +# ::user/spam_handle_unknown_custom +# Override default handling of stanzas that weren't explicitly +# passed or rejected by the anti-spam checks. +# +# ::user/spam_reject_custom +# Override default handling of stanzas that have +# been recognised as spam (default is to bounce +# a policy-violation error). +# +################################################################## + +#### Entry point for all incoming stanzas ######################## +::deliver + +# Override this if you want to prevent certain stanzas going through +# the normal spam_check chain +JUMP_CHAIN=user/spam_check_custom + +# Run the default spam_check chain +JUMP_CHAIN=user/spam_check + +################################################################## + +#### General spam-checking rules (all stanzas) ################### +::user/spam_check + +# Pass stanzas that a user sends to their own account +TO SELF? +PASS. + +# Pass stanzas that are addressed to a valid full JID +TO FULL JID? +PASS. + +# Pass stanzas from contacts +SUBSCRIBED? +PASS. + +# Run extra rules that apply to messages only +KIND: message +JUMP CHAIN=user/spam_check_message + +# Run extra rules that apply to presence stanzas only +KIND: presence +JUMP CHAIN=user/spam_check_presence + +JUMP CHAIN=user/spam_handle_unknown + +# Default is to allow, override this with +# the 'user/spam_handle_unknown' chain +PASS. + +#### Rules for messages ########################################## +::user/spam_check_message + +JUMP CHAIN=user/spam_check_message_custom + +# Type 'groupchat' messages addressed to an offline full JID are harmless, +# and should be routed normally to handle MUC 'ghosts' correctly +TO: <*>@<*>/<*> +TYPE: groupchat +PASS. + +# Mediated MUC invitations are naturally from 'strangers' and have special +# handling. We lean towards accepting them, unless overridden by custom rules. +NOT FROM FULL JID? +INSPECT: {http://jabber.org/protocol/muc#user}x/invite +JUMP CHAIN=user/spam_check_muc_invite + +# Non-chat message types often generate pop-ups in clients, +# so we won't accept them from strangers +NOT TYPE: chat +JUMP CHAIN=user/spam_reject + +JUMP CHAIN=user/spam_check_message_content + +# This chain can be used by other scripts +# and modules that analyze message content +JUMP CHAIN=user/spam_check_message_content_custom + +################################################################## + +#### Rules for presence stanzas ################################## +::user/spam_check_presence + +JUMP CHAIN=user/spam_check_presence_custom + +# Presence to offline full JIDs is harmless, and should be routed +# normally to handle MUC 'ghosts' correctly +TO: <*>@<*>/<*> +PASS. + +# These may be received if rosters get out of sync and are harmless +# because they will not be routed to the client unless necessary +TYPE: unsubscribe|unsubscribed +PASS. + +# We don't want to receive presence from random strangers, +# but still allow subscription requests +NOT TYPE: subscribe|subscribed +DROP. + +# This chain can be used by other scripts +# and modules to filter subscription requests +JUMP CHAIN=user/spam_check_subscription_request + +JUMP CHAIN=user/spam_check_subscription_request_custom + +################################################################## + +#### Rules for MUC invitations ################################### + +::user/spam_check_muc_invite + +# This chain can be used to inspect the invitation and determine +# the appropriate action. Otherwise, we proceed with the default +# action below. +JUMP CHAIN=user/spam_check_muc_invite_custom + +# Allow mediated MUC invitations by default +PASS. + +#### Stanzas reaching this chain will be rejected ################ +::user/spam_reject + +# This chain can be used by other scripts +# and modules to override the default behaviour +# when rejecting spam stanzas +JUMP CHAIN=user/spam_reject_custom + +LOG=Rejecting suspected spam: $(stanza:top_tag()) +BOUNCE=policy-violation + +################################################################## + +#### Stanzas that may be spam, but we're not sure either way ##### +::user/spam_handle_unknown + +# This chain can be used by other scripts +# and modules to apply additional checks, or to +# override the default behaviour +JUMP CHAIN=user/spam_handle_unknown_custom + +#LOG=[debug] Spam check allowing: $(stanza:top_tag()) + +################################################################## diff --git a/prosody-modules/mod_firewall/scripts/spam-blocklists.pfw b/prosody-modules/mod_firewall/scripts/spam-blocklists.pfw new file mode 100644 index 00000000..e6f94b99 --- /dev/null +++ b/prosody-modules/mod_firewall/scripts/spam-blocklists.pfw @@ -0,0 +1,20 @@ +# This script depends on spam-blocking.pfw also being loaded +# Any traffic that is not explicitly blocked or allowed by other +# rules will be checked against the JabberSPAM server blocklist + +%LIST blocklist: https://cdn.jsdelivr.net/gh/jabberspam/blacklist/blacklist.txt + +::user/spam_handle_unknown_custom + +CHECK LIST: blocklist contains $<@from|host> +BOUNCE=policy-violation (Your server is blocked due to spam) + +::user/spam_check_muc_invite_custom + +# Check the server we received the invitation from +CHECK LIST: blocklist contains $<@from|host> +BOUNCE=policy-violation (Your server is blocked due to spam) + +# Check the inviter's JID against the blocklist, too +CHECK LIST: blocklist contains $<{http://jabber.org/protocol/muc#user}x/invite@from|host> +BOUNCE=policy-violation (Your server is blocked due to spam) diff --git a/prosody-modules/mod_firewall/scripts/spam-strip-xhtml.pfw b/prosody-modules/mod_firewall/scripts/spam-strip-xhtml.pfw new file mode 100644 index 00000000..38ca00f5 --- /dev/null +++ b/prosody-modules/mod_firewall/scripts/spam-strip-xhtml.pfw @@ -0,0 +1,5 @@ +# Strip XHTML-IM from messages received from strangers + +::user/spam_check_message_custom + +STRIP=html http://jabber.org/protocol/xhtml-im diff --git a/prosody-modules/mod_firewall/test.lib.lua b/prosody-modules/mod_firewall/test.lib.lua new file mode 100644 index 00000000..a72b0215 --- /dev/null +++ b/prosody-modules/mod_firewall/test.lib.lua @@ -0,0 +1,75 @@ +-- luacheck: globals load_unload_scripts +local set = require "util.set"; +local ltn12 = require "ltn12"; + +local xmppstream = require "util.xmppstream"; + +local function stderr(...) + io.stderr:write("** ", table.concat({...}, "\t", 1, select("#", ...)), "\n"); +end + +return function (arg) + require "net.http".request = function (url, ex, cb) + stderr("Making HTTP request to "..url); + local body_table = {}; + local ok, response_status, response_headers = require "ssl.https".request({ + url = url; + headers = ex.headers; + method = ex.body and "POST" or "GET"; + sink = ltn12.sink.table(body_table); + source = ex.body and ltn12.source.string(ex.body) or nil; + }); + stderr("HTTP response "..response_status); + cb(table.concat(body_table), response_status, { headers = response_headers }); + return true; + end; + + local stats_dropped, stats_passed = 0, 0; + + load_unload_scripts(set.new(arg)); + local stream_callbacks = { default_ns = "jabber:client" }; + + function stream_callbacks.streamopened(session) + session.notopen = nil; + end + function stream_callbacks.streamclosed() + end + function stream_callbacks.error(session, error_name, error_message) -- luacheck: ignore 212/session + stderr("Fatal error parsing XML stream: "..error_name..": "..tostring(error_message)) + assert(false); + end + function stream_callbacks.handlestanza(session, stanza) + if not module:fire_event("firewall/chains/deliver", { origin = session, stanza = stanza }) then + stats_passed = stats_passed + 1; + print(stanza); + print(""); + else + stats_dropped = stats_dropped + 1; + end + end + + local session = { notopen = true }; + function session.send(stanza) + stderr("Reply:", "\n"..tostring(stanza).."\n"); + end + local stream = xmppstream.new(session, stream_callbacks); + stream:feed(""); + local line_count = 0; + for line in io.lines() do + line_count = line_count + 1; + local ok, err = stream:feed(line.."\n"); + if not ok then + stderr("Fatal XML parse error on line "..line_count..": "..err); + return 1; + end + end + + stderr("Summary"); + stderr("-------"); + stderr(""); + stderr(stats_dropped + stats_passed, "processed"); + stderr(stats_passed, "passed"); + stderr(stats_dropped, "dropped"); + stderr(line_count, "input lines"); + stderr(""); +end diff --git a/prosody-modules/mod_muc_anonymize_moderation_actions/README.markdown b/prosody-modules/mod_muc_anonymize_moderation_actions/README.markdown new file mode 100644 index 00000000..62a1b323 --- /dev/null +++ b/prosody-modules/mod_muc_anonymize_moderation_actions/README.markdown @@ -0,0 +1,32 @@ + +# mod_muc_anonymize_moderation_actions + +This modules allows to anonymize affiliation and role changes in MUC rooms. + +Enabling this module on a MUC Virtualhost will add a settings in the roomconfig form. +When the feature is enabled, when a moderator changes the role or affiliation of an occupant (kick, ban, ...) their name will be removed from the broadcasted message, to not disclose who did the moderation action. + +This is particularly usefull to prevent some revenge when a moderator bans someone. + +This module is under AGPL-3.0 license. + +It was tested on Prosody 0.12.x. + +## Configuration + +Just enable the module on your MUC VirtualHost. +The feature will be accessible throught the room configuration form. + +You can tweak the position of the settings in the MUC configuration form using `anonymize_moderation_actions_form_position`. +This value will be passed as priority for the "muc-config-form" hook, so you can move field up by increasing the value, or down by decreasing the value. + +By default, the field will be between muc#roomconfig_changesubject and muc#roomconfig_moderatedroom (default value is `78`). + +``` lua +VirtualHost "muc.example.com" + modules_enabled = { "muc_anonymize_moderation_actions" } + anonymize_moderation_actions_form_position = 96 +``` diff --git a/prosody-modules/mod_muc_anonymize_moderation_actions/mod_muc_anonymize_moderation_actions.lua b/prosody-modules/mod_muc_anonymize_moderation_actions/mod_muc_anonymize_moderation_actions.lua new file mode 100644 index 00000000..89a41cc7 --- /dev/null +++ b/prosody-modules/mod_muc_anonymize_moderation_actions/mod_muc_anonymize_moderation_actions.lua @@ -0,0 +1,76 @@ +-- mod_muc_anonymize_moderation_actions +-- +-- SPDX-FileCopyrightText: 2024 John Livingston +-- SPDX-License-Identifier: AGPL-3.0-only + +-- form_position: the position in the room config form (this value will be passed as priority for the "muc-config-form" hook). +-- By default, field will be between muc#roomconfig_changesubject and muc#roomconfig_moderatedroom +local form_position = module:get_option_number("anonymize_moderation_actions_form_position") or 80-2; + +local function get_anonymize_moderation_actions(room) + return room._data.anonymize_moderation_actions or false; +end + +local function set_anonymize_moderation_actions(room, anonymize_moderation_actions) + anonymize_moderation_actions = anonymize_moderation_actions and true or nil; + if get_anonymize_moderation_actions(room) == anonymize_moderation_actions then return false; end + room._data.anonymize_moderation_actions = anonymize_moderation_actions; + return true; +end + +-- Config form declaration +local function add_form_option(event) + table.insert(event.form, { + name = "muc#roomconfig_anonymize_moderation_actions"; + type = "boolean"; + label = "Anonymize moderation actions"; + desc = "When this is enabled, moderation actions will be anonymized, to avoid disclosing who is banning/kicking/… occupants."; + value = get_anonymize_moderation_actions(event.room); + }); +end + +local function config_submitted(event) + set_anonymize_moderation_actions(event.room, event.value); +end + +local function remove_actor(event) + if (event.room and get_anonymize_moderation_actions(event.room)) then + event.actor = nil; + end +end + +local function remove_moderate_actor(event) + local room, announcement, tombstone = event.room, event.announcement, event.tombstone; + if not get_anonymize_moderation_actions(room) then + return; + end + + local moderated = announcement:find("{urn:xmpp:fasten:0}apply-to/{urn:xmpp:message-moderate:0}moderated"); + if moderated then + module:log("debug", "We must anonymize the moderation announcement for stanza %s", event.stanza_id); + -- FIXME: XEP-0245 has changed. + -- urn:xmpp:message-moderate:0 requires a "by" attribute + -- urn:xmpp:message-moderate:1 do not require the "by" attribute + -- So, for now, settings the room jid, as we only implement urn:xmpp:message-moderate:0. + moderated.attr.by = room.jid; + moderated:remove_children("occupant-id", "urn:xmpp:occupant-id:0"); + end + + if tombstone then + local moderated = tombstone:get_child("moderated", "urn:xmpp:message-moderate:0"); + if moderated then + module:log("debug", "We must anonymize the moderation tombstone for stanza %s", event.stanza_id); + -- FIXME: XEP-0245 has changed. + -- urn:xmpp:message-moderate:0 requires a "by" attribute + -- urn:xmpp:message-moderate:1 do not require the "by" attribute + -- So, for now, settings the room jid, as we only implement urn:xmpp:message-moderate:0. + moderated.attr.by = room.jid; + moderated:remove_children("occupant-id", "urn:xmpp:occupant-id:0"); + end + end +end + +module:hook("muc-config-submitted/muc#roomconfig_anonymize_moderation_actions", config_submitted); +module:hook("muc-config-form", add_form_option, form_position); +module:hook("muc-broadcast-presence", remove_actor); +module:hook("muc-moderate-message", remove_moderate_actor); diff --git a/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua b/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua index 1a217671..771498ad 100644 --- a/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua +++ b/prosody-modules/mod_muc_http_defaults/mod_muc_http_defaults.lua @@ -8,6 +8,7 @@ -- * "slow_mode_duration" -- * "mute_anonymous" -- * "moderation_delay" +-- * "anonymize_moderation_actions" -- These options are introduced in the Peertube livechat plugin. -- -- The "slow_mode_duration" comes with mod_muc_slow_mode. @@ -132,6 +133,9 @@ local function apply_config(room, settings) -- (and thus we don't need to broadcast changes) room._data.livechat_muc_terms = config.livechat_muc_terms; end + if (type(config.anonymize_moderation_actions) == "boolean") then + room._data.anonymize_moderation_actions = config.anonymize_moderation_actions; + end elseif config ~= nil then module:log("error", "Invalid config returned from API for %s: %q", room.jid, config); return nil, "format", { field = "config" }; diff --git a/prosody-modules/mod_muc_mam_search/README.markdown b/prosody-modules/mod_muc_mam_search/README.markdown new file mode 100644 index 00000000..0ca73b79 --- /dev/null +++ b/prosody-modules/mod_muc_mam_search/README.markdown @@ -0,0 +1,23 @@ + +# mod_muc_mam_search + +With this module you can make some advanced search in MAM (MUC Archive Management - XEP-0313). + +This module is part of peertube-plugin-livechat, and is under AGPL-3.0.-only license. +This module can work on any Prosody server (version >= 0.12.x). +This module is still experimental. + +## Configuration + +Just enable the module on your MUC component. + +The feature will be announced using Service Discovery (XEP-0030). + +TODO: document the Disco namespace. + +You can then query archives using query similar to those for XEP-0313. + +TODO: document available queries. diff --git a/prosody-modules/mod_muc_mam_search/archive.lib.lua b/prosody-modules/mod_muc_mam_search/archive.lib.lua new file mode 100644 index 00000000..d26be1e6 --- /dev/null +++ b/prosody-modules/mod_muc_mam_search/archive.lib.lua @@ -0,0 +1,25 @@ +-- SPDX-FileCopyrightText: 2024 John Livingston +-- SPDX-License-Identifier: AGPL-3.0-only + +-- FIXME: these imports are copied from mod_muc_mam, we should avoid that. +local log_all_rooms = module:get_option_boolean("muc_log_all_rooms", false); +local log_by_default = module:get_option_boolean("muc_log_by_default", true); + +-- FIXME: this function is copied from mod_muc_mam. We should not do so, and use directly the original function. +local function archiving_enabled(room) + if log_all_rooms then + module:log("debug", "Archiving all rooms"); + return true; + end + local enabled = room._data.archiving; + if enabled == nil then + module:log("debug", "Default is %s (for %s)", log_by_default, room.jid); + return log_by_default; + end + module:log("debug", "Logging in room %s is %s", room.jid, enabled); + return enabled; +end + +return { + archiving_enabled = archiving_enabled; +}; diff --git a/prosody-modules/mod_muc_mam_search/filter.lib.lua b/prosody-modules/mod_muc_mam_search/filter.lib.lua new file mode 100644 index 00000000..e202c54f --- /dev/null +++ b/prosody-modules/mod_muc_mam_search/filter.lib.lua @@ -0,0 +1,24 @@ +-- SPDX-FileCopyrightText: 2024 John Livingston +-- SPDX-License-Identifier: AGPL-3.0-only + +-- Perform the search criteria. +-- Returns true if the item match. +-- Note: there is a logical OR between search_from and search_occupant_id +local function item_match(id, item, search_from, search_occupant_id) + if (search_from ~= nil) then + if (search_from == item.attr.from) then + return true; + end + end + if (search_occupant_id ~= nil) then + local occupant_id = item:get_child("occupant-id", "urn:xmpp:occupant-id:0"); + if (occupant_id and occupant_id.attr.id == search_occupant_id) then + return true; + end + end + return false; +end + +return { + item_match = item_match; +}; diff --git a/prosody-modules/mod_muc_mam_search/mod_muc_mam_search.lua b/prosody-modules/mod_muc_mam_search/mod_muc_mam_search.lua new file mode 100644 index 00000000..6a92c2aa --- /dev/null +++ b/prosody-modules/mod_muc_mam_search/mod_muc_mam_search.lua @@ -0,0 +1,154 @@ +-- mod_muc_mam_search +-- +-- SPDX-FileCopyrightText: 2024 John Livingston +-- SPDX-License-Identifier: AGPL-3.0-only +-- + +local archiving_enabled = module:require("archive").archiving_enabled; +local item_match = module:require("filter").item_match; +local jid_split = require "util.jid".split; +local jid_bare = require "util.jid".bare; +local st = require "util.stanza"; +local datetime = require"util.datetime"; +local dataform = require "util.dataforms".new; +local get_form_type = require "util.dataforms".get_type; + +local mod_muc = module:depends"muc"; +local get_room_from_jid = mod_muc.get_room_from_jid; +local muc_log_archive = module:open_store("muc_log", "archive"); + +local xmlns_mam = "urn:xmpp:mam:2"; +local xmlns_mam_search = "urn:xmpp:mam:2#x-search"; +local xmlns_delay = "urn:xmpp:delay"; +local xmlns_forward = "urn:xmpp:forward:0"; + +module:hook("muc-disco#info", function(event) + if archiving_enabled(event.room) then + event.reply:tag("feature", {var=xmlns_mam_search}):up(); + end +end); + +local query_form = dataform { + { name = "FORM_TYPE"; type = "hidden"; value = xmlns_mam_search }; + { name = "from"; type = "jid-single" }; + { name = "occupant_id"; type = "text-single" }; +}; + +-- Serve form +module:hook("iq-get/bare/"..xmlns_mam_search..":query", function(event) + local origin, stanza = event.origin, event.stanza; + origin.send(st.reply(stanza):query(xmlns_mam_search):add_child(query_form:form())); + return true; +end); + +-- Handle archive queries +module:hook("iq-set/bare/"..xmlns_mam_search..":query", function(event) + local origin, stanza = event.origin, event.stanza; + local room_jid = stanza.attr.to; + local room_node = jid_split(room_jid); + local orig_from = stanza.attr.from; + local query = stanza.tags[1]; + + local room = get_room_from_jid(room_jid); + if not room then + origin.send(st.error_reply(stanza, "cancel", "item-not-found")) + return true; + end + local from = jid_bare(orig_from); + + -- Must be room admin or owner. + local from_affiliation = room:get_affiliation(from); + if (from_affiliation ~= "owner" and from_affiliation ~= "admin") then + origin.send(st.error_reply(stanza, "auth", "forbidden")) + return true; + end + + local qid = query.attr.queryid; + + -- Search query parameters + local search_from; + local search_occupant_id; + local form = query:get_child("x", "jabber:x:data"); + if form then + local form_type, err = get_form_type(form); + if not form_type then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Invalid dataform: "..err)); + return true; + elseif form_type ~= xmlns_mam_search then + origin.send(st.error_reply(stanza, "modify", "bad-request", "Unexpected FORM_TYPE, expected '"..xmlns_mam_search.."'")); + return true; + end + form, err = query_form:data(form); + if err then + origin.send(st.error_reply(stanza, "modify", "bad-request", select(2, next(err)))); + return true; + end + + search_from = form["from"]; + search_occupant_id = form["occupant_id"]; + else + module:log("debug", "Missing query form, forbidden.") + origin.send(st.error_reply(stanza, "modify", "bad-request", "Missing dataform")); + return true; + end + + -- TODO: handle RSM (pagination)? + module:log("debug", "Archive query by %s id=%s", from, qid); + + -- Load all the data! + local data, err = muc_log_archive:find(room_node, { + start = nil; ["end"] = nil; + with = "message tag, containing the original senders JID, unless the room makes this public. + -- but we only allow this feature to owner and admin, so we don't need to remove this. + + item.attr.to = nil; + item.attr.xmlns = "jabber:client"; + fwd_st:add_child(item); + + origin.send(fwd_st); + end + end + + origin.send(st.reply(stanza) + -- The result uses xmlns_mam and not xmlns_mam_search, so that the frontend handles this in the same way than xmlns_mam. + :tag("fin", { xmlns = xmlns_mam, complete = "true" })); + + -- That's all folks! + module:log("debug", "Archive query id=%s completed, %d items returned", qid or stanza.attr.id, count); + return true; +end); diff --git a/prosody-modules/mod_muc_moderation/README.markdown b/prosody-modules/mod_muc_moderation/README.markdown index 6c914443..4abb1f66 100644 --- a/prosody-modules/mod_muc_moderation/README.markdown +++ b/prosody-modules/mod_muc_moderation/README.markdown @@ -1,7 +1,9 @@ - +summary: Let moderators remove spam and abuse messages +--- + # Introduction This module implements [XEP-0425: Message Moderation]. @@ -16,7 +18,7 @@ role in the channel / group chat. Example [MUC component][doc:chatrooms] configuration: ``` {.lua} -VirtualHost "channels.example.com" "muc" +Component "channels.example.com" "muc" modules_enabled = { "muc_mam", "muc_moderation", @@ -25,20 +27,19 @@ modules_enabled = { # Compatibility -- Should work with Prosody 0.11.x and later. -- Tested with trunk rev `52c6dfa04dba`. -- Message tombstones requires a compatible storage module implementing - a new message replacement API. +- Basic functionality with Prosody 0.11.x and later +- Full functionality with Prosody 0.12.x and `internal` or `sql` + storage^[Replacing moderated messages with tombstones requires new storage API methods.] +- Works with [mod_storage_xmlarchive] ## Clients -- Tested with [Converse.js](https://conversejs.org/) - [v6.0.1](https://github.com/conversejs/converse.js/releases/tag/v6.0.1) +- [Converse.js](https://conversejs.org/) +- [Gajim](https://dev.gajim.org/gajim/gajim/-/issues/10107) +- [clix](https://code.zash.se/clix/rev/6c1953fbe0fa) ### Feature requests -- [Conv](https://github.com/iNPUTmice/Conversations/issues/3722)[ersa](https://github.com/iNPUTmice/Conversations/issues/3920)[tions](https://github.com/iNPUTmice/Conversations/issues/4227) -- [Dino](https://github.com/dino/dino/issues/1133) -- [Gajim](https://dev.gajim.org/gajim/gajim/-/issues/10107) -- [Poezio](https://lab.louiz.org/poezio/poezio/-/issues/3543) -- [Profanity](https://github.com/profanity-im/profanity/issues/1336) +- [Conversations](https://codeberg.org/iNPUTmice/Conversations/issues/20) +- [Dino](https://github.com/dino/dino/issues/1133) +- [Profanity](https://github.com/profanity-im/profanity/issues/1336) diff --git a/prosody-modules/mod_muc_moderation/mod_muc_moderation.lua b/prosody-modules/mod_muc_moderation/mod_muc_moderation.lua index 318175be..1e794841 100644 --- a/prosody-modules/mod_muc_moderation/mod_muc_moderation.lua +++ b/prosody-modules/mod_muc_moderation/mod_muc_moderation.lua @@ -27,6 +27,7 @@ end -- Namespaces local xmlns_fasten = "urn:xmpp:fasten:0"; local xmlns_moderate = "urn:xmpp:message-moderate:0"; +local xmlns_occupant_id = "urn:xmpp:occupant-id:0"; local xmlns_retract = "urn:xmpp:message-retract:0"; -- Discovering support @@ -34,36 +35,17 @@ module:hook("muc-disco#info", function (event) event.reply:tag("feature", { var = xmlns_moderate }):up(); end); --- Main handling -module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) - local stanza, origin = event.stanza, event.origin; +-- TODO error registry, requires Prosody 0.12+ - -- Collect info we need - local apply_to = stanza.tags[1]; - local moderate_tag = apply_to:get_child("moderate", xmlns_moderate); - if not moderate_tag then return end -- some other kind of fastening? - - local reason = moderate_tag:get_child_text("reason"); - local retract = moderate_tag:get_child("retract", xmlns_retract); - - local room_jid = stanza.attr.to; +-- moderate : function (string, string, string, boolean, string) : boolean, enum, enum, string +local function moderate(actor, room_jid, stanza_id, retract, reason) local room_node = jid.split(room_jid); local room = mod_muc.get_room_from_jid(room_jid); - local stanza_id = apply_to.attr.id; - - -- Permissions - local actor = stanza.attr.from; + -- Permissions is based on role, which is a property of a current occupant, + -- so check if the actor is an occupant, otherwise if they have a reserved + -- nickname that can be used to retrieve the role. local actor_nick = room:get_occupant_jid(actor); - local affiliation = room:get_affiliation(actor); - -- Retrieve their current role, iff they are in the room, otherwise what they - -- would have based on affiliation. - local role = room:get_role(actor_nick) or room:get_default_role(affiliation); - if valid_roles[role or "none"] < valid_roles.moderator then - origin.send(st.error_reply(stanza, "auth", "forbidden", "You need a role of at least 'moderator'")); - return true; - end - if not actor_nick then local reserved_nickname = room:get_affiliation_data(jid.bare(actor), "reserved_nickname"); if reserved_nickname then @@ -71,6 +53,14 @@ module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) end end + -- Retrieve their current role, iff they are in the room, otherwise what they + -- would have based on affiliation. + local affiliation = room:get_affiliation(actor); + local role = room:get_role(actor_nick) or room:get_default_role(affiliation); + if valid_roles[role or "none"] < valid_roles.moderator then + return false, "auth", "forbidden", "You need a role of at least 'moderator'"; + end + -- Original stanza to base tombstone on local original, err; if muc_log_archive.get then @@ -84,20 +74,26 @@ module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) end end end + if not original then if err == "item-not-found" then - origin.send(st.error_reply(stanza, "modify", "item-not-found")); + return false, "modify", "item-not-found"; else - origin.send(st.error_reply(stanza, "wait", "internal-server-error")); + return false, "wait", "internal-server-error"; end - return true; end + local actor_occupant = room:get_occupant_by_real_jid(actor) or room:new_occupant(jid.bare(actor), actor_nick); local announcement = st.message({ from = room_jid, type = "groupchat", id = id.medium(), }) :tag("apply-to", { xmlns = xmlns_fasten, id = stanza_id }) :tag("moderated", { xmlns = xmlns_moderate, by = actor_nick }) + if room.get_occupant_id then + -- This isn't a regular broadcast message going through the events occupant_id.lib hooks so we do this here + announcement:add_child(st.stanza("occupant-id", { xmlns = xmlns_occupant_id; id = room:get_occupant_id(actor_occupant) })); + end + if retract then announcement:tag("retract", { xmlns = xmlns_retract }):up(); end @@ -106,8 +102,16 @@ module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) announcement:text_tag("reason", reason); end + local moderated_occupant_id = original:get_child("occupant-id", xmlns_occupant_id); + if room.get_occupant_id and moderated_occupant_id then + announcement:add_direct_child(moderated_occupant_id); + end + + announcement:reset(); + + local tombstone = nil; if muc_log_archive.set and retract then - local tombstone = st.message({ from = original.attr.from, type = "groupchat", id = original.attr.id }) + tombstone = st.message({ from = original.attr.from, type = "groupchat", id = original.attr.id }) :tag("moderated", { xmlns = xmlns_moderate, by = actor_nick }) :tag("retracted", { xmlns = xmlns_retract, stamp = dt.datetime() }):up(); @@ -115,10 +119,39 @@ module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) tombstone:text_tag("reason", reason); end + if room.get_occupant_id then + if actor_occupant then + tombstone:add_child(st.stanza("occupant-id", { xmlns = xmlns_occupant_id; id = room:get_occupant_id(actor_occupant) })); + end + + if moderated_occupant_id then + -- Copy occupant id from moderated message + tombstone:add_direct_child(moderated_occupant_id); + end + end + tombstone:reset(); + end + + -- fire an event, that can be used to cancel the moderation, or modify stanzas. + local event = { + room = room; + announcement = announcement; + tombstone = tombstone; + stanza_id = stanza_id; + retract = retract; + reason = reason; + actor = actor; + actor_nick = actor_nick; + }; + if module:fire_event("muc-moderate-message", event) then + -- TODO: allow to change the error message? + return false, "wait", "internal-server-error"; + end + + if tombstone then local was_replaced = muc_log_archive:set(room_node, stanza_id, tombstone); if not was_replaced then - origin.send(st.error_reply(stanza, "wait", "internal-server-error")); - return true; + return false, "wait", "internal-server-error"; end end @@ -126,6 +159,32 @@ module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) module:log("info", "Message with id '%s' in room %s moderated by %s, reason: %s", stanza_id, room_jid, actor, reason); room:broadcast_message(announcement); + return true; +end + +-- Main handling +module:hook("iq-set/bare/" .. xmlns_fasten .. ":apply-to", function (event) + local stanza, origin = event.stanza, event.origin; + + local actor = stanza.attr.from; + local room_jid = stanza.attr.to; + + -- Collect info we need + local apply_to = stanza.tags[1]; + local moderate_tag = apply_to:get_child("moderate", xmlns_moderate); + if not moderate_tag then return end -- some other kind of fastening? + + local reason = moderate_tag:get_child_text("reason"); + local retract = moderate_tag:get_child("retract", xmlns_retract); + + local stanza_id = apply_to.attr.id; + + local ok, error_type, error_condition, error_text = moderate(actor, room_jid, stanza_id, retract, reason); + if not ok then + origin.send(st.error_reply(stanza, error_type, error_condition, error_text)); + return true; + end + origin.send(st.reply(stanza)); return true; end); diff --git a/prosody-modules/mod_muc_moderation_delay/README.md b/prosody-modules/mod_muc_moderation_delay/README.markdown similarity index 93% rename from prosody-modules/mod_muc_moderation_delay/README.md rename to prosody-modules/mod_muc_moderation_delay/README.markdown index 7c831885..2b1d7812 100644 --- a/prosody-modules/mod_muc_moderation_delay/README.md +++ b/prosody-modules/mod_muc_moderation_delay/README.markdown @@ -6,8 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only With this module, you can apply a delay to groupchat messages delivery, so that room moderators can moderate them before other participants receives them. -This module is part of peertube-plugin-livechat, and is under the same LICENSE. +This module is part of peertube-plugin-livechat, and is under AGPL-3.0.-only license. This module can work on any Prosody server (version >= 0.12.x). +This module is still experimental. ## Configuration diff --git a/prosody-modules/mod_muc_poll/README.md b/prosody-modules/mod_muc_poll/README.md index 072a19a2..be61bb41 100644 --- a/prosody-modules/mod_muc_poll/README.md +++ b/prosody-modules/mod_muc_poll/README.md @@ -13,16 +13,16 @@ There will probably be a XEP proposal for this module behaviour. When done, this ## Configuration Just enable the module on your MUC component. -All above configurations are optional. +All following configurations are optional. -## poll_groupchat_votes_priority +### poll_groupchat_votes_priority The priority for the hook that will take into account votes. You can change this, if you have some specific hook that should be done after/before counting votes (slow mode, firewall, ...). Default: 40 (Prosody checks visitor role with priority of 50, we want this to be after). -## Strings +### Strings You can change some defaults strings, if you want for example to localize the poll messages. Here are the existing strings and default values: diff --git a/prosody-modules/mod_muc_slow_mode/README.md b/prosody-modules/mod_muc_slow_mode/README.markdown similarity index 100% rename from prosody-modules/mod_muc_slow_mode/README.md rename to prosody-modules/mod_muc_slow_mode/README.markdown diff --git a/prosody-modules/mod_pubsub_peertubelivechat/README.md b/prosody-modules/mod_pubsub_peertubelivechat/README.md index b0525942..62d143cc 100644 --- a/prosody-modules/mod_pubsub_peertubelivechat/README.md +++ b/prosody-modules/mod_pubsub_peertubelivechat/README.md @@ -7,13 +7,16 @@ SPDX-License-Identifier: AGPL-3.0-only This module is a custom module that provide some pubsub services associated to a MUC room. This module is entended to be used in the peertube-plugin-livechat project. -For each MUC room, there will be an associated pubsub node. -This node in only accessible by the ROOM admin/owner. +For each MUC room, there will be a associated pubsub nodes. +These nodes are only accessible by the ROOM admins/owners. -This node can contains various objects: +Here are a description of existing nodes, and objects they can contain: -* task lists -* tasks +* livechat-tasks: + * task lists + * tasks +* livechat-notes: + * notes * ... (more to come) These objects are meant te be shared between admin/owner. diff --git a/prosody-modules/mod_pubsub_peertubelivechat/mod_pubsub_peertubelivechat.lua b/prosody-modules/mod_pubsub_peertubelivechat/mod_pubsub_peertubelivechat.lua index 95465bf1..95afee23 100644 --- a/prosody-modules/mod_pubsub_peertubelivechat/mod_pubsub_peertubelivechat.lua +++ b/prosody-modules/mod_pubsub_peertubelivechat/mod_pubsub_peertubelivechat.lua @@ -15,6 +15,7 @@ -- Implemented nodes: -- * livechat-tasks: contains tasklist and task items, specific to livechat plugin. +-- * livechat-notes: contains notes, specific to livechat plugin. -- There are some other tricks in this module: -- * unsubscribing users that have left the room (the front-end will subscribe again when needed) @@ -39,16 +40,14 @@ local xmlns_pubsub = "http://jabber.org/protocol/pubsub"; local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event"; local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner"; local xmlns_tasklist = "urn:peertube-plugin-livechat:tasklist"; -local xmlns_task = "urn:peertube-plugin-livechat:task" +local xmlns_task = "urn:peertube-plugin-livechat:task"; +local xmlns_note = "urn:peertube-plugin-livechat:note"; local lib_pubsub = module:require "pubsub"; local mod_muc = module:depends"muc"; local get_room_from_jid = mod_muc.get_room_from_jid; -local muc_util = module:require "muc/util"; -local valid_roles = muc_util.valid_roles; - -- room_jid => object passed to module:add_items() local mep_service_items = {}; @@ -389,4 +388,5 @@ end); module:hook("muc-disco#info", function (event) event.reply:tag("feature", { var = xmlns_task }):up(); event.reply:tag("feature", { var = xmlns_tasklist }):up(); + event.reply:tag("feature", { var = xmlns_note }):up(); end); diff --git a/server/lib/configuration/channel/sanitize.ts b/server/lib/configuration/channel/sanitize.ts index 3aa45480..8a72f9ed 100644 --- a/server/lib/configuration/channel/sanitize.ts +++ b/server/lib/configuration/channel/sanitize.ts @@ -38,6 +38,7 @@ async function sanitizeChannelConfigurationOptions ( const moderationData = data.moderation ?? {} // comes with livechat 10.3.0 moderationData.delay ??= 0 + moderationData.anonymize ??= false // comes with livechat 11.0.0 // mute not present in livechat <= 10.2.0 const mute = data.mute ?? {} @@ -73,7 +74,8 @@ async function sanitizeChannelConfigurationOptions ( anonymous: _readBoolean(mute, 'anonymous') }, moderation: { - delay: _readInteger(moderationData, 'delay', 0, 60) + delay: _readInteger(moderationData, 'delay', 0, 60), + anonymize: _readBoolean(moderationData, 'anonymize') } } if (terms !== undefined) { diff --git a/server/lib/configuration/channel/storage.ts b/server/lib/configuration/channel/storage.ts index d5dbdf90..d9ecc1fe 100644 --- a/server/lib/configuration/channel/storage.ts +++ b/server/lib/configuration/channel/storage.ts @@ -54,7 +54,8 @@ function getDefaultChannelConfigurationOptions (_options: RegisterServerOptions) anonymous: false }, moderation: { - delay: 0 + delay: 0, + anonymize: false }, terms: undefined } diff --git a/server/lib/external-auth/oidc.ts b/server/lib/external-auth/oidc.ts index a694d00e..b2780450 100644 --- a/server/lib/external-auth/oidc.ts +++ b/server/lib/external-auth/oidc.ts @@ -87,8 +87,8 @@ class ExternalAuthOIDC { private readonly redirectUrl: string private readonly connectUrl: string private readonly externalVirtualhost: string - private readonly avatarsDir: string - private readonly avatarsFiles: string[] + private readonly avatarsDir: string | undefined + private readonly avatarsFiles: string[] | undefined private readonly encryptionOptions = { algorithm: 'aes256' as string, @@ -129,8 +129,8 @@ class ExternalAuthOIDC { connectUrl: string redirectUrl: string externalVirtualhost: string - avatarsDir: string - avatarsFiles: string[] + avatarsDir?: string + avatarsFiles?: string[] }) { this.logger = { debug: (s) => params.logger.debug('[ExternalAuthOIDC] ' + s), @@ -591,8 +591,8 @@ class ExternalAuthOIDC { */ private async getRandomAvatar (): Promise { try { - if (!this.avatarsDir || !this.avatarsFiles.length) { - throw new Error('Seems there is no default avatars') + if (!this.avatarsDir || !this.avatarsFiles?.length) { + return undefined } const file = this.avatarsFiles[Math.floor(Math.random() * this.avatarsFiles.length)] diff --git a/server/lib/firewall/config.ts b/server/lib/firewall/config.ts new file mode 100644 index 00000000..e29eaa88 --- /dev/null +++ b/server/lib/firewall/config.ts @@ -0,0 +1,210 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { RegisterServerOptions } from '@peertube/peertube-types' +import type { AdminFirewallConfiguration } from '../../../shared/lib/types' +import * as path from 'path' +import * as fs from 'fs' +import { + firewallNameRegexp, maxFirewallFileSize, maxFirewallFiles, maxFirewallNameLength +} from '../../../shared/lib/admin-firewall' + +/** + * Indicates if the firewall configuration can be changed in the Peertube web interface. + * Sys admins can disable this feature by creating a special file in the plugin folder. + * @param options Peertube server options + */ +export async function canEditFirewallConfig (options: RegisterServerOptions): Promise { + const peertubeHelpers = options.peertubeHelpers + const logger = peertubeHelpers.logger + if (!peertubeHelpers.plugin) { + return false + } + + const filepath = path.resolve(peertubeHelpers.plugin.getDataDirectoryPath(), 'disable_mod_firewall_editing') + try { + // Testing if file exist by reading it. + await fs.promises.readFile(filepath) + return false + } catch (err: any) { + if (('code' in err) && err.code === 'ENOENT') { + // File does not exist + return true + } + logger.error(err) + // Here it is safer to disable the editing... + return false + } +} + +/** + * Returns the list of mod_firewall configuration files. + * @param options: Peertube server options. + * @param dir the path to the directory containing these configuration files. + * @param includeDisabled if true, disabled files are included in the results. + */ +export async function listModFirewallFiles ( + options: RegisterServerOptions, + dir: string, + includeDisabled?: boolean +): Promise { + try { + const files = (await fs.promises.readdir(dir, { withFileTypes: true })).filter(file => { + if (!file.isFile()) { + return false + } + + if ( + file.name.endsWith('.pfw') && + // we only load valid names, to avoid having files that could not be edited from frontend + firewallNameRegexp.test(file.name.substring(0, file.name.length - 4)) + ) { + return true + } + + if ( + includeDisabled && + file.name.endsWith('.pfw.disabled') && + firewallNameRegexp.test(file.name.substring(0, file.name.length - 13)) + ) { + return true + } + + return false + }) + + return files.map(f => path.join(dir, f.name)).sort() + } catch (err) { + // should be that the directory does not exists + return [] + } +} + +/** + * Returns the modFirewall configuration. + * @param options Peertube server options + * @param dir the path to the directory containing these configuration files. + * @throws will throw an error if it can't read any of the configuration file. + */ +export async function getModFirewallConfig ( + options: RegisterServerOptions, + dir: string +): Promise { + const filePaths = await listModFirewallFiles(options, dir, true) + + const files = [] + for (const filePath of filePaths) { + const content = (await fs.promises.readFile(filePath)).toString() + const name = path.basename(filePath).replace(/\.pfw(\.disabled)?$/, '') + files.push({ + name, + content, + enabled: !filePath.endsWith('.disabled') + }) + } + + const enabled = (await options.settingsManager.getSetting('prosody-firewall-enabled')) === true + + return { + enabled, + files + } +} + +/** + * Sanitize any data received from the frontend, to store in modFirewall configuration. + * Throws an exception if data is invalid. + * @param options Peertube server options + * @param data Incoming data + */ +export async function sanitizeModFirewallConfig ( + options: RegisterServerOptions, + data: any +): Promise { + if (typeof data !== 'object') { + throw new Error('Invalid data type') + } + if (!Array.isArray(data.files)) { + throw new Error('Invalid data.files') + } + + if (data.files.length > maxFirewallFiles) { + throw new Error('Too many files') + } + + const files: AdminFirewallConfiguration['files'] = [] + for (const entry of data.files) { + if (typeof entry !== 'object') { + throw new Error('Invalid data in data.files') + } + if (typeof entry.enabled !== 'boolean') { + throw new Error('Invalid data in data.files (enabled)') + } + if (typeof entry.name !== 'string') { + throw new Error('Invalid data in data.files (name)') + } + if (typeof entry.content !== 'string') { + throw new Error('Invalid data in data.files (content)') + } + + if (entry.name.length > maxFirewallNameLength || !firewallNameRegexp.test(entry.name)) { + throw new Error('Invalid name in data.files') + } + if (entry.content.length > maxFirewallFileSize) { + throw new Error('File content too big in data.files') + } + + files.push({ + enabled: entry.enabled, + name: entry.name, + content: entry.content + }) + } + + const result = { + enabled: !!data.enabled, // this is not saved, so no need to check type. + files + } + + return result +} + +/** + * Saves the modFirewall configuration. + * FIXME: currently, if the save fails on one file, remaining files will not be saved. So there is a risk of data loss. + * @param options Peertube server options + * @param dir the path to the directory containing these configuration files. + * @param config the configuration to save + * @throws will throw an error if it can't read any of the configuration file. + */ +export async function saveModFirewallConfig ( + options: RegisterServerOptions, + dir: string, + config: AdminFirewallConfiguration +): Promise { + const logger = options.peertubeHelpers.logger + + const previousFiles = await listModFirewallFiles(options, dir, true) + + logger.debug('[mod-firewall-lib] Creating the ' + dir + ' directory.') + await fs.promises.mkdir(dir, { recursive: true }) + + const seen = new Map() + for (const f of config.files) { + const filePath = path.join( + dir, + f.name + '.pfw' + (f.enabled ? '' : '.disabled') + ) + logger.info('[mod-firewall-lib] Saving ' + filePath) + await fs.promises.writeFile(filePath, f.content) + seen.set(filePath, true) + } + + // Removing deprecated files: + for (const p of previousFiles) { + if (seen.has(p)) { continue } + logger.info('[mod-firewall-lib] Deleting deprecated file ' + p) + await fs.promises.rm(p) + } +} diff --git a/server/lib/middlewares/is-admin.ts b/server/lib/middlewares/is-admin.ts new file mode 100644 index 00000000..d220513c --- /dev/null +++ b/server/lib/middlewares/is-admin.ts @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { RegisterServerOptions } from '@peertube/peertube-types' +import type { Request, Response, NextFunction } from 'express' +import type { RequestPromiseHandler } from './async' +import { isUserAdmin } from '../helpers' + +/** + * Returns a middleware handler to check if advanced configuration is not disabled + * @param options Peertube server options + * @returns middleware function + */ +function checkUserIsAdminMiddleware (options: RegisterServerOptions): RequestPromiseHandler { + return async (req: Request, res: Response, next: NextFunction) => { + const logger = options.peertubeHelpers.logger + if (!await isUserAdmin(options, res)) { + logger.warn('Current user tries to access a page only allowed for admins, and has no right.') + res.sendStatus(403) + return + } + + logger.debug('User is admin, can access the page..') + next() + } +} + +export { + checkUserIsAdminMiddleware +} diff --git a/server/lib/migration/settings.ts b/server/lib/migration/settings.ts index b1a19d63..f73f729f 100644 --- a/server/lib/migration/settings.ts +++ b/server/lib/migration/settings.ts @@ -3,11 +3,77 @@ // SPDX-License-Identifier: AGPL-3.0-only import type { RegisterServerOptions } from '@peertube/peertube-types' +import { pluginShortName } from '../helpers' async function migrateSettings (options: RegisterServerOptions): Promise { const logger = options.peertubeHelpers.logger logger.info('Checking if there is a migration script to launch...') // 2022-10-10: as we removed the «chat-type» settings, there is no migration needed for now. + // 2024-09-02: concord theme was removed from ConverseJS, must change if used. + await _migrateConverseTheme(options) +} + +async function _migrateConverseTheme (options: RegisterServerOptions): Promise { + const peertubeHelpers = options.peertubeHelpers + const logger = peertubeHelpers.logger + // NB: we cant use safely settingsManager.getSetting, because settings are not registered yet. + logger.info('Checking if we need to migrate converse-theme') + if (!/^[-a-z]+$/.test(pluginShortName)) { + // to prevent sql injection... be sure there is no special char here. + throw new Error(`Wrong pluginShortName '${pluginShortName}'`) + } + const [results] = await peertubeHelpers.database.query( + 'SELECT "settings" FROM "plugin"' + + ' WHERE "plugin"."name" = :pluginShortName', + { + replacements: { + pluginShortName + } + } + ) + if (!Array.isArray(results)) { + throw new Error('_migrateConverseTheme: query result is not an array.') + } + if (results.length === 0) { + logger.error('Plugin not found in database') + return + } + if (results.length > 1) { + logger.error('Multiple lines for plugin in database, dont know which one to migrate... Aborting.') + return + } + const settings = results[0].settings + if (!settings) { + logger.info('Plugin settings are empty in database, no migration needed.') + return + } + if (typeof settings !== 'object') { + logger.error('Plugin settings in database seems to be invalid json') + return + } + if (!('converse-theme' in settings)) { + logger.debug('The setting converse-theme is not here, no need to migrate.') + return + } + if (settings['converse-theme'] !== 'concord') { + logger.debug('The setting converse-theme is not set to concord, no need to migrate.') + return + } + + logger.info('The setting converse-theme is set to concord, we must replace by peertube..') + await peertubeHelpers.database.query( + 'UPDATE "plugin" ' + + ' SET "settings" = "settings" || :value ' + + ' WHERE "name" = :pluginShortName', + { + replacements: { + pluginShortName, + value: JSON.stringify({ + 'converse-theme': 'peertube' + }) + } + } + ) } export { diff --git a/server/lib/prosody/config.ts b/server/lib/prosody/config.ts index c132dfa8..25557f3b 100644 --- a/server/lib/prosody/config.ts +++ b/server/lib/prosody/config.ts @@ -18,6 +18,7 @@ import { getRemoteServerInfosDir } from '../federation/storage' import { BotConfiguration } from '../configuration/bot' import { debugMucAdmins } from '../debug' import { ExternalAuthOIDC } from '../external-auth/oidc' +import { listModFirewallFiles } from '../firewall/config' async function getWorkingDir (options: RegisterServerOptions): Promise { const peertubeHelpers = options.peertubeHelpers @@ -102,14 +103,23 @@ async function getProsodyFilePaths (options: RegisterServerOptions): Promise() @@ -356,7 +368,9 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise { await initPromoteApiRouter(options, router) await initEmojisRouter(options, router) + await initAdminFirewallApiRouter(options, router) + if (isDebugMode(options)) { // Only add this route if the debug mode is enabled at time of the server launch. // Note: the isDebugMode will be tested again when the API is called. diff --git a/server/lib/routers/api/admin/firewall.ts b/server/lib/routers/api/admin/firewall.ts new file mode 100644 index 00000000..502d2738 --- /dev/null +++ b/server/lib/routers/api/admin/firewall.ts @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +import type { RegisterServerOptions } from '@peertube/peertube-types' +import type { Router, Request, Response, NextFunction } from 'express' +import type { AdminFirewallConfiguration } from '../../../../../shared/lib/types' +import { asyncMiddleware, RequestPromiseHandler } from '../../../middlewares/async' +import { checkUserIsAdminMiddleware } from '../../../middlewares/is-admin' +import { + getModFirewallConfig, sanitizeModFirewallConfig, saveModFirewallConfig, canEditFirewallConfig +} from '../../../firewall/config' +import { getProsodyFilePaths, writeProsodyConfig } from '../../../prosody/config' +import { reloadProsody } from '../../../prosody/ctl' + +function canEditFirewallConfigMiddleware (options: RegisterServerOptions): RequestPromiseHandler { + return async (req: Request, res: Response, next: NextFunction) => { + if (!await canEditFirewallConfig(options)) { + options.peertubeHelpers.logger.info('Firewall configuration editing is disabled') + res.sendStatus(403) + return + } + next() + } +} + +async function initAdminFirewallApiRouter (options: RegisterServerOptions, router: Router): Promise { + const logger = options.peertubeHelpers.logger + + router.get('/admin/firewall', asyncMiddleware([ + checkUserIsAdminMiddleware(options), + canEditFirewallConfigMiddleware(options), + async (req: Request, res: Response, _next: NextFunction): Promise => { + try { + const prosodyPaths = await getProsodyFilePaths(options) + const result: AdminFirewallConfiguration = await getModFirewallConfig(options, prosodyPaths.modFirewallFiles) + res.status(200) + res.json(result) + } catch (err) { + options.peertubeHelpers.logger.error(err) + res.sendStatus(500) + } + } + ])) + + router.post('/admin/firewall', asyncMiddleware([ + checkUserIsAdminMiddleware(options), + canEditFirewallConfigMiddleware(options), + async (req: Request, res: Response, _next: NextFunction): Promise => { + try { + const prosodyPaths = await getProsodyFilePaths(options) + + let data: AdminFirewallConfiguration + try { + data = await sanitizeModFirewallConfig(options, req.body) + } catch (err) { + logger.error(err) + res.sendStatus(400) + return + } + + await saveModFirewallConfig(options, prosodyPaths.modFirewallFiles, data) + + logger.info('Just saved a new mod_firewall const, must rewrite Prosody configuration file, and reload Prosody.') + await writeProsodyConfig(options) + await reloadProsody(options) + + const result: AdminFirewallConfiguration = await getModFirewallConfig(options, prosodyPaths.modFirewallFiles) + res.status(200) + res.json(result) + } catch (err) { + options.peertubeHelpers.logger.error(err) + res.sendStatus(500) + } + } + ])) +} + +export { + initAdminFirewallApiRouter +} diff --git a/server/lib/routers/api/room.ts b/server/lib/routers/api/room.ts index ec2d0da8..a3efbad6 100644 --- a/server/lib/routers/api/room.ts +++ b/server/lib/routers/api/room.ts @@ -39,6 +39,7 @@ interface RoomDefaults { mute_anonymous?: boolean livechat_muc_terms?: string moderation_delay?: number + anonymize_moderation_actions?: boolean } affiliations?: Affiliations } @@ -54,7 +55,8 @@ async function _getChannelSpecificOptions ( slow_mode_duration: channelOptions.slowMode.duration, mute_anonymous: channelOptions.mute.anonymous, livechat_muc_terms: channelOptions.terms, - moderation_delay: channelOptions.moderation.delay + moderation_delay: channelOptions.moderation.delay, + anonymize_moderation_actions: channelOptions.moderation.anonymize } } diff --git a/server/lib/settings.ts b/server/lib/settings.ts index 3b82fdde..ca45e7a9 100644 --- a/server/lib/settings.ts +++ b/server/lib/settings.ts @@ -11,9 +11,10 @@ import { ExternalAuthOIDC, ExternalAuthOIDCType } from './external-auth/oidc' import { Emojis } from './emojis' import { LivechatProsodyAuth } from './prosody/auth' import { loc } from './loc' +import { canEditFirewallConfig } from './firewall/config' const escapeHTML = require('escape-html') -type AvatarSet = 'sepia' | 'cat' | 'bird' | 'fenec' | 'abstract' | 'legacy' | 'nctv' +type AvatarSet = 'sepia' | 'cat' | 'bird' | 'fenec' | 'abstract' | 'legacy' | 'none' | 'nctv' async function initSettings (options: RegisterServerOptions): Promise { const { peertubeHelpers, settingsManager } = options @@ -27,7 +28,7 @@ async function initSettings (options: RegisterServerOptions): Promise { initAdvancedChannelCustomizationSettings(options) initChatBehaviourSettings(options) initThemingSettings(options) - initChatServerAdvancedSettings(options) + await initChatServerAdvancedSettings(options) await ExternalAuthOIDC.initSingletons(options) const loadOidcs = (): void => { @@ -511,7 +512,8 @@ function initThemingSettings ({ registerSetting }: RegisterServerOptions): void { value: 'bird', label: loc('avatar_set_option_bird') }, { value: 'fenec', label: loc('avatar_set_option_fenec') }, { value: 'abstract', label: loc('avatar_set_option_abstract') }, - { value: 'legacy', label: loc('avatar_set_option_legacy') } + { value: 'legacy', label: loc('avatar_set_option_legacy') }, + { value: 'none', label: loc('avatar_set_option_none') } ] as Array<{ value: AvatarSet label: string @@ -525,9 +527,9 @@ function initThemingSettings ({ registerSetting }: RegisterServerOptions): void default: 'peertube' as ConverseJSTheme, private: false, options: [ - { value: 'peertube', label: loc('peertube') }, - { value: 'default', label: loc('default') }, - { value: 'concord', label: loc('concord') } + { value: 'peertube', label: loc('converse_theme_option_peertube') }, + { value: 'default', label: loc('converse_theme_option_default') }, + { value: 'cyberpunk', label: loc('converse_theme_option_cyberpunk') } ] as Array<{value: ConverseJSTheme, label: string}>, descriptionHTML: loc('converse_theme_description') }) @@ -555,7 +557,9 @@ function initThemingSettings ({ registerSetting }: RegisterServerOptions): void * Registers settings related to the "Chat server advanded settings" section. * @param param0 server options */ -function initChatServerAdvancedSettings ({ registerSetting }: RegisterServerOptions): void { +async function initChatServerAdvancedSettings (options: RegisterServerOptions): Promise { + const { registerSetting } = options + registerSetting({ name: 'prosody-advanced', type: 'html', @@ -723,6 +727,23 @@ function initChatServerAdvancedSettings ({ registerSetting }: RegisterServerOpti private: true, descriptionHTML: loc('prosody_components_list_description') }) + + registerSetting({ + name: 'prosody-firewall-enabled', + label: loc('prosody_firewall_label'), + type: 'input-checkbox', + default: false, + private: true, + descriptionHTML: loc('prosody_firewall_description') + }) + if (await canEditFirewallConfig(options)) { + registerSetting({ + type: 'html', + name: 'prosody-firewall-configure-button', + private: true, + descriptionHTML: loc('prosody_firewall_configure_button') + }) + } } export { diff --git a/shared/lib/admin-firewall.ts b/shared/lib/admin-firewall.ts new file mode 100644 index 00000000..36d4fabf --- /dev/null +++ b/shared/lib/admin-firewall.ts @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2024 John Livingston +// +// SPDX-License-Identifier: AGPL-3.0-only + +// Note: API request body size is limited to 100Kb (expressjs body-parser defaut limit, and Peertube nginx config). +// So we must be sure to never send more than 100Kb. +// All files are sent in one JSON object. +export const maxFirewallFileSize: number = 3 * 1024 +export const maxFirewallFiles = 20 + +export const maxFirewallNameLength = 20 +export const firewallNameRegexp = /^[a-zA-Z0-9_-]+$/ diff --git a/shared/lib/types.ts b/shared/lib/types.ts index a827efb7..9d3915da 100644 --- a/shared/lib/types.ts +++ b/shared/lib/types.ts @@ -13,7 +13,7 @@ interface ActorImage { updatedAt: Date | string } -type ConverseJSTheme = 'peertube' | 'default' | 'concord' +type ConverseJSTheme = 'peertube' | 'default' | 'cyberpunk' interface InitConverseJSParams { peertubeVideoOriginalUrl?: string @@ -109,6 +109,7 @@ interface ChannelConfigurationOptions { terms?: string // comes with Livechat 10.2.0 moderation: { // comes with Livechat 10.3.0 delay: number + anonymize: boolean // comes with Livechat 11.0.0 } } @@ -191,6 +192,17 @@ interface LivechatToken { date: number } +interface AdminFirewallConfigurationFile { + name: string + content: string + enabled: boolean +} + +interface AdminFirewallConfiguration { + enabled: boolean + files: AdminFirewallConfigurationFile[] +} + export type { ConverseJSTheme, InitConverseJSParams, @@ -211,5 +223,7 @@ export type { ChannelEmojis, ChannelEmojisConfiguration, ProsodyAuthentInfos, - LivechatToken + LivechatToken, + AdminFirewallConfiguration, + AdminFirewallConfigurationFile } diff --git a/support/documentation/config.toml b/support/documentation/config.toml index b2cda28e..089114b8 100644 --- a/support/documentation/config.toml +++ b/support/documentation/config.toml @@ -6,21 +6,20 @@ baseURL = "http://localhost:1313/peertube-plugin-livechat/" # this will be overr languageCode = "en-us" defaultContentLanguage = "en" title = "Peertube plugin livechat" -theme = 'hugo-theme-learn' +theme = 'hugo-theme-relearn' [outputs] -home = [ "HTML", "RSS", "JSON"] +home = [ "HTML", "RSS", "SEARCH"] [params] - author = "John Livingston" - description = "Peertube plugin livechat documentation" showVisitedLinks = true disableSearch = false disableLandingPageButton = false + landingPageName = " Livechat" themeVariant = "red" - landingPageURL = "/peertube-plugin-livechat/" - landingPageName = " Home" - custom_css = ["css/livechatdoc.css"] + + [params.author] + name = "John Livingston" [[menu.shortcuts]] name = " Github repository" @@ -34,187 +33,185 @@ weight = 20 [Languages] [Languages.en] + weight = 1 title = "Peertube plugin livechat documentation" languageName = "English" contentDir = "content/en" [Languages.ar] + weight = 2 languageName = "العربية" - landingPageURL = "/peertube-plugin-livechat/ar/" contentDir = "content/translations/ar" [Languages.ca] + weight = 3 languageName = "Català" - landingPageURL = "/peertube-plugin-livechat/ca/" contentDir = "content/translations/ca" [Languages.cs] + weight = 4 languageName = "Čeština" - landingPageURL = "/peertube-plugin-livechat/cs/" contentDir = "content/translations/cs" [Languages.de] + weight = 5 title = "Peertube Plugin Livechat Dokumentation" languageName = "Deutsch" - landingPageName = " Home" - landingPageURL = "/peertube-plugin-livechat/de/" contentDir = "content/translations/de" [Languages.el] + weight = 6 languageName = "ελληνικά" - landingPageURL = "/peertube-plugin-livechat/el/" contentDir = "content/translations/el" [Languages.eo] + weight = 7 languageName = "Esperanto" - landingPageURL = "/peertube-plugin-livechat/eo/" contentDir = "content/translations/eo" [Languages.es] + weight = 8 languageName = "Español" - landingPageURL = "/peertube-plugin-livechat/es/" contentDir = "content/translations/es" [Languages.eu] + weight = 9 languageName = "Euskara" - landingPageURL = "/peertube-plugin-livechat/eu/" contentDir = "content/translations/eu" [Languages.fa] + weight = 10 languageName = "فارسی" - landingPageURL = "/peertube-plugin-livechat/fa/" contentDir = "content/translations/fa" [Languages.fi] + weight = 11 languageName = "Suomi" - landingPageURL = "/peertube-plugin-livechat/fi/" contentDir = "content/translations/fi" [Languages.fr] + weight = 12 title = "Documentation plugin Peertube livechat" languageName = "Français" - landingPageName = " Accueil" - landingPageURL = "/peertube-plugin-livechat/fr/" contentDir = "content/translations/fr" [Languages.gd] + weight = 13 languageName = "Gàidhlig" - landingPageURL = "/peertube-plugin-livechat/gd/" contentDir = "content/translations/gd" [Languages.gl] + weight = 14 languageName = "Galego" - landingPageURL = "/peertube-plugin-livechat/gl/" contentDir = "content/translations/gl" [Languages.hr] + weight = 15 languageName = "Hrvatski" - landingPageURL = "/peertube-plugin-livechat/hr/" contentDir = "content/translations/hr" [Languages.hu] + weight = 16 languageName = "Magyar" - landingPageURL = "/peertube-plugin-livechat/hu/" contentDir = "content/translations/hu" [Languages.is] + weight = 17 languageName = "Íslenska" - landingPageURL = "/peertube-plugin-livechat/is/" contentDir = "content/translations/is" [Languages.it] + weight = 18 languageName = "Italiano" - landingPageURL = "/peertube-plugin-livechat/it/" contentDir = "content/translations/it" [Languages.ja] + weight = 19 title = "PeerTube ライブチャットプラグイン" languageName = "日本語バージョン" - landingPageName = " Home" - landingPageURL = "/peertube-plugin-livechat/ja/" contentDir = "content/translations/ja" [Languages.kab] + weight = 20 languageName = "Taqbaylit" - landingPageURL = "/peertube-plugin-livechat/ka/" contentDir = "content/translations/kab" [Languages.nb] + weight = 21 languageName = "Norsk bokmål" - landingPageURL = "/peertube-plugin-livechat/nb/" contentDir = "content/translations/nb" [Languages.nl] + weight = 22 languageName = "Nederlands" - landingPageURL = "/peertube-plugin-livechat/nl/" contentDir = "content/translations/nl" [Languages.nn] + weight = 23 languageName = "Norsk nynorsk" - landingPageURL = "/peertube-plugin-livechat/nn/" contentDir = "content/translations/nn" [Languages.oc] + weight = 24 languageName = "Occitan" - landingPageURL = "/peertube-plugin-livechat/oc/" contentDir = "content/translations/oc" [Languages.pl] + weight = 25 languageName = "Polski" - landingPageURL = "/peertube-plugin-livechat/pl/" contentDir = "content/translations/pl" [Languages.pt] + weight = 26 languageName = "uguês" - landingPageURL = "/peertube-plugin-livechat/pt/" contentDir = "content/translations/pt" [Languages.ru] + weight = 27 languageName = "Pусский" - landingPageURL = "/peertube-plugin-livechat/ru/" contentDir = "content/translations/ru" [Languages.sq] + weight = 28 languageName = "Shqip" - landingPageURL = "/peertube-plugin-livechat/sq/" contentDir = "content/translations/sq" [Languages.sv] + weight = 29 languageName = "Svenska" - landingPageURL = "/peertube-plugin-livechat/sv/" contentDir = "content/translations/sv" [Languages.th] + weight = 30 languageName = "ไทย" - landingPageURL = "/peertube-plugin-livechat/th/" contentDir = "content/translations/th" [Languages.tr] + weight = 31 languageName = "Türkçe" - landingPageURL = "/peertube-plugin-livechat/tr/" contentDir = "content/translations/tr" [Languages.tok] + weight = 32 languageName = "Toki Pona" - landingPageURL = "/peertube-plugin-livechat/to/" contentDir = "content/translations/tok" [Languages.uk] + weight = 33 languageName = "украї́нська мо́ва" - landingPageURL = "/peertube-plugin-livechat/uk/" contentDir = "content/translations/uk" [Languages.vi] + weight = 34 languageName = "Tiếng Việt" - landingPageURL = "/peertube-plugin-livechat/vi/" contentDir = "content/translations/vi" [Languages.zh-Hans] + weight = 35 languageName = "简体中文(中国)" - landingPageURL = "/peertube-plugin-livechat/zh-Hans/" contentDir = "content/translations/zh-Hans" [Languages.zh-Hant] + weight = 36 languageName = "繁體中文(台灣" - landingPageURL = "/peertube-plugin-livechat/zh-Hant/" contentDir = "content/translations/zh-Hant" diff --git a/support/documentation/content/en/_index.md b/support/documentation/content/en/_index.md index 6d762580..3951b333 100644 --- a/support/documentation/content/en/_index.md +++ b/support/documentation/content/en/_index.md @@ -1,11 +1,14 @@ -# PeerTube plugin livechat +--- +title: "Peertube livechat" +description: "Peertube plugin livechat documentation" +--- {{% notice tip %}} You can use the language selector in the left menu to view this documentation in different languages. Some translations are missing or incomplete. In this case, you'll see the English version of the text. {{% /notice %}} -![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px "Chat screenshot") Welcome the **Peertube Livechat Plugin** documentation. diff --git a/support/documentation/content/en/contributing/document/_index.md b/support/documentation/content/en/contributing/document/_index.md index 69cb336a..1bc429f8 100644 --- a/support/documentation/content/en/contributing/document/_index.md +++ b/support/documentation/content/en/contributing/document/_index.md @@ -17,7 +17,10 @@ The documentation source code is in the `support/documentation/content` folder. The documentation is generated using [Hugo](https://gohugo.io/). You have to install it on your computer if you want to preview your work. -The used theme is [hugo-theme-learn](https://learn.netlify.app/). +The minimum required version for Hugo is 0.121.0. +It was tested using version 0.132.2. + +The used theme is [hugo-theme-relearn](https://mcshelby.github.io/hugo-theme-relearn). You should read its documentation before starting editing the documentation. When a new plugin version is released, or when documentation is updated, plugin maintainers will merge the `main` branch to the `documentation` branch. diff --git a/support/documentation/content/en/documentation/admin/advanced/_index.md b/support/documentation/content/en/documentation/admin/advanced/_index.md index 81290c44..b9856d83 100644 --- a/support/documentation/content/en/documentation/admin/advanced/_index.md +++ b/support/documentation/content/en/documentation/admin/advanced/_index.md @@ -1,7 +1,7 @@ --- title: "Advanced usage" description: "Some advanced features" -weight: 20 +weight: 40 chapter: false --- diff --git a/support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md b/support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md index d8774472..7dbdbae0 100644 --- a/support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md +++ b/support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -113,7 +113,7 @@ You must then place these certificates in a folder that will be accessible to th {{% notice tip %}} If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing "xxx" with the arguments you wish to pass to -prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx` +prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx` {{% /notice %}} The plugin will check once a day to see if any files have been modified in this folder, and reload Prosody if necessary. diff --git a/support/documentation/content/en/documentation/admin/external_auth.md b/support/documentation/content/en/documentation/admin/external_auth.md index c6c3ca42..8bc6424f 100644 --- a/support/documentation/content/en/documentation/admin/external_auth.md +++ b/support/documentation/content/en/documentation/admin/external_auth.md @@ -14,9 +14,9 @@ Such "external account users" will be easier to moderate than anonymous accounts This also allows user to join the chat without creating Peertube account (in case your instance has closed registration for example, or without waiting for account approval). -![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a "{{% livechat_label login_using_external_account %}}" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px "{{% livechat_label login_using_external_account %}} button") -![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px) +![Screenshot of a dialog with an "OpenID Connect" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px "External login dialog - OpenID Connect") This page will describe available authentication methods. @@ -49,7 +49,7 @@ You will now have to fill some settings. This is the button label in the following screenshot: -![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px) +![Screenshot of a dialog with an "OpenID Connect" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px "External login dialog - OpenID Connect") For now, it is not possible to localize this label. diff --git a/support/documentation/content/en/documentation/admin/mod_firewall.md b/support/documentation/content/en/documentation/admin/mod_firewall.md new file mode 100644 index 00000000..404e3c00 --- /dev/null +++ b/support/documentation/content/en/documentation/admin/mod_firewall.md @@ -0,0 +1,55 @@ +--- +title: "Prosody mod_firewall" +description: "Advanced firewall rules for the Prosody server" +weight: 30 +chapter: false +--- + +{{% notice info %}} +This feature comes with the livechat plugin version 11.0.0. +{{% /notice %}} + +You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server. + +Doing so, Peertube admins will be able to define advanced firewall rules. + +{{% notice warning %}} +These rules could be used to run arbitrary code on the server. +If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). +This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. +You can still use mod_firewall by editing files directly on the server. +{{% /notice %}} + +## Edit rules + +First, you must enable the feature in the [plugin settings](/peertube-plugin-livechat/documentation/admin/settings). + +Just bellow the settings, you will find a "Configure mod_firewall" button. +This button will open a configuration page. + +![Screenshot of the "{{% livechat_label prosody_firewall_configuration %}}" form.](/peertube-plugin-livechat/images/mod_firewall.png?classes=shadow,border&height=400px "{{% livechat_label prosody_firewall_configuration %}}") + +Here you can add several configuration files. + +You can enable/disable each files. + +Files will be loaded in the alphabetical order. +You can use a number as prefix to easily choose the order. + +{{% notice info %}} +You can also edit these firewall rules directly on the server, in the `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` directory. +File names must only contains alphanumerical characters, underscores and hyphens. +The extension must be `.pfw`, or `.pfw.disabled` if you want to disable a file. +Please be sure that the peertube system user has write access to these files, else the web editing interface will fail. +Once you have edited these files, you must reload prosody. +This can be done by saving the plugin settings, or saving the mod_firewall configuration in the web interface, or by restarting Peertube. +{{% /notice %}} + +When you save the configuration, the server will automatically reload it, and your rules will apply immediatly. +You can check that there is no parsing error in the Prosody error log. +To do so, you can read the `plugins/data/peertube-plugin-livechat/prosody/prosody.err` file, or use the [diagnostic tool](/peertube-plugin-livechat/documentation/installation/troubleshooting/) that will show last Prosody errors. + +## Examples + +Don't hesitate to share your rules. +To do so, you can for example edit this [page](/peertube-plugin-livechat/contributing/document/#write-documentation). diff --git a/support/documentation/content/en/documentation/admin/settings.md b/support/documentation/content/en/documentation/admin/settings.md index b6167453..a778bd15 100644 --- a/support/documentation/content/en/documentation/admin/settings.md +++ b/support/documentation/content/en/documentation/admin/settings.md @@ -120,27 +120,27 @@ You can choose from several different sets the default avatars that will be used {{% livechat_label avatar_set_option_sepia %}}: [David Revoy's Peertube avatar generator](https://www.peppercarrot.com/extras/html/2023_peertube-generator/), [CC-By](https://creativecommons.org/licenses/by/4.0/) license -![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px) +![Sepia avatar example](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px "Sepia") {{% livechat_label avatar_set_option_cat %}}: [David Revoy's cat avatar generator](https://www.peppercarrot.com/extras/html/2016_cat-generator/), [CC-By](https://creativecommons.org/licenses/by/4.0/) license -![Cats](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px) +![Cats avatar example](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px "Cats") {{% livechat_label avatar_set_option_bird %}}: [David Revoy's bird avatar generator](https://www.peppercarrot.com/extras/html/2019_bird-generator/), [CC-By](https://creativecommons.org/licenses/by/4.0/) license -![Birds](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px) +![Birds avatar example](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px "Birds") {{% livechat_label avatar_set_option_fenec %}}: [David Revoy's fenec/mobilizon avatar generator](https://www.peppercarrot.com/extras/html/2020_mobilizon-generator/), [CC-By](https://creativecommons.org/licenses/by/4.0/) license -![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px) +![Fenecs avatar example](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px "Fenecs") {{% livechat_label avatar_set_option_abstract %}}: [David Revoy's Abstract avatar generator](https://www.peppercarrot.com/extras/html/2017_abstract-generator/index.php), [CC-By](https://creativecommons.org/licenses/by/4.0/) license -![Abstracts](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px) +![Abstracts avatar example](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px "Abtracts") {{% livechat_label avatar_set_option_legacy %}}: Based on [David Revoy' work](https://www.davidrevoy.com), [AGPL-v3](https://www.gnu.org/licenses/agpl-3.0.en.html) license -![Legacy](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px) +![Legacy avatar example](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px "Legacy") If you can't see the change immediatly, it could be because of your browser cache. Just clear your browser session storage, or restart it. @@ -150,7 +150,7 @@ You can choose which theme to use for ConverseJS: - Peertube theme: this is a special theme, made especially for peertube's integration. - Default ConverseJS theme: this is the default ConverseJS theme. -- ConverseJS concord theme: this is a theme provided by ConverseJS. +- ConverseJS cyberpunk theme: this is a theme provided by ConverseJS. ### {{% livechat_label autocolors_label %}} @@ -254,3 +254,9 @@ More informations on Prosody external components [here](https://prosody.im/doc/c #### {{% livechat_label prosody_components_list_label %}} {{% livechat_label prosody_components_list_description %}} + +### {{% livechat_label prosody_firewall_label %}} + +You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server. + +For more information, please check [the documentation](/peertube-plugin-livechat/documentation/admin/mod_firewall/). diff --git a/support/documentation/content/en/documentation/installation/troubleshooting.md b/support/documentation/content/en/documentation/installation/troubleshooting.md index cbf8527a..348a878e 100644 --- a/support/documentation/content/en/documentation/installation/troubleshooting.md +++ b/support/documentation/content/en/documentation/installation/troubleshooting.md @@ -15,11 +15,11 @@ If the chat does not work, there is a diagnostic tool in the plugin's settings p Open the plugin settings, and click on the "launch diagnostic" button. -![Launch diagnostic](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px) +![Screenshot of the plugin's settings page, with a "launch diagnostic" button.](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px "Launch diagnostic") If there is any error in the diagnostic page, you can search in this page for a solution, or refer to the [Bug tracking documentation page](/peertube-plugin-livechat/issues/) if you can't find any response. -![Diagnostic result](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px) +![Screenshot of the diagnostic result page. This gives a lot of information, with status for different test suites.](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px "Diagnostic result") ## Chat does not load diff --git a/support/documentation/content/en/documentation/user/obs.md b/support/documentation/content/en/documentation/user/obs.md index e08f19b0..ec0560ab 100644 --- a/support/documentation/content/en/documentation/user/obs.md +++ b/support/documentation/content/en/documentation/user/obs.md @@ -12,18 +12,18 @@ In the current page, you will find some advices to handle your live chats using You can easily include the chat in your video stream. -![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px "Embeding the chat in a live stream") You can use the "{{% livechat_label share_chat_link %}}" feature to generate an URL to your chat. This button should be near the chat if you are the video owner (unless it was desactivated by your server admins). Check the "{{% livechat_label read_only %}}" checkbox in the modal. -![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, where the "{{% livechat_label read_only %}}" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px "Share link popup") Then use this link as a "web browser source" in OBS. -![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px) +![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px "Embeding the chat in OBS") You can use the "{{% livechat_label transparent_background %}}" option to have a transparent background in OBS. If you want to customize the background transparency, you can add this CSS in your OBS browser source's settings: @@ -56,17 +56,17 @@ The livechat plugin offers a way to create long term token that can identify you To do so, just use the "{{% livechat_label share_chat_link %}}" feature, and open the "{{% livechat_label share_chat_dock %}}" tab. From there, you can create a new token using the "+" button. -![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, on the "{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable."](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px "Share link popup - dock tab") Then, copy the url, and use the "Docks / Custom browser docks" menu from your OBS to add a dock with this URL. -![OBS - Dock menu](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px) +![Screenshot of the OBS Dock menu, with a "Custom Browser Docks" entry.](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px "OBS - Dock menu") -![OBS - Dock dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px) +![Screenshot of the OBS Custom Browser Docks dialog, with a new dock called "My chat".](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px "OBS - Dock dialog") Once you have done, you will have a new dock connected to the chat with your account. -![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px) +![Screenshot of OBS with a new dock including the chat. The user is logged in with their Peertube account, and can chat directly from OBS.](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px "OBS - Dock") {{% notice tip %}} Tokens are valid to join any chat room. You don't have to generate separate tokens for each of your rooms. diff --git a/support/documentation/content/en/documentation/user/streamers/basics.md b/support/documentation/content/en/documentation/user/streamers/basics.md index 3114f059..79f42c24 100644 --- a/support/documentation/content/en/documentation/user/streamers/basics.md +++ b/support/documentation/content/en/documentation/user/streamers/basics.md @@ -14,12 +14,12 @@ Information in this section are only true in the default case. When you create or modify a Peertube live, there is a "plugin settings" tab: -![New live](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px) +![Screenshot of the Peertube new live form.](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px "New live") In the "plugin settings" tab, there is a "{{% livechat_label use_chat %}}" checkbox. Just check or uncheck it to enable or disable the chat associated to your video. -![Activate the chat](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px) +![Screenshot of the form, with a "{{% livechat_label use_chat %}}" checkbox.](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px "Activate the chat") {{% notice tip %}} There can be other settings in this tab, depending on plugins installed on your Peertube instance. @@ -39,7 +39,7 @@ This url can be shared. The "{{% livechat_label share_chat_embed %}}" tab provide some links to embed the chat in websites, or in your live stream. -![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, where the "{{% livechat_label read_only %}}" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px "Share link popup") You can customize some options: @@ -49,19 +49,19 @@ You can customize some options: For more information on the "{{% livechat_label share_chat_dock %}}" tab, check the [OBS documentation](/peertube-plugin-livechat/documentation/user/obs). -![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, on the "{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable."](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px "Share link popup - dock tab") In the "{{% livechat_label web %}}" tab, the provided url opens the chat in the Peertube interface. You can share this link to other users to invite them to join the chat. -![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, on the "{{% livechat_label web %}} tab. There is a url you can copy.](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px "Share link popup - web tab") The "{{% livechat_label share_chat_link %}}" popup can also contain a "{{% livechat_label connect_using_xmpp %}}" tab. This will only be available if your instance's administators have enabled an correctly configured this option. Using this option, you can provide a link to join the chat using any [XMPP client software](https://en.wikipedia.org/wiki/XMPP#Clients). Using such softwares can for example facilitate moderation actions. -![Share link popup - xmpp tab](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, on the "{{% livechat_label connect_using_xmpp %}}" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px "{{% livechat_label connect_using_xmpp %}}") ## Moderation @@ -80,11 +80,11 @@ User joining will see messages posted before their arrival. You can change the persistence behaviour. [Open the chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), and click on "Configure". -![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px) +![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px "Chat menu") There are several options that can be changed. -![Configure chat room](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px) +![Screenshot of the chat configuration form.](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px "Configure chat room") You can for example set the default and maximum number of messages to return to 0, so that new incomers won't see any previously sent message. diff --git a/support/documentation/content/en/documentation/user/streamers/bot/_index.md b/support/documentation/content/en/documentation/user/streamers/bot/_index.md index ffd81d45..d81031d3 100644 --- a/support/documentation/content/en/documentation/user/streamers/bot/_index.md +++ b/support/documentation/content/en/documentation/user/streamers/bot/_index.md @@ -12,7 +12,7 @@ This feature comes with the livechat plugin version 8.0.0, and can be disabled b You can enable a chat bot on your chatrooms. The bot configuration is made channel per channel, and will apply to all related videos' chatrooms. -![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px) +![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px "Channel configuration") To access this page, check the [channel configuration documentation](/peertube-plugin-livechat/documentation/user/streamers/channel). diff --git a/support/documentation/content/en/documentation/user/streamers/bot/commands.md b/support/documentation/content/en/documentation/user/streamers/bot/commands.md index e80d8bb7..c9a2f773 100644 --- a/support/documentation/content/en/documentation/user/streamers/bot/commands.md +++ b/support/documentation/content/en/documentation/user/streamers/bot/commands.md @@ -7,7 +7,7 @@ chapter: false {{% livechat_label livechat_configuration_channel_command_desc %}} -![Commands configuration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px) +![Screenshot of the channel options page, with some fields to configure the bot commands.](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px "Commands configuration") You can setup several commands. @@ -15,6 +15,6 @@ You can setup several commands. {{% livechat_label livechat_configuration_channel_command_cmd_desc %}} -## {{% livechat_label livechat_configuration_channel_quote_delay_label %}} +## {{% livechat_label livechat_configuration_channel_command_message_label %}} -{{% livechat_label livechat_configuration_channel_quote_delay_desc %}} +{{% livechat_label livechat_configuration_channel_command_message_desc %}} diff --git a/support/documentation/content/en/documentation/user/streamers/bot/forbidden_words.md b/support/documentation/content/en/documentation/user/streamers/bot/forbidden_words.md index b40a875b..b8fd0ff1 100644 --- a/support/documentation/content/en/documentation/user/streamers/bot/forbidden_words.md +++ b/support/documentation/content/en/documentation/user/streamers/bot/forbidden_words.md @@ -7,9 +7,9 @@ chapter: false {{% livechat_label livechat_configuration_channel_forbidden_words_desc %}} -![Forbidden words configuration](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px) +![Screenshot of the channel options page, with several fields to configure the forbidden words.](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px "Forbidden words configuration") -![Deleted message](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px) +![Screenshot of a chat message that was deleted, with the following reason: "No url allowed".](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px "Deleted message") You can fill several "{{% livechat_label livechat_configuration_channel_forbidden_words_label %}}" fields. When a user sends a message that match the configured criteria, the message will automatically be deleted. @@ -35,6 +35,10 @@ See the [contribution guide](/peertube-plugin-livechat/contributing/) for more i These words are case insensitive. {{% /notice %}} +{{% notice tip %}} +You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them. +{{% /notice %}} + {{% notice warning %}} This features is still experimental. There might be some issues with non-latin alphabets. diff --git a/support/documentation/content/en/documentation/user/streamers/bot/quotes.md b/support/documentation/content/en/documentation/user/streamers/bot/quotes.md index f41ee149..6c384b62 100644 --- a/support/documentation/content/en/documentation/user/streamers/bot/quotes.md +++ b/support/documentation/content/en/documentation/user/streamers/bot/quotes.md @@ -11,7 +11,7 @@ chapter: false If there is no user in the chatroom, the bot won't send any message. {{% /notice %}} -![Timers configuration](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px) +![Screenshot of the channel options page, with some fields to configure a new timer.](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px "Timers configuration") ## {{% livechat_label livechat_configuration_channel_quote_label %}} diff --git a/support/documentation/content/en/documentation/user/streamers/channel.md b/support/documentation/content/en/documentation/user/streamers/channel.md index a36079f7..3a4d5ed8 100644 --- a/support/documentation/content/en/documentation/user/streamers/channel.md +++ b/support/documentation/content/en/documentation/user/streamers/channel.md @@ -11,12 +11,12 @@ This feature comes with the livechat plugin version 8.0.0, and can be disabled b In the Peertube left menu, there is a "{{% livechat_label menu_configuration_label %}}" entry: -![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px) +![Screenshot of the chatrooms configuration page. The page list the user's channels.](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px "Chatrooms menu") This "{{% livechat_label menu_configuration_label %}}" link takes you to a list of your channels. By clicking on a channel, you will then be able to setup some options for your channels: -![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px) +![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px "Channel configuration") Here you can configure: diff --git a/support/documentation/content/en/documentation/user/streamers/emojis.md b/support/documentation/content/en/documentation/user/streamers/emojis.md index ed9336a0..f0119060 100644 --- a/support/documentation/content/en/documentation/user/streamers/emojis.md +++ b/support/documentation/content/en/documentation/user/streamers/emojis.md @@ -15,11 +15,11 @@ Streamers can add custom emojis to their channels. On the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel), open the "{{% livechat_label livechat_configuration_channel_emojis_title %}}" tab: -![Channel configuration / Channel emojis configuration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px) +![Screenshot of the emoji configuration page. There is a form where you can add new emojis.](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px "Channel configuration / Channel emojis configuration") {{% livechat_label livechat_configuration_channel_emojis_desc %}} -![Channel configuration / Channel emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px) +![Screenshot of a chat session, with messages containing custom emojis. The emoji picker is open, and shows custom emojis.](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px "Channel configuration / Channel emojis") {{% livechat_label livechat_emojis_shortname_desc %}} diff --git a/support/documentation/content/en/documentation/user/streamers/moderation.md b/support/documentation/content/en/documentation/user/streamers/moderation.md index eb3c0e9a..7881553a 100644 --- a/support/documentation/content/en/documentation/user/streamers/moderation.md +++ b/support/documentation/content/en/documentation/user/streamers/moderation.md @@ -23,7 +23,7 @@ Check [the chat bot documentation](/peertube-plugin-livechat/documentation/user/ You can access room settings and moderation tools using the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers) at the top of the chat. -![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px) +![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px "Chat menu") {{% notice tip %}} The video owner will be owner of the chat room. @@ -50,11 +50,11 @@ You can prevent anonymous users to send messages. In such case, only registered To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the "configure" menu. In the form, you will find a "{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}" checkbox. -![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px) +![Screenshot of the room configuration form. There is a "{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}" checkbox.](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px "Room configuration / Mute anonymous users") Anonymous users won't have the message field, and will see following prompt: "{{% livechat_label muted_anonymous_message %}}" -![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px) +![Screenshot of a chat session. The current user has no message field. There is a message: "{{% livechat_label muted_anonymous_message %}}"](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px "Room configuration / Muted anonymous users") When this feature is enabled, anonymous users will be assigned the "visitor" role. You can change their role to "participant" if you want to allow some of them to talk. @@ -73,6 +73,47 @@ This section is still incomplete. You can promote users as moderators, if you need some help. +## {{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}} + +{{% notice info %}} +This feature comes with the livechat plugin version 11.0.0. +{{% /notice %}} + +It is possible to anonymize moderation actions, to avoid disclosing who is banning/kicking/… occupants. + +To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the "configure" menu. +In the form, you will find a "{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}" checkbox. + +You can choose to enable or disable this feature for new chatrooms on the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel). + +## Participant message history search + +{{% notice info %}} +This feature comes with the livechat plugin version 11.0.0. +{{% /notice %}} + +As a room admin or owner, you can search all messages sent by a given participant. + +To do so, you have several ways: + +* using the "{{% livechat_label search_occupant_message %}}" action in the dropdown menu besides participants in the sidebar +* using the "{{% livechat_label search_occupant_message %}}" action in the dropdown menu besides chat messages + +![Screenshot of a chat session. The moderator has open the message menu, and there is a "{{% livechat_label search_occupant_message %}}" button.](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px "Message history search") + +{{% notice tip %}} +To have more space and better readability, open the chat in full-page mode. +{{% /notice %}} + +In the search results, there are several informations that are shown at the right of the participant nickname: + +* if the current nickname is different than the nickname when the participant has sent the message, the original nickname will be shown +* you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant +* you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant + +The search result will also include all messages related to participants who had the same nickname. +You can differenciate them by comparing [JID](https://xmpp.org/extensions/xep-0029.html) and [occupant-id](https://xmpp.org/extensions/xep-0421.html). + ## Delete room content You can delete old rooms: join the room, and use the menu on the top to destroy the room. diff --git a/support/documentation/content/en/documentation/user/streamers/moderation_delay.md b/support/documentation/content/en/documentation/user/streamers/moderation_delay.md index 3033b5cc..3c6718c9 100644 --- a/support/documentation/content/en/documentation/user/streamers/moderation_delay.md +++ b/support/documentation/content/en/documentation/user/streamers/moderation_delay.md @@ -22,7 +22,7 @@ Please note that messages sent by moderators will also be delayed, to avoid them On the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel), you can set the "{{% livechat_label moderation_delay %}}" option: -![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px) +![Screenshot of the channel option form, with a field to configure the moderation delay.](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px "Channel configuration / Moderation delay") This value will apply as a default value for all your channel's chatrooms. @@ -39,8 +39,12 @@ Currently, this feature has one known bug: users that join the chat will get all However, messages sent after they joined will be delayed correctly. {{% /notice %}} +{{% notice tip %}} +You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them. +{{% /notice %}} + ## In the chat As a moderator, you will see the remaining time (in seconds) before the message is broadcasted, just besides the message datetime. -![Moderation delay timer](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border) +![Screenshot of a chat message. A timer is displayed next to the message datetime. The timer is in seconds.](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border "Moderation delay timer") diff --git a/support/documentation/content/en/documentation/user/streamers/moderation_notes.md b/support/documentation/content/en/documentation/user/streamers/moderation_notes.md new file mode 100644 index 00000000..87cf394e --- /dev/null +++ b/support/documentation/content/en/documentation/user/streamers/moderation_notes.md @@ -0,0 +1,105 @@ +--- +title: "Moderation notes" +description: "Plugin peertube-plugin-livechat moderation notes" +weight: 355 +chapter: false +--- + +{{% notice info %}} +This feature comes with the livechat plugin version 11.0.0. +{{% /notice %}} + +## Introduction + +The livechat plugin includes a Moderator Notes Application: you can write some notes, that could be associated to chat participants. +Every room's admins have access to these notes, so they can edit them collaboratively. + +You can for example use this Application to: + +* share some notes between moderators +* take notes about participants that were kicked or caused troubles +* ... + +## Using the Moderator Notes Application + +### Opening the Moderator Notes Application + +To open the Moderator Notes Application, there is a "{{% livechat_label "moderator_notes" %}}" button in the top chat menu: + +![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a "{{% livechat_label "moderator_notes" %}}" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_video.png?classes=shadow,border&height=200px "Opening the Moderator Notes Application") + +![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a "{{% livechat_label "moderator_notes" %}}" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px "Opening the Moderator Notes Application") + +Clicking this button will toggle the Application display: + +![Screenshot of a Peertube video, with the chat on the right. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px "Moderator Notes Application") + +![Screenshot of a Peertube chat, fullscreen. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px "Moderator Notes Application") + +{{% notice tip %}} +To have more space and better readability, open the chat in full-page mode. +{{% /notice %}} + +### Access rights + +Every room's admins have access to this Application (read and write access). + +When you promote someone as room admin or owner, they gets instant access to this Application. +When you remove admin or owner rights to someone, they instantly lose access to this Application. + +### Scope + +Notes are only available in the room in which you have created them. + +Chatrooms can be releated to video or channel. +If you want to keep notes from one video to another, please consider using rooms associated to channels. + +{{% notice warning %}} +Currently the video vs channel rooms is an instance-wide settings. +Only Peertube admins can change it, and it applies to all chatrooms. +In the future, this choice will be added in your channel's options. +{{% /notice %}} + +### Notes + +#### Create/Edit Notes + +You can use the plus button on the top to create a new note. +You can also edit existing notes using the edit button, or delate any note. + +{{% notice tip %}} +All modification are instantly visible in all your browser tabs, and for all room's admins. +{{% /notice %}} + +You can create a note associated to a participant in several ways: + +* using the "{{% livechat_label moderator_note_create_for_participant %}}" action in the dropdown menu besides participants in the sidebar +* using the "{{% livechat_label moderator_note_create_for_participant %}}" action in the dropdown menu besides chat messages + +When a note is associated to a participant, you will see their nickname and avatar on the top of the note. + +#### Notes filtering + +You can filter notes to find all notes related to a given participant in several ways: + +* click on the "{{% livechat_label moderator_note_search_for_participant %}}" button that is available on notes to find all notes related to the same participant +* click on the "{{% livechat_label moderator_note_search_for_participant %}}" button in the dropdown menu besides participants in the sidebar +* click on the "{{% livechat_label moderator_note_search_for_participant %}}" button in the dropdown menu besides chat messages + +You can remove the filter by clicking on the close button. + +![Screenshot of the note application, with a filter enabled for user "Mike". The only notes that are shown are the notes for the Mike user.](/peertube-plugin-livechat/images/moderation_notes_filters.png?classes=shadow,border&height=200px "Moderator Notes Application - filtering") + +When you filters notes on a participant, there are several informations that are shown at the right of the participant nickname: + +* if the current nickname is different than the nickname when you created the note, the original nickname will be shown +* you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant +* you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant + +The search result will also include all notes related to participants who had the same nickname. +So you can also take note for anonymous users (who don't have any consistent JID or occupant-id). +You can differenciate them by comparing JID and occupant-id. + +#### Sorting notes + +You can sort notes simply using drag & drop. diff --git a/support/documentation/content/en/documentation/user/streamers/polls.md b/support/documentation/content/en/documentation/user/streamers/polls.md index d737a90d..1f6f172f 100644 --- a/support/documentation/content/en/documentation/user/streamers/polls.md +++ b/support/documentation/content/en/documentation/user/streamers/polls.md @@ -13,7 +13,7 @@ This feature comes with the livechat plugin version 10.2.0. You can create a new poll by using the "{{% livechat_label new_poll %}}" action in the chat top menu: -![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px) +![Screenshot of a "{{% livechat_label new_poll %}}" form. The form contains several fields: question, duration, choices, …](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px "Poll form") {{% notice warning %}} This poll feature should not be considered as a reliable voting system. @@ -54,7 +54,7 @@ When the polls starts, a first message will be sent in the chat, from the accoun A banner will also appear to show the poll, and will be updated regularly with the current votes. -![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px) +![Screenshot of a chat session. In the chat, there is a message with the poll question, and the different choices. There is also a banner on the top of the chat, where you can see the question, and the number of votes for each answers.](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px "Poll start") Viewers can then vote by clicking on their choice, or by sending message like "!1" in the chat. @@ -63,7 +63,7 @@ Votes counts will be updated regularly in the banner. Viewers can change their vote at any time, just by making a new choice. Their precedent choice will be replaced by the new one. -![Poll votes](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px) +![Screenshot of a chat session, with an ongoing poll. The current user has just voted by sending "!1".](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px "Poll votes") {{% notice tip %}} Anonymous viewers can only vote once they have choosen their nickname. @@ -79,7 +79,7 @@ But they will see the message in the chat and will be able to vote by sending me When the poll ends, a new message will be sent in the chat, with the results. -![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px) +![Screenshot of a chat session, with poll that has ended. The banner no more accept new votes. There is a message in the chat with the poll results. For each choice, there is the number of votes, and the percentage of the total it represents.](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px "Poll end") {{% notice info %}} The only way to get old polls results is to search for the poll end message in the chat. diff --git a/support/documentation/content/en/documentation/user/streamers/slow_mode.md b/support/documentation/content/en/documentation/user/streamers/slow_mode.md index 8dd43462..577cdc7d 100644 --- a/support/documentation/content/en/documentation/user/streamers/slow_mode.md +++ b/support/documentation/content/en/documentation/user/streamers/slow_mode.md @@ -26,7 +26,7 @@ This limitation does not apply to moderators. On the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel), you can set the slow mode option: -![Channel configuration / Slow Mode](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px) +![Screenshot of the channel options form, with a slow mode field.](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px "Channel configuration / Slow Mode") This value will apply as a default value for all your channel's chatrooms. @@ -40,7 +40,7 @@ To modify the value for an already existing room, just open the room "configurat If the slow mode is enabled, users will be informed by a message. -![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px) +![Screenshot of a chat session. There is a banner on the bottom of the chat, indicating that the slow mode is enabled, and that users can send a message every 2 seconds.](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px "Slow mode infobox") When they send a message, the input field will be disabled for X seconds (where X is the slow mode duration). diff --git a/support/documentation/content/en/documentation/user/streamers/tasks.md b/support/documentation/content/en/documentation/user/streamers/tasks.md index 0c09b75b..05f538fc 100644 --- a/support/documentation/content/en/documentation/user/streamers/tasks.md +++ b/support/documentation/content/en/documentation/user/streamers/tasks.md @@ -26,15 +26,15 @@ You can for example use the Task Application to: To open the Task Application, there is a "{{% livechat_label "tasks" %}}" button in the top chat menu: -![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a "{{% livechat_label tasks %}}" button.](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px "Opening the Task Application") -![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a "{{% livechat_label tasks %}}" button.](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px "Opening the Task Application") Clicking this button will toggle the Task Application display: -![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video, with the chat on the right. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px "Task Application") -![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube chat, fullscreen. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px "Task Application") {{% notice tip %}} To have more space and better readability, open the chat in full-page mode. @@ -56,7 +56,7 @@ You can also edit existing task lists using the edit button, or delete any task Task lists are sorted alphabetically. -![Task lists](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px) +![Screenshot of a chat session, with the Task application. There are several task lists.](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px "Task lists") {{% notice tip %}} All modification are instantly visible in all your browser tabs, and for all room's admins. @@ -69,9 +69,9 @@ All modification are instantly visible in all your browser tabs, and for all roo You can create a task using the button on the right of task lists. This opens a form with two fields: a mandatory task name, and an optional description. -![Task form](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px) +![Screenshot of the task application. Under the first task list, there is a form to create a new task.](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px "Task form") -![Task created](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px) +![Screenshot of the task application. Under the first task list, a new task was created.](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px "Task created") #### Edit tasks @@ -79,15 +79,15 @@ Tasks can be edited by using the edit button on the right. Tasks can be marked complete (or uncomplete) by clicking directly on the checkbox in the list. -![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px) +![Screenshot of the task application. Under task lists, there are several tasks. Some of them are checked, other not.](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px "Tasks") #### Sorting tasks / change task list You can sort tasks, or move tasks from one list to another, simply using drag & drop. -![Drag and drop to sort](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px) +![Screenshot of the task application. There is a task that is dragged over another.](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px "Drag and drop to sort") -![Drag and drop to move to another list](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px) +![Screenshot of the task application. There is a task that is dragged over another task list.](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px "Drag and drop to move to another list") #### Create a task from a chat message @@ -95,10 +95,10 @@ You can create a task from a message in a chat, using the "{{% livechat_label "t This will open a dialog box where you can choose which task list you want to add the task into. The task name will be the user nickname, and the task description the message content. -![Create task from message](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px) +![Screenshot of a chat session. The menu besides a message is open, with a button to create a new task.](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px "Create task from message") -![Choose the task list](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px) +![Screenshot of a dialog, where you can choose in which task list you want to add the new task.](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px "Choose the task list") -![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px) +![Screenshot of the task application. A new task was added in the "chat questions" task list, with the user's nickname, and the message as content.](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px "Task created") Using this feature, for example, you can ask your moderators to highlight all chat questions, so you can see them at a glance during your livestream, and check them as answered. diff --git a/support/documentation/content/en/documentation/user/streamers/terms.md b/support/documentation/content/en/documentation/user/streamers/terms.md index bf190c04..04ed3835 100644 --- a/support/documentation/content/en/documentation/user/streamers/terms.md +++ b/support/documentation/content/en/documentation/user/streamers/terms.md @@ -16,7 +16,7 @@ These terms will be shown to all users joining the chat. To configure the terms & conditions, go to the [channel configuration page](/peertube-plugin-livechat/documentation/user/streamers/channel): -![Channel configuration / Terms](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px) +![Screenshot of the channel options form, with a field to configure your terms and conditions.](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px "Channel configuration / Terms") URL in the message will be clickable. You can also do some styling: [Message Styling](https://xmpp.org/extensions/xep-0393.html). @@ -25,7 +25,7 @@ You can also do some styling: [Message Styling](https://xmpp.org/extensions/xep- When joining the chat, viewers will see the terms: -![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px) +![Screenshot of a chat session. On the top of the chat, there are terms and conditions for both the server and the channel.](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px "Terms") {{% notice info %}} Peertube instance's admin can also set global terms & conditions. diff --git a/support/documentation/content/en/documentation/user/viewers.md b/support/documentation/content/en/documentation/user/viewers.md index d9bbe70e..d7130b4a 100644 --- a/support/documentation/content/en/documentation/user/viewers.md +++ b/support/documentation/content/en/documentation/user/viewers.md @@ -9,7 +9,7 @@ chapter: false When you are watching a Peertube video that has the chat activated, you will see the chat next to the video: -![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px "Chat screenshot") There are two slightly different use cases, depending on wether or not you have an account on the Peertube instance. See bellow for more informations. @@ -23,11 +23,11 @@ This feature can be disabled by the instance's adminitrators. If you are not logged in on the Peertube instance where you are watching the video, you will automatically join the chat. You will be assigned a random nickname (something like "Anonymous 12345"). -![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px) +![Screenshot of a chat. In the participant list, there is John Livingston, and an anonymous account using "Anonymous 212873" nickname.](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px "Chat with an anonymous user") Before being able to speak in the chat room, you have to enter a nickname in the field on the bottom of the window. -![Joining chat when not connected](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px) +![Screenshot of the chat. The current user is not logged in, and must choose a nickname before being able to write in the chat.](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px "Joining chat when not connected") #### Log in using an external authentication provider @@ -39,9 +39,9 @@ The Peertube instance can configure external authentication providers (Mastodon In such case, you will see a "{{% livechat_label login_using_external_account %}}" button, that will open a dialog modal. In this dialog modal, there will be some buttons to connect using a remote account. -![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a "{{% livechat_label login_using_external_account %}}" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px "{{% livechat_label login_using_external_account %}} button") -![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px) +![Screenshot of a dialog with an "OpenID Connect" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px "External login dialog - OpenID Connect") Once you signed in the remote account, and have granted access, your nickname and avatar (if available) will be automatically fetched. No other data will be stored. @@ -68,9 +68,9 @@ This button will open a dialog where you can enter your Peertube instance URL. Once you entered it, it will check if the livechat plugin is available on the remote instance, and if the video is available. If it is the case, you will be redirected to the video on the remote instance. -![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a "{{% livechat_label login_using_external_account %}}" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px "{{% livechat_label login_using_external_account %}} button") -![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label login_using_external_account %}}" dialog. There is a field where you can enter a Peertube url.](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px "External login dialog") ## Chatting @@ -91,7 +91,7 @@ You can also click on a nickname in the participants list to insert it in the me To see the list of participants, just open the right menu: -![Participants list](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px) +![Screenshot of a chat session, with on the right the list of participants.](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px "Participants list") You can see that some participants have special rights (moderator, owner, ...). @@ -101,14 +101,14 @@ There is a dropdown menu on the top of the chat, with some advanced features. This is especially useful for [moderation features](/peertube-plugin-livechat/documentation/user/streamers/moderation). Available features depends on your access level. -![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px) +![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px "Chat menu") ## Opening full screen On top of the chat, there is a button to open the chat in fullscreen. This will open a new browser tab with the following content: -![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px) +![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px "Fullscreen chat screenshot") It can be easier to chat using a full browser tab. diff --git a/support/documentation/content/en/documentation/user/xmpp_clients.md b/support/documentation/content/en/documentation/user/xmpp_clients.md index b04c6382..19d923e2 100644 --- a/support/documentation/content/en/documentation/user/xmpp_clients.md +++ b/support/documentation/content/en/documentation/user/xmpp_clients.md @@ -29,7 +29,7 @@ chats using any XMPP account. To get the address of the room you want to join, you can use the "share chat" button that is located above the chat: -![Share button](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px) +![Screenshot of the chat, with a share button on the top.](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px "Share button") {{% notice info %}} By default, the share button is only visible to the owner of the video, @@ -37,9 +37,9 @@ and the admins/moderators of the instance. However, admins can decide to display this button for everyone. {{% /notice %}} -Then, choose "Connect using XMPP": +Then, choose "{{% livechat_label connect_using_xmpp %}}": -![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px) +![Screenshot of the "{{% livechat_label share_chat_link %}}" dialog, on the "{{% livechat_label connect_using_xmpp %}}" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px "{{% livechat_label connect_using_xmpp %}}") Then you just have to click on "open" or copy/paste the address of the chat room into your XMPP client (using the "join a room" feature). diff --git a/support/documentation/content/en/images/message_search.png b/support/documentation/content/en/images/message_search.png new file mode 100644 index 00000000..8177be9b Binary files /dev/null and b/support/documentation/content/en/images/message_search.png differ diff --git a/support/documentation/content/en/images/mod_firewall.png b/support/documentation/content/en/images/mod_firewall.png new file mode 100644 index 00000000..9c6e9952 Binary files /dev/null and b/support/documentation/content/en/images/mod_firewall.png differ diff --git a/support/documentation/content/en/images/moderation_notes_filters.png b/support/documentation/content/en/images/moderation_notes_filters.png new file mode 100644 index 00000000..1eed5de4 Binary files /dev/null and b/support/documentation/content/en/images/moderation_notes_filters.png differ diff --git a/support/documentation/content/en/images/moderation_notes_open_app_fullpage.png b/support/documentation/content/en/images/moderation_notes_open_app_fullpage.png new file mode 100644 index 00000000..4a461645 Binary files /dev/null and b/support/documentation/content/en/images/moderation_notes_open_app_fullpage.png differ diff --git a/support/documentation/content/en/images/moderation_notes_open_app_video.png b/support/documentation/content/en/images/moderation_notes_open_app_video.png new file mode 100644 index 00000000..18e2b819 Binary files /dev/null and b/support/documentation/content/en/images/moderation_notes_open_app_video.png differ diff --git a/support/documentation/content/en/images/moderator_notes_app_fullpage_1.png b/support/documentation/content/en/images/moderator_notes_app_fullpage_1.png new file mode 100644 index 00000000..3f691d80 Binary files /dev/null and b/support/documentation/content/en/images/moderator_notes_app_fullpage_1.png differ diff --git a/support/documentation/content/en/images/moderator_notes_app_video_1.png b/support/documentation/content/en/images/moderator_notes_app_video_1.png new file mode 100644 index 00000000..3ec9d5ad Binary files /dev/null and b/support/documentation/content/en/images/moderator_notes_app_video_1.png differ diff --git a/support/documentation/content/en/intro/_index.md b/support/documentation/content/en/intro/_index.md index 3fa3ca94..87500d8e 100644 --- a/support/documentation/content/en/intro/_index.md +++ b/support/documentation/content/en/intro/_index.md @@ -13,14 +13,14 @@ By default, once you have installed the plugin on your Peertube instance, a chat On the following screenshot, you can see a classic Peertube video page, with a chat room on the right (click on the picture to view it full screen): -![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px "Chat screenshot") The chat room will be accessible for all viewers, even those who don't have an account on your instance. Those "anonymous" users just have to choose a nickname before they can begin talking in the chat. By default, the chat is displayed next to the video. But you can open it in another browser tab, using the button on top of it : -![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px) +![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px "Fullscreen chat screenshot") {{% notice tip %}} You can test the livechat plugin with this [demo page](https://www.yiny.org/w/399a8d13-d4cf-4ef2-b843-98530a8ccbae). @@ -31,7 +31,7 @@ You can test the livechat plugin with this [demo page](https://www.yiny.org/w/39 As a Peertube administrator, you can setup this plugin on your instance simply by using the Peertube plugin marketplace included in the administration interface. Search for "livechat", then click "install": that's it! -![Livechat installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px) +![Screenshot of Peertube plugins admin page. The search fields contains "livechat", and the search results show the livechat plugin.](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px "Livechat installation") ## Livechat capabilities @@ -76,11 +76,11 @@ This is for example useful for replays. In the following screenshot, you can see a live replay, where the chat content is embeded on bottom of the video: -![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px) +![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px "Embeding the chat in a live stream") In the following screenshot, you can see an OBS setup, where the chat is included as a source in the current scene (background color can be changed, and can be transparent): -![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px) +![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px "Embeding the chat in OBS") ## Other usages diff --git a/support/documentation/content/en/technical/moderation_notes.md b/support/documentation/content/en/technical/moderation_notes.md new file mode 100644 index 00000000..d775583d --- /dev/null +++ b/support/documentation/content/en/technical/moderation_notes.md @@ -0,0 +1,86 @@ +--- +title: "Moderator notes overview" +description: "Moderator Notes Application technical overview" +weight: 75 +chapter: false +livechatnotranslation: true +--- + +The livechat plugin includes a [Moderation Notes Application](/peertube-plugin-livechat/documentation/user/streamers/moderation_notes). +The present document describes how this is implemented. + +## Basics + +This features relies on [XEP-0060: Publish-Subscribe](https://xmpp.org/extensions/xep-0060.html). +This XEP provide a way to store and retrieve items, and to receive push notifications when an item is created/deleted/modified. + +There is a Prosody Module, [mod_pubsub_peertubelivechat](https://github.com/JohnXLivingston/peertube-plugin-livechat/tree/main/prosody-modules/mod_pubsub_peertubelivechat), to implement some specific use of the pubsub mechanism. + +This module is also used for [Tasks](/peertube-plugin-livechat/technical/tasks/). + +We use the [JID+NodeID addressing](https://xmpp.org/extensions/xep-0060.html#addressing-jidnode) to specify some nodes related to each MUC room. +The JID is the MUC room JID, the NodeID is functionnality we want to address. + +This modules implement the "livechat-notes" node, to handle moderator notes. + +The "livechat-notes" node contains one type of objects: Note (XML Namespaces: `urn:peertube-plugin-livechat:note`). + +On the front-end, we have the [livechat-converse-notes](https://github.com/JohnXLivingston/peertube-plugin-livechat/tree/main/conversejs/custom/plugins/notes) plugin for ConverseJS. + +## Workflow / Unsubscribing + +This is basically the same as for [Tasks](/peertube-plugin-livechat/technical/tasks/). + +## Items + +Here we describes the content of note items. + +* Item tag: `note` +* XML Namespace: `urn:peertube-plugin-livechat:note` +* item attributes: + * `order`: the order of the note in the note list +* item childs: + * `description`: the text content of the note + * `note-about`: an optional tag, if the note is associated to a participant + +The `note-about` tag, if present, has following structure: + +* Item tag: `note-about` +* XML Namespace: none +* item attributes: + * `jid`: the JID of the occupant + * `nick` the nick of the occupant, at time of note creation +* item childs: + * `occupant-id`: see [XEP-0421](https://xmpp.org/extensions/xep-0421.html). + +Example: + +```xml + + + + + + Some text. + + + + + + + + +``` diff --git a/support/documentation/content/en/technical/tasks.md b/support/documentation/content/en/technical/tasks.md index e4b38879..adef52b4 100644 --- a/support/documentation/content/en/technical/tasks.md +++ b/support/documentation/content/en/technical/tasks.md @@ -16,10 +16,12 @@ This XEP provide a way to store and retrieve items, and to receive push notifica There is a Prosody Module, [mod_pubsub_peertubelivechat](https://github.com/JohnXLivingston/peertube-plugin-livechat/tree/main/prosody-modules/mod_pubsub_peertubelivechat), to implement some specific use of the pubsub mechanism. +This module is also used for [Moderator Notes](/peertube-plugin-livechat/technical/moderation_notes/). + We use the [JID+NodeID addressing](https://xmpp.org/extensions/xep-0060.html#addressing-jidnode) to specify some nodes related to each MUC room. The JID is the MUC room JID, the NodeID is functionnality we want to address. -For now, this modules only implement one such node: "livechat-tasks", to handle tasks and task lists. -But the module code anticipates futur uses. + +This modules implement the "livechat-tasks" node, to handle tasks and task lists. The "livechat-tasks" node contains two type of objects: Task and TaskList (XML Namespaces: `urn:peertube-plugin-livechat:tasklist` and `urn:peertube-plugin-livechat:task`). Tasks have an attribute containing their task list id. diff --git a/support/documentation/static/css/livechatdoc.css b/support/documentation/layouts/partials/custom-header.html similarity index 56% rename from support/documentation/static/css/livechatdoc.css rename to support/documentation/layouts/partials/custom-header.html index 3850d345..37f5085d 100644 --- a/support/documentation/static/css/livechatdoc.css +++ b/support/documentation/layouts/partials/custom-header.html @@ -1,16 +1,13 @@ -/* - * SPDX-FileCopyrightText: 2024 John Livingston - * - * SPDX-License-Identifier: AGPL-3.0-only - */ + \ No newline at end of file diff --git a/support/documentation/layouts/partials/custom-header.html.license b/support/documentation/layouts/partials/custom-header.html.license new file mode 100644 index 00000000..b253ad42 --- /dev/null +++ b/support/documentation/layouts/partials/custom-header.html.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2024 John Livingston + +SPDX-License-Identifier: AGPL-3.0-only diff --git a/support/documentation/layouts/partials/logo.html b/support/documentation/layouts/partials/logo.html index a72f60c6..df524763 100644 --- a/support/documentation/layouts/partials/logo.html +++ b/support/documentation/layouts/partials/logo.html @@ -1,5 +1,6 @@ - + \n" -"Language-Team: Arabic \n" +"Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" -"X-Generator: Weblate 5.3.1\n" +"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : (n%100>=3 " +"&& n%100<=10) ? 3 : n%100>=11 ? 4 : 5);\n" +"X-Generator: Weblate 5.7\n" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/contact/_index.md @@ -91,22 +93,22 @@ msgstr "" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "Git" -msgstr "" +msgstr "جيت" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "NodeJS" -msgstr "" +msgstr "NodeJS" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "NPM" -msgstr "" +msgstr "NPM" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "Typescript" -msgstr "" +msgstr "Typescript" #. type: Plain text #: support/documentation/content/en/contributing/develop/_index.md @@ -116,7 +118,7 @@ msgstr "" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "`git`" -msgstr "" +msgstr "`git`" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md @@ -131,7 +133,7 @@ msgstr "" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "`build-essential`" -msgstr "" +msgstr "`build-essential`" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md @@ -146,7 +148,7 @@ msgstr "" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/develop/_index.md msgid "`reuse`" -msgstr "" +msgstr "`reuse`" #. type: Plain text #: support/documentation/content/en/contributing/develop/_index.md @@ -430,7 +432,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md -msgid "The used theme is [hugo-theme-learn](https://learn.netlify.app/). You should read its documentation before starting editing the documentation." +msgid "The minimum required version for Hugo is 0.121.0. It was tested using version 0.132.2." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/contributing/document/_index.md +msgid "The used theme is [hugo-theme-relearn](https://mcshelby.github.io/hugo-theme-relearn). You should read its documentation before starting editing the documentation." msgstr "" #. type: Plain text @@ -479,7 +486,7 @@ msgstr "" #: support/documentation/content/en/contributing/document/_index.md #, no-wrap msgid "Preview" -msgstr "" +msgstr "معاينة" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md @@ -539,7 +546,7 @@ msgstr "" #: support/documentation/content/en/contributing/document/_index.md #, no-wrap msgid "Write documentation" -msgstr "" +msgstr "كتابة الدليل" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md @@ -596,7 +603,7 @@ msgstr "" #: support/documentation/content/en/contributing/document/_index.md #, no-wrap msgid "Publication" -msgstr "" +msgstr "النشر" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md @@ -651,7 +658,7 @@ msgstr "" #: support/documentation/content/en/contributing/translate/_index.md #, no-wrap msgid "How to" -msgstr "" +msgstr "كيف ذلك" #. type: Bullet: '* ' #: support/documentation/content/en/contributing/translate/_index.md @@ -690,10 +697,9 @@ msgstr "" #. type: Title ## #: support/documentation/content/en/contributing/translate/_index.md -#, fuzzy, no-wrap -#| msgid "Translations" +#, no-wrap msgid "ConverseJS translations" -msgstr "الترجمات" +msgstr "ترجمات ConverseJS" #. type: Plain text #: support/documentation/content/en/contributing/translate/_index.md @@ -804,7 +810,7 @@ msgstr "" #: support/documentation/content/en/contributing/translate/_index.md #, no-wrap msgid "Documentation translation" -msgstr "" +msgstr "ترجمة الدليل" #. type: Plain text #: support/documentation/content/en/contributing/translate/_index.md @@ -892,7 +898,7 @@ msgstr "" #: support/documentation/content/en/credits/_index.md #, no-wrap msgid "Credits" -msgstr "" +msgstr "المُساهِمون في المشروع" #. type: Plain text #: support/documentation/content/en/credits/_index.md @@ -943,13 +949,13 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/_index.md #, no-wrap msgid "Some advanced features" -msgstr "" +msgstr "بعض الميزات المتقدمة" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/admin/advanced/_index.md #, no-wrap msgid "Advanced usage" -msgstr "" +msgstr "استخدام متقدم" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/admin/advanced/matterbridge.md @@ -961,7 +967,7 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/matterbridge.md #, no-wrap msgid "Using Matterbridge" -msgstr "" +msgstr "استخدم Matterbridge" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/matterbridge.md @@ -1067,7 +1073,7 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/matterbridge.md #, no-wrap msgid "Configurating Matterbridge" -msgstr "" +msgstr "ضبط Matterbridge" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/matterbridge.md @@ -1183,7 +1189,7 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md #, no-wrap msgid "Plugin settings" -msgstr "" +msgstr "إعدادات الملحق" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -1225,7 +1231,7 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md #, no-wrap msgid "DNS" -msgstr "" +msgstr "نظام أسماء النطاقات" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -1312,7 +1318,7 @@ msgstr "" #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md #, no-wrap msgid "Using trusted certificates" -msgstr "" +msgstr "استخدام شهادات موثوق فيها" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -1342,7 +1348,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md -msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx`" +msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx`" msgstr "" #. type: Plain text @@ -1663,7 +1669,7 @@ msgstr "" #: support/documentation/content/en/documentation/installation/troubleshooting.md #, no-wrap msgid "Troubleshooting" -msgstr "" +msgstr "في حال مشكلة" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -1681,7 +1687,7 @@ msgstr "" #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap msgid "External Authentication" -msgstr "" +msgstr "استيثاق خارجي" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md @@ -1706,13 +1712,13 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a \"{{% livechat_label login_using_external_account %}}\" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px \"{{% livechat_label login_using_external_account %}} button\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog with an \"OpenID Connect\" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px \"External login dialog - OpenID Connect\")" msgstr "" #. type: Plain text @@ -1839,7 +1845,7 @@ msgstr "" #: build/documentation/pot_in/documentation/admin/external_auth.md #, no-wrap msgid "More to come" -msgstr "" +msgstr "المزيد لاحقا…" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md @@ -1854,10 +1860,101 @@ msgstr "" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/admin/_index.md -#, fuzzy, no-wrap -#| msgid "Documentation" +#, no-wrap msgid "Admin documentation" -msgstr "المستندات" +msgstr "مستندات الإدارة" + +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Advanced firewall rules for the Prosody server" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Prosody mod_firewall" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "This feature comes with the livechat plugin version 11.0.0." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Doing so, Peertube admins will be able to define advanced firewall rules." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "These rules could be used to run arbitrary code on the server. If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. You can still use mod_firewall by editing files directly on the server." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Edit rules" +msgstr "تعديل القواعد" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "First, you must enable the feature in the [plugin settings](/peertube-plugin-livechat/documentation/admin/settings)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Just bellow the settings, you will find a \"Configure mod_firewall\" button. This button will open a configuration page." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "![Screenshot of the \"{{% livechat_label prosody_firewall_configuration %}}\" form.](/peertube-plugin-livechat/images/mod_firewall.png?classes=shadow,border&height=400px \"{{% livechat_label prosody_firewall_configuration %}}\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Here you can add several configuration files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can enable/disable each files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Files will be loaded in the alphabetical order. You can use a number as prefix to easily choose the order." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can also edit these firewall rules directly on the server, in the `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` directory. File names must only contains alphanumerical characters, underscores and hyphens. The extension must be `.pfw`, or `.pfw.disabled` if you want to disable a file. Please be sure that the peertube system user has write access to these files, else the web editing interface will fail. Once you have edited these files, you must reload prosody. This can be done by saving the plugin settings, or saving the mod_firewall configuration in the web interface, or by restarting Peertube." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "When you save the configuration, the server will automatically reload it, and your rules will apply immediatly. You can check that there is no parsing error in the Prosody error log. To do so, you can read the `plugins/data/peertube-plugin-livechat/prosody/prosody.err` file, or use the [diagnostic tool](/peertube-plugin-livechat/documentation/installation/troubleshooting/) that will show last Prosody errors." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Examples" +msgstr "أمثلة" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Don't hesitate to share your rules. To do so, you can for example edit this [page](/peertube-plugin-livechat/contributing/document/#write-documentation)." +msgstr "" #. type: Yaml Front Matter Hash Value: description #: build/documentation/pot_in/documentation/admin/settings.md @@ -1869,7 +1966,7 @@ msgstr "" #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap msgid "Settings" -msgstr "" +msgstr "الإعدادات" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -1896,7 +1993,7 @@ msgstr "" #: support/documentation/content/en/intro/_index.md #, no-wrap msgid "Federation" -msgstr "" +msgstr "الفديرالية" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -1905,10 +2002,9 @@ msgstr "" #. type: Title ## #: build/documentation/pot_in/documentation/admin/settings.md -#, fuzzy, no-wrap -#| msgid "Documentation" +#, no-wrap msgid "Authentication" -msgstr "المستندات" +msgstr "الإستيثاق" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -1929,7 +2025,7 @@ msgstr "" #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap msgid "Channel advanced configuration" -msgstr "" +msgstr "ضبط متقدم للقناة" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -1985,7 +2081,7 @@ msgstr "" #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap msgid "Theming" -msgstr "" +msgstr "تخصيص المظهر" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -1999,7 +2095,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px)" +msgid "![Sepia avatar example](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px \"Sepia\")" msgstr "" #. type: Plain text @@ -2009,7 +2105,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Cats](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px)" +msgid "![Cats avatar example](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px \"Cats\")" msgstr "" #. type: Plain text @@ -2019,7 +2115,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Birds](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px)" +msgid "![Birds avatar example](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px \"Birds\")" msgstr "" #. type: Plain text @@ -2029,7 +2125,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px)" +msgid "![Fenecs avatar example](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px \"Fenecs\")" msgstr "" #. type: Plain text @@ -2039,7 +2135,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Abstracts](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px)" +msgid "![Abstracts avatar example](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px \"Abtracts\")" msgstr "" #. type: Plain text @@ -2049,7 +2145,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Legacy](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px)" +msgid "![Legacy avatar example](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px \"Legacy\")" msgstr "" #. type: Plain text @@ -2074,7 +2170,7 @@ msgstr "" #. type: Bullet: '- ' #: build/documentation/pot_in/documentation/admin/settings.md -msgid "ConverseJS concord theme: this is a theme provided by ConverseJS." +msgid "ConverseJS cyberpunk theme: this is a theme provided by ConverseJS." msgstr "" #. type: Title ## @@ -2129,12 +2225,16 @@ msgstr "" msgid "More informations on Prosody external components [here](https://prosody.im/doc/components)." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "For more information, please check [the documentation](/peertube-plugin-livechat/documentation/admin/mod_firewall/)." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/_index.md -#, fuzzy, no-wrap -#| msgid "Documentation" +#, no-wrap msgid "Plugin documentation" -msgstr "المستندات" +msgstr "مستندات الملحق" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/installation/cpu_compatibility.md @@ -2265,7 +2365,7 @@ msgstr "" #: support/documentation/content/en/documentation/installation/_index.md #, no-wrap msgid "Installation guide" -msgstr "" +msgstr "دليل التثبيت" #. type: Plain text #: support/documentation/content/en/documentation/installation/_index.md @@ -2304,7 +2404,7 @@ msgstr "" #: support/documentation/content/en/documentation/installation/troubleshooting.md #, no-wrap msgid "Diagnostic tool" -msgstr "" +msgstr "أدوات الفحص" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md @@ -2318,7 +2418,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Launch diagnostic](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the plugin's settings page, with a \"launch diagnostic\" button.](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px \"Launch diagnostic\")" msgstr "" #. type: Plain text @@ -2328,7 +2428,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Diagnostic result](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the diagnostic result page. This gives a lot of information, with status for different test suites.](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px \"Diagnostic result\")" msgstr "" #. type: Title ## @@ -2363,7 +2463,7 @@ msgstr "" #: support/documentation/content/en/documentation/installation/troubleshooting.md #, no-wrap msgid "Websocket" -msgstr "" +msgstr "Websocket" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md @@ -2396,7 +2496,7 @@ msgstr "" #: support/documentation/content/en/documentation/installation/upgrade_before_6.0.0.md #, no-wrap msgid "IMPORTANT NOTE" -msgstr "" +msgstr "ملاحظة هامة" #. type: Plain text #: support/documentation/content/en/documentation/installation/upgrade_before_6.0.0.md @@ -2421,10 +2521,9 @@ msgstr "" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/_index.md -#, fuzzy, no-wrap -#| msgid "Documentation" +#, no-wrap msgid "User documentation" -msgstr "المستندات" +msgstr "دليل المستخدم" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/obs.md @@ -2457,7 +2556,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px \"Embeding the chat in a live stream\")" msgstr "" #. type: Plain text @@ -2473,7 +2572,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, where the \"{{% livechat_label read_only %}}\" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px \"Share link popup\")" msgstr "" #. type: Plain text @@ -2484,7 +2583,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px \"Embeding the chat in OBS\")" msgstr "" #. type: Plain text @@ -2542,7 +2641,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable.\"](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px \"Share link popup - dock tab\")" msgstr "" #. type: Plain text @@ -2552,12 +2651,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock menu](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Dock menu, with a \"Custom Browser Docks\" entry.](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px \"OBS - Dock menu\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Custom Browser Docks dialog, with a new dock called \"My chat\".](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px \"OBS - Dock dialog\")" msgstr "" #. type: Plain text @@ -2567,7 +2666,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of OBS with a new dock including the chat. The user is logged in with their Peertube account, and can chat directly from OBS.](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px \"OBS - Dock\")" msgstr "" #. type: Plain text @@ -2611,7 +2710,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/basics.md #, no-wrap msgid "Some basics" -msgstr "" +msgstr "بعض الأساسيات" #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2631,7 +2730,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![New live](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the Peertube new live form.](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px \"New live\")" msgstr "" #. type: Plain text @@ -2641,7 +2740,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Activate the chat](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the form, with a \"{{% livechat_label use_chat %}}\" checkbox.](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px \"Activate the chat\")" msgstr "" #. type: Plain text @@ -2664,7 +2763,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/basics.md #, no-wrap msgid "Share the chat" -msgstr "" +msgstr "إعادة نشر المحادثة" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2713,7 +2812,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label web %}} tab. There is a url you can copy.](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px \"Share link popup - web tab\")" msgstr "" #. type: Plain text @@ -2723,7 +2822,8 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - xmpp tab](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +#: support/documentation/content/en/documentation/user/xmpp_clients.md +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label connect_using_xmpp %}}\" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px \"{{% livechat_label connect_using_xmpp %}}\")" msgstr "" #. type: Title ## @@ -2732,7 +2832,7 @@ msgstr "" #: support/documentation/content/en/intro/_index.md #, no-wrap msgid "Moderation" -msgstr "" +msgstr "الإشراف" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2771,7 +2871,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/basics.md #: build/documentation/pot_in/documentation/user/streamers/moderation.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px \"Chat menu\")" msgstr "" #. type: Plain text @@ -2781,7 +2881,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Configure chat room](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat configuration form.](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px \"Configure chat room\")" msgstr "" #. type: Plain text @@ -2825,11 +2925,11 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md #, no-wrap msgid "Commands" -msgstr "" +msgstr "الأوامر" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md -msgid "![Commands configuration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with some fields to configure the bot commands.](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px \"Commands configuration\")" msgstr "" #. type: Plain text @@ -2847,16 +2947,16 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md #, no-wrap msgid "Forbidden words" +msgstr "المصطلحات الممنوعة" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md +msgid "![Screenshot of the channel options page, with several fields to configure the forbidden words.](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px \"Forbidden words configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Forbidden words configuration](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px)" -msgstr "" - -#. type: Plain text -#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Deleted message](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px)" +msgid "![Screenshot of a chat message that was deleted, with the following reason: \"No url allowed\".](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px \"Deleted message\")" msgstr "" #. type: Plain text @@ -2894,6 +2994,12 @@ msgstr "" msgid "These words are case insensitive." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md +#: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +msgid "You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them." +msgstr "" + #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md msgid "This features is still experimental. There might be some issues with non-latin alphabets. You can [open an issue](https://github.com/JohnXLivingston/peertube-plugin-livechat/issues) to report your problems." @@ -2908,14 +3014,14 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #, no-wrap msgid "Chat bot setup" -msgstr "" +msgstr "ضبط روبوت المحادثة" #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #: support/documentation/content/en/intro/_index.md #, no-wrap msgid "Chat bot" -msgstr "" +msgstr "روبوت محادثة" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md @@ -2931,7 +3037,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px \"Channel configuration\")" msgstr "" #. type: Plain text @@ -2968,7 +3074,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/quotes.md -msgid "![Timers configuration](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the channel options page, with some fields to configure a new timer.](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px \"Timers configuration\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -2980,10 +3086,9 @@ msgstr "معلومات عامة" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/streamers/channel.md -#, fuzzy, no-wrap -#| msgid "General information" +#, no-wrap msgid "Channel configuration" -msgstr "معلومات عامة" +msgstr "ضبط القناة" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md @@ -2992,7 +3097,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the chatrooms configuration page. The page list the user's channels.](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px \"Chatrooms menu\")" msgstr "" #. type: Plain text @@ -3045,13 +3150,13 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/emojis.md #, no-wrap msgid "Custom emojis" -msgstr "" +msgstr "الإيموجي المخصصة" #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/emojis.md #, no-wrap msgid "Channel emojis" -msgstr "" +msgstr "الوجوه التعبيرية الخاصة بالقناة" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md @@ -3065,12 +3170,12 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis configuration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the emoji configuration page. There is a form where you can add new emojis.](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session, with messages containing custom emojis. The emoji picker is open, and shows custom emojis.](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis\")" msgstr "" #. type: Title ### @@ -3116,7 +3221,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/_index.md #, no-wrap msgid "For streamers" -msgstr "" +msgstr "للناشرين على المباشر" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md @@ -3137,6 +3242,7 @@ msgstr "" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #: support/documentation/content/en/documentation/user/streamers/tasks.md #: support/documentation/content/en/intro/_index.md @@ -3172,7 +3278,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel option form, with a field to configure the moderation delay.](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Moderation delay\")" msgstr "" #. type: Plain text @@ -3215,7 +3321,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Moderation delay timer](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border)" +msgid "![Screenshot of a chat message. A timer is displayed next to the message datetime. The timer is in seconds.](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border \"Moderation delay timer\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -3241,7 +3347,7 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap msgid "The chat bot" -msgstr "" +msgstr "روبوت المحادثة" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3252,7 +3358,7 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap msgid "Accessing moderation tools" -msgstr "" +msgstr "الوصول إلى أدوات الإشراف" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3293,7 +3399,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the room configuration form. There is a \"{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}\" checkbox.](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px \"Room configuration / Mute anonymous users\")" msgstr "" #. type: Plain text @@ -3303,7 +3409,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. The current user has no message field. There is a message: \"{{% livechat_label muted_anonymous_message %}}\"](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px \"Room configuration / Muted anonymous users\")" msgstr "" #. type: Plain text @@ -3337,6 +3443,81 @@ msgstr "" msgid "You can promote users as moderators, if you need some help." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "It is possible to anonymize moderation actions, to avoid disclosing who is banning/kicking/… occupants." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the \"configure\" menu. In the form, you will find a \"{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}\" checkbox." +msgstr "" + +#. type: Title ## +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#, no-wrap +msgid "Participant message history search" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "As a room admin or owner, you can search all messages sent by a given participant." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To do so, you have several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "![Screenshot of a chat session. The moderator has open the message menu, and there is a \"{{% livechat_label search_occupant_message %}}\" button.](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px \"Message history search\")" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "To have more space and better readability, open the chat in full-page mode." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "In the search results, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "if the current nickname is different than the nickname when the participant has sent the message, the original nickname will be shown" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "The search result will also include all messages related to participants who had the same nickname. You can differenciate them by comparing [JID](https://xmpp.org/extensions/xep-0029.html) and [occupant-id](https://xmpp.org/extensions/xep-0421.html)." +msgstr "" + #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap @@ -3352,7 +3533,7 @@ msgstr "" #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap msgid "Instance moderation" -msgstr "" +msgstr "الإشراف على الخادم" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3369,6 +3550,230 @@ msgstr "" msgid "From there, you can also promote yourself as room moderator by using the \"{{% livechat_label promote %}}\" button on the right." msgstr "" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Plugin peertube-plugin-livechat moderation notes" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Moderation notes" +msgstr "ملاحظات الإشراف" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The livechat plugin includes a Moderator Notes Application: you can write some notes, that could be associated to chat participants. Every room's admins have access to these notes, so they can edit them collaboratively." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can for example use this Application to:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "share some notes between moderators" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "take notes about participants that were kicked or caused troubles" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "..." +msgstr "..." + +#. type: Title ## +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Using the Moderator Notes Application" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Opening the Moderator Notes Application" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "To open the Moderator Notes Application, there is a \"{{% livechat_label \"moderator_notes\" %}}\" button in the top chat menu:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_video.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Clicking this button will toggle the Application display:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/polls.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +#, no-wrap +msgid "Access rights" +msgstr "صلاحيات الوصول" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Every room's admins have access to this Application (read and write access)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you promote someone as room admin or owner, they gets instant access to this Application. When you remove admin or owner rights to someone, they instantly lose access to this Application." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Scope" +msgstr "النطاق" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Notes are only available in the room in which you have created them." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Chatrooms can be releated to video or channel. If you want to keep notes from one video to another, please consider using rooms associated to channels." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Currently the video vs channel rooms is an instance-wide settings. Only Peertube admins can change it, and it applies to all chatrooms. In the future, this choice will be added in your channel's options." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes" +msgstr "الملاحظات" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Create/Edit Notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can use the plus button on the top to create a new note. You can also edit existing notes using the edit button, or delate any note." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can create a note associated to a participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When a note is associated to a participant, you will see their nickname and avatar on the top of the note." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes filtering" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can filter notes to find all notes related to a given participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button that is available on notes to find all notes related to the same participant" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can remove the filter by clicking on the close button." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of the note application, with a filter enabled for user \"Mike\". The only notes that are shown are the notes for the Mike user.](/peertube-plugin-livechat/images/moderation_notes_filters.png?classes=shadow,border&height=200px \"Moderator Notes Application - filtering\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you filters notes on a participant, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "if the current nickname is different than the nickname when you created the note, the original nickname will be shown" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The search result will also include all notes related to participants who had the same nickname. So you can also take note for anonymous users (who don't have any consistent JID or occupant-id). You can differenciate them by comparing JID and occupant-id." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Sorting notes" +msgstr "ترتيب الملاحظات" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can sort notes simply using drag & drop." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/polls.md #, no-wrap @@ -3379,7 +3784,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/polls.md #, no-wrap msgid "Polls" -msgstr "" +msgstr "استطلاعات الرأي" #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/polls.md @@ -3394,7 +3799,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a \"{{% livechat_label new_poll %}}\" form. The form contains several fields: question, duration, choices, …](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px \"Poll form\")" msgstr "" #. type: Plain text @@ -3448,13 +3853,6 @@ msgstr "" msgid "If there was a previous unfinished poll, it will end and its result will be shown." msgstr "" -#. type: Title ### -#: support/documentation/content/en/documentation/user/streamers/polls.md -#: support/documentation/content/en/documentation/user/streamers/tasks.md -#, no-wrap -msgid "Access rights" -msgstr "" - #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md msgid "Every room's admins can create a new poll." @@ -3493,7 +3891,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. In the chat, there is a message with the poll question, and the different choices. There is also a banner on the top of the chat, where you can see the question, and the number of votes for each answers.](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px \"Poll start\")" msgstr "" #. type: Plain text @@ -3513,7 +3911,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll votes](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with an ongoing poll. The current user has just voted by sending \"!1\".](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px \"Poll votes\")" msgstr "" #. type: Plain text @@ -3538,7 +3936,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with poll that has ended. The banner no more accept new votes. There is a message in the chat with the poll results. For each choice, there is the number of votes, and the percentage of the total it represents.](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px \"Poll end\")" msgstr "" #. type: Plain text @@ -3556,7 +3954,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #, no-wrap msgid "Slow mode" -msgstr "" +msgstr "الوضع البطيء" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md @@ -3597,7 +3995,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #, no-wrap msgid "Slow mode option" -msgstr "" +msgstr "ميزة الوضع البطيء" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md @@ -3606,7 +4004,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Channel configuration / Slow Mode](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a slow mode field.](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Slow Mode\")" msgstr "" #. type: Plain text @@ -3633,7 +4031,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. There is a banner on the bottom of the chat, indicating that the slow mode is enabled, and that users can send a message every 2 seconds.](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px \"Slow mode infobox\")" msgstr "" #. type: Plain text @@ -3678,11 +4076,6 @@ msgstr "" msgid "highlight questions from your viewers, so you can come back to them later without forgetting to answer them" msgstr "" -#. type: Bullet: '* ' -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "..." -msgstr "" - #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap @@ -3702,12 +4095,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text @@ -3717,17 +4110,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "To have more space and better readability, open the chat in full-page mode." +msgid "![Screenshot of a Peertube chat, fullscreen. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text @@ -3744,7 +4132,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap msgid "Task lists" -msgstr "" +msgstr "قائمة المهام" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3763,25 +4151,20 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task lists](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgid "![Screenshot of a chat session, with the Task application. There are several task lists.](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px \"Task lists\")" msgstr "" #. type: Title ### #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap msgid "Tasks" -msgstr "" +msgstr "المهام" #. type: Title #### #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap msgid "Create tasks" -msgstr "" +msgstr "إنشاء مهام" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3790,19 +4173,19 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task form](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, there is a form to create a new task.](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px \"Task form\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, a new task was created.](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Title #### #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap msgid "Edit tasks" -msgstr "" +msgstr "تعديل المهام" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3816,7 +4199,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under task lists, there are several tasks. Some of them are checked, other not.](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px \"Tasks\")" msgstr "" #. type: Title #### @@ -3832,12 +4215,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to sort](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another.](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px \"Drag and drop to sort\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to move to another list](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another task list.](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px \"Drag and drop to move to another list\")" msgstr "" #. type: Title #### @@ -3853,17 +4236,17 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Create task from message](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. The menu besides a message is open, with a button to create a new task.](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px \"Create task from message\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Choose the task list](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog, where you can choose in which task list you want to add the new task.](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px \"Choose the task list\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. A new task was added in the \"chat questions\" task list, with the user's nickname, and the message as content.](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Plain text @@ -3886,10 +4269,9 @@ msgstr "" #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/terms.md -#, fuzzy, no-wrap -#| msgid "General information" +#, no-wrap msgid "Configuration" -msgstr "معلومات عامة" +msgstr "الضبط" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md @@ -3903,7 +4285,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Channel configuration / Terms](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a field to configure your terms and conditions.](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px \"Channel configuration / Terms\")" msgstr "" #. type: Plain text @@ -3915,7 +4297,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/terms.md #, no-wrap msgid "Viewers" -msgstr "" +msgstr "مشاهدون" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md @@ -3924,7 +4306,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. On the top of the chat, there are terms and conditions for both the server and the channel.](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px \"Terms\")" msgstr "" #. type: Plain text @@ -3962,7 +4344,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #, no-wrap msgid "Joining chat rooms" -msgstr "" +msgstr "الإلتحاق بغرف المحادثة" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -3973,7 +4355,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/_index.md #: support/documentation/content/en/intro/_index.md -msgid "![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px \"Chat screenshot\")" msgstr "" #. type: Plain text @@ -3994,7 +4376,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat. In the participant list, there is John Livingston, and an anonymous account using \"Anonymous 212873\" nickname.](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px \"Chat with an anonymous user\")" msgstr "" #. type: Plain text @@ -4004,7 +4386,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Joining chat when not connected](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat. The current user is not logged in, and must choose a nickname before being able to write in the chat.](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px \"Joining chat when not connected\")" msgstr "" #. type: Title #### @@ -4057,14 +4439,14 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label login_using_external_account %}}\" dialog. There is a field where you can enter a Peertube url.](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px \"External login dialog\")" msgstr "" #. type: Title ## #: support/documentation/content/en/documentation/user/viewers.md #, no-wrap msgid "Chatting" -msgstr "" +msgstr "الدردشة" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4090,7 +4472,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #, no-wrap msgid "Participants list" -msgstr "" +msgstr "قائمة المشاركين" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4099,7 +4481,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Participants list](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with on the right the list of participants.](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px \"Participants list\")" msgstr "" #. type: Plain text @@ -4132,7 +4514,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/intro/_index.md -msgid "![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px \"Fullscreen chat screenshot\")" msgstr "" #. type: Plain text @@ -4144,7 +4526,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #, no-wrap msgid "Changing nickname" -msgstr "" +msgstr "تغيير الكنية" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4190,7 +4572,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share button](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat, with a share button on the top.](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px \"Share button\")" msgstr "" #. type: Plain text @@ -4200,12 +4582,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "Then, choose \"Connect using XMPP\":" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +msgid "Then, choose \"{{% livechat_label connect_using_xmpp %}}\":" msgstr "" #. type: Plain text @@ -4213,10 +4590,17 @@ msgstr "" msgid "Then you just have to click on \"open\" or copy/paste the address of the chat room into your XMPP client (using the \"join a room\" feature)." msgstr "" -#. type: Title # +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/_index.md +#, fuzzy, no-wrap +#| msgid "Documentation" +msgid "Peertube plugin livechat documentation" +msgstr "المستندات" + +#. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/_index.md #, no-wrap -msgid "PeerTube plugin livechat" +msgid "Peertube livechat" msgstr "" #. type: Plain text @@ -4284,7 +4668,7 @@ msgstr "" #: support/documentation/content/en/intro/_index.md #, no-wrap msgid "Installation" -msgstr "" +msgstr "التثبيت" #. type: Plain text #: support/documentation/content/en/intro/_index.md @@ -4293,7 +4677,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/intro/_index.md -msgid "![Livechat installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of Peertube plugins admin page. The search fields contains \"livechat\", and the search results show the livechat plugin.](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px \"Livechat installation\")" msgstr "" #. type: Title ## @@ -4372,7 +4756,7 @@ msgstr "" #: support/documentation/content/en/intro/_index.md #, no-wrap msgid "Other usages" -msgstr "" +msgstr "استخدامات أخرى" #. type: Plain text #: support/documentation/content/en/intro/_index.md diff --git a/support/documentation/po/livechat.ca.po b/support/documentation/po/livechat.ca.po index a721daee..869b9ee8 100644 --- a/support/documentation/po/livechat.ca.po +++ b/support/documentation/po/livechat.ca.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: peertube-plugin-livechat-documentation VERSION\n" -"POT-Creation-Date: 2024-07-10 16:54+0200\n" +"POT-Creation-Date: 2024-08-30 16:24+0200\n" "PO-Revision-Date: 2023-07-17 10:52+0000\n" "Last-Translator: Anonymous \n" "Language-Team: Catalan \n" @@ -430,7 +430,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md -msgid "The used theme is [hugo-theme-learn](https://learn.netlify.app/). You should read its documentation before starting editing the documentation." +msgid "The minimum required version for Hugo is 0.121.0. It was tested using version 0.132.2." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/contributing/document/_index.md +msgid "The used theme is [hugo-theme-relearn](https://mcshelby.github.io/hugo-theme-relearn). You should read its documentation before starting editing the documentation." msgstr "" #. type: Plain text @@ -1340,7 +1345,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md -msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx`" +msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx`" msgstr "" #. type: Plain text @@ -1704,13 +1709,13 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a \"{{% livechat_label login_using_external_account %}}\" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px \"{{% livechat_label login_using_external_account %}} button\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog with an \"OpenID Connect\" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px \"External login dialog - OpenID Connect\")" msgstr "" #. type: Plain text @@ -1856,6 +1861,98 @@ msgstr "" msgid "Admin documentation" msgstr "" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Advanced firewall rules for the Prosody server" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Prosody mod_firewall" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "This feature comes with the livechat plugin version 11.0.0." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Doing so, Peertube admins will be able to define advanced firewall rules." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "These rules could be used to run arbitrary code on the server. If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. You can still use mod_firewall by editing files directly on the server." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Edit rules" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "First, you must enable the feature in the [plugin settings](/peertube-plugin-livechat/documentation/admin/settings)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Just bellow the settings, you will find a \"Configure mod_firewall\" button. This button will open a configuration page." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "![Screenshot of the \"{{% livechat_label prosody_firewall_configuration %}}\" form.](/peertube-plugin-livechat/images/mod_firewall.png?classes=shadow,border&height=400px \"{{% livechat_label prosody_firewall_configuration %}}\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Here you can add several configuration files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can enable/disable each files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Files will be loaded in the alphabetical order. You can use a number as prefix to easily choose the order." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can also edit these firewall rules directly on the server, in the `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` directory. File names must only contains alphanumerical characters, underscores and hyphens. The extension must be `.pfw`, or `.pfw.disabled` if you want to disable a file. Please be sure that the peertube system user has write access to these files, else the web editing interface will fail. Once you have edited these files, you must reload prosody. This can be done by saving the plugin settings, or saving the mod_firewall configuration in the web interface, or by restarting Peertube." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "When you save the configuration, the server will automatically reload it, and your rules will apply immediatly. You can check that there is no parsing error in the Prosody error log. To do so, you can read the `plugins/data/peertube-plugin-livechat/prosody/prosody.err` file, or use the [diagnostic tool](/peertube-plugin-livechat/documentation/installation/troubleshooting/) that will show last Prosody errors." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Examples" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Don't hesitate to share your rules. To do so, you can for example edit this [page](/peertube-plugin-livechat/contributing/document/#write-documentation)." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap @@ -1995,7 +2092,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px)" +msgid "![Sepia avatar example](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px \"Sepia\")" msgstr "" #. type: Plain text @@ -2005,7 +2102,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Cats](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px)" +msgid "![Cats avatar example](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px \"Cats\")" msgstr "" #. type: Plain text @@ -2015,7 +2112,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Birds](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px)" +msgid "![Birds avatar example](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px \"Birds\")" msgstr "" #. type: Plain text @@ -2025,7 +2122,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px)" +msgid "![Fenecs avatar example](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px \"Fenecs\")" msgstr "" #. type: Plain text @@ -2035,7 +2132,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Abstracts](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px)" +msgid "![Abstracts avatar example](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px \"Abtracts\")" msgstr "" #. type: Plain text @@ -2045,7 +2142,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Legacy](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px)" +msgid "![Legacy avatar example](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px \"Legacy\")" msgstr "" #. type: Plain text @@ -2070,7 +2167,7 @@ msgstr "" #. type: Bullet: '- ' #: build/documentation/pot_in/documentation/admin/settings.md -msgid "ConverseJS concord theme: this is a theme provided by ConverseJS." +msgid "ConverseJS cyberpunk theme: this is a theme provided by ConverseJS." msgstr "" #. type: Title ## @@ -2125,6 +2222,11 @@ msgstr "" msgid "More informations on Prosody external components [here](https://prosody.im/doc/components)." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "For more information, please check [the documentation](/peertube-plugin-livechat/documentation/admin/mod_firewall/)." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/_index.md #, no-wrap @@ -2313,7 +2415,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Launch diagnostic](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the plugin's settings page, with a \"launch diagnostic\" button.](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px \"Launch diagnostic\")" msgstr "" #. type: Plain text @@ -2323,7 +2425,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Diagnostic result](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the diagnostic result page. This gives a lot of information, with status for different test suites.](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px \"Diagnostic result\")" msgstr "" #. type: Title ## @@ -2451,7 +2553,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px \"Embeding the chat in a live stream\")" msgstr "" #. type: Plain text @@ -2467,7 +2569,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, where the \"{{% livechat_label read_only %}}\" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px \"Share link popup\")" msgstr "" #. type: Plain text @@ -2478,7 +2580,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px \"Embeding the chat in OBS\")" msgstr "" #. type: Plain text @@ -2536,7 +2638,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable.\"](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px \"Share link popup - dock tab\")" msgstr "" #. type: Plain text @@ -2546,12 +2648,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock menu](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Dock menu, with a \"Custom Browser Docks\" entry.](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px \"OBS - Dock menu\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Custom Browser Docks dialog, with a new dock called \"My chat\".](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px \"OBS - Dock dialog\")" msgstr "" #. type: Plain text @@ -2561,7 +2663,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of OBS with a new dock including the chat. The user is logged in with their Peertube account, and can chat directly from OBS.](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px \"OBS - Dock\")" msgstr "" #. type: Plain text @@ -2625,7 +2727,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![New live](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the Peertube new live form.](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px \"New live\")" msgstr "" #. type: Plain text @@ -2635,7 +2737,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Activate the chat](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the form, with a \"{{% livechat_label use_chat %}}\" checkbox.](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px \"Activate the chat\")" msgstr "" #. type: Plain text @@ -2707,7 +2809,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label web %}} tab. There is a url you can copy.](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px \"Share link popup - web tab\")" msgstr "" #. type: Plain text @@ -2717,7 +2819,8 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - xmpp tab](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +#: support/documentation/content/en/documentation/user/xmpp_clients.md +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label connect_using_xmpp %}}\" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px \"{{% livechat_label connect_using_xmpp %}}\")" msgstr "" #. type: Title ## @@ -2765,7 +2868,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/basics.md #: build/documentation/pot_in/documentation/user/streamers/moderation.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px \"Chat menu\")" msgstr "" #. type: Plain text @@ -2775,7 +2878,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Configure chat room](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat configuration form.](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px \"Configure chat room\")" msgstr "" #. type: Plain text @@ -2823,7 +2926,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md -msgid "![Commands configuration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with some fields to configure the bot commands.](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px \"Commands configuration\")" msgstr "" #. type: Plain text @@ -2845,12 +2948,12 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Forbidden words configuration](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with several fields to configure the forbidden words.](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px \"Forbidden words configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Deleted message](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px)" +msgid "![Screenshot of a chat message that was deleted, with the following reason: \"No url allowed\".](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px \"Deleted message\")" msgstr "" #. type: Plain text @@ -2888,6 +2991,12 @@ msgstr "" msgid "These words are case insensitive." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md +#: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +msgid "You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them." +msgstr "" + #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md msgid "This features is still experimental. There might be some issues with non-latin alphabets. You can [open an issue](https://github.com/JohnXLivingston/peertube-plugin-livechat/issues) to report your problems." @@ -2925,7 +3034,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px \"Channel configuration\")" msgstr "" #. type: Plain text @@ -2962,7 +3071,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/quotes.md -msgid "![Timers configuration](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the channel options page, with some fields to configure a new timer.](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px \"Timers configuration\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -2984,7 +3093,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the chatrooms configuration page. The page list the user's channels.](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px \"Chatrooms menu\")" msgstr "" #. type: Plain text @@ -3057,12 +3166,12 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis configuration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the emoji configuration page. There is a form where you can add new emojis.](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session, with messages containing custom emojis. The emoji picker is open, and shows custom emojis.](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis\")" msgstr "" #. type: Title ### @@ -3129,6 +3238,7 @@ msgstr "" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #: support/documentation/content/en/documentation/user/streamers/tasks.md #: support/documentation/content/en/intro/_index.md @@ -3164,7 +3274,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel option form, with a field to configure the moderation delay.](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Moderation delay\")" msgstr "" #. type: Plain text @@ -3207,7 +3317,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Moderation delay timer](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border)" +msgid "![Screenshot of a chat message. A timer is displayed next to the message datetime. The timer is in seconds.](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border \"Moderation delay timer\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -3285,7 +3395,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the room configuration form. There is a \"{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}\" checkbox.](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px \"Room configuration / Mute anonymous users\")" msgstr "" #. type: Plain text @@ -3295,7 +3405,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. The current user has no message field. There is a message: \"{{% livechat_label muted_anonymous_message %}}\"](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px \"Room configuration / Muted anonymous users\")" msgstr "" #. type: Plain text @@ -3329,6 +3439,81 @@ msgstr "" msgid "You can promote users as moderators, if you need some help." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "It is possible to anonymize moderation actions, to avoid disclosing who is banning/kicking/… occupants." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the \"configure\" menu. In the form, you will find a \"{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}\" checkbox." +msgstr "" + +#. type: Title ## +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#, no-wrap +msgid "Participant message history search" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "As a room admin or owner, you can search all messages sent by a given participant." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To do so, you have several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "![Screenshot of a chat session. The moderator has open the message menu, and there is a \"{{% livechat_label search_occupant_message %}}\" button.](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px \"Message history search\")" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "To have more space and better readability, open the chat in full-page mode." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "In the search results, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "if the current nickname is different than the nickname when the participant has sent the message, the original nickname will be shown" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "The search result will also include all messages related to participants who had the same nickname. You can differenciate them by comparing [JID](https://xmpp.org/extensions/xep-0029.html) and [occupant-id](https://xmpp.org/extensions/xep-0421.html)." +msgstr "" + #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap @@ -3361,6 +3546,230 @@ msgstr "" msgid "From there, you can also promote yourself as room moderator by using the \"{{% livechat_label promote %}}\" button on the right." msgstr "" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Plugin peertube-plugin-livechat moderation notes" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Moderation notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The livechat plugin includes a Moderator Notes Application: you can write some notes, that could be associated to chat participants. Every room's admins have access to these notes, so they can edit them collaboratively." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can for example use this Application to:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "share some notes between moderators" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "take notes about participants that were kicked or caused troubles" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "..." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Using the Moderator Notes Application" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Opening the Moderator Notes Application" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "To open the Moderator Notes Application, there is a \"{{% livechat_label \"moderator_notes\" %}}\" button in the top chat menu:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_video.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Clicking this button will toggle the Application display:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/polls.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +#, no-wrap +msgid "Access rights" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Every room's admins have access to this Application (read and write access)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you promote someone as room admin or owner, they gets instant access to this Application. When you remove admin or owner rights to someone, they instantly lose access to this Application." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Scope" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Notes are only available in the room in which you have created them." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Chatrooms can be releated to video or channel. If you want to keep notes from one video to another, please consider using rooms associated to channels." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Currently the video vs channel rooms is an instance-wide settings. Only Peertube admins can change it, and it applies to all chatrooms. In the future, this choice will be added in your channel's options." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes" +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Create/Edit Notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can use the plus button on the top to create a new note. You can also edit existing notes using the edit button, or delate any note." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can create a note associated to a participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When a note is associated to a participant, you will see their nickname and avatar on the top of the note." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes filtering" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can filter notes to find all notes related to a given participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button that is available on notes to find all notes related to the same participant" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can remove the filter by clicking on the close button." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of the note application, with a filter enabled for user \"Mike\". The only notes that are shown are the notes for the Mike user.](/peertube-plugin-livechat/images/moderation_notes_filters.png?classes=shadow,border&height=200px \"Moderator Notes Application - filtering\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you filters notes on a participant, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "if the current nickname is different than the nickname when you created the note, the original nickname will be shown" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The search result will also include all notes related to participants who had the same nickname. So you can also take note for anonymous users (who don't have any consistent JID or occupant-id). You can differenciate them by comparing JID and occupant-id." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Sorting notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can sort notes simply using drag & drop." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/polls.md #, no-wrap @@ -3386,7 +3795,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a \"{{% livechat_label new_poll %}}\" form. The form contains several fields: question, duration, choices, …](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px \"Poll form\")" msgstr "" #. type: Plain text @@ -3440,13 +3849,6 @@ msgstr "" msgid "If there was a previous unfinished poll, it will end and its result will be shown." msgstr "" -#. type: Title ### -#: support/documentation/content/en/documentation/user/streamers/polls.md -#: support/documentation/content/en/documentation/user/streamers/tasks.md -#, no-wrap -msgid "Access rights" -msgstr "" - #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md msgid "Every room's admins can create a new poll." @@ -3485,7 +3887,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. In the chat, there is a message with the poll question, and the different choices. There is also a banner on the top of the chat, where you can see the question, and the number of votes for each answers.](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px \"Poll start\")" msgstr "" #. type: Plain text @@ -3505,7 +3907,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll votes](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with an ongoing poll. The current user has just voted by sending \"!1\".](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px \"Poll votes\")" msgstr "" #. type: Plain text @@ -3530,7 +3932,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with poll that has ended. The banner no more accept new votes. There is a message in the chat with the poll results. For each choice, there is the number of votes, and the percentage of the total it represents.](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px \"Poll end\")" msgstr "" #. type: Plain text @@ -3598,7 +4000,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Channel configuration / Slow Mode](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a slow mode field.](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Slow Mode\")" msgstr "" #. type: Plain text @@ -3625,7 +4027,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. There is a banner on the bottom of the chat, indicating that the slow mode is enabled, and that users can send a message every 2 seconds.](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px \"Slow mode infobox\")" msgstr "" #. type: Plain text @@ -3670,11 +4072,6 @@ msgstr "" msgid "highlight questions from your viewers, so you can come back to them later without forgetting to answer them" msgstr "" -#. type: Bullet: '* ' -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "..." -msgstr "" - #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap @@ -3694,12 +4091,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text @@ -3709,17 +4106,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "To have more space and better readability, open the chat in full-page mode." +msgid "![Screenshot of a Peertube chat, fullscreen. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text @@ -3755,12 +4147,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task lists](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgid "![Screenshot of a chat session, with the Task application. There are several task lists.](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px \"Task lists\")" msgstr "" #. type: Title ### @@ -3782,12 +4169,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task form](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, there is a form to create a new task.](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px \"Task form\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, a new task was created.](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Title #### @@ -3808,7 +4195,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under task lists, there are several tasks. Some of them are checked, other not.](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px \"Tasks\")" msgstr "" #. type: Title #### @@ -3824,12 +4211,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to sort](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another.](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px \"Drag and drop to sort\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to move to another list](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another task list.](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px \"Drag and drop to move to another list\")" msgstr "" #. type: Title #### @@ -3845,17 +4232,17 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Create task from message](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. The menu besides a message is open, with a button to create a new task.](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px \"Create task from message\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Choose the task list](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog, where you can choose in which task list you want to add the new task.](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px \"Choose the task list\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. A new task was added in the \"chat questions\" task list, with the user's nickname, and the message as content.](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Plain text @@ -3893,7 +4280,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Channel configuration / Terms](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a field to configure your terms and conditions.](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px \"Channel configuration / Terms\")" msgstr "" #. type: Plain text @@ -3914,7 +4301,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. On the top of the chat, there are terms and conditions for both the server and the channel.](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px \"Terms\")" msgstr "" #. type: Plain text @@ -3963,7 +4350,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/_index.md #: support/documentation/content/en/intro/_index.md -msgid "![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px \"Chat screenshot\")" msgstr "" #. type: Plain text @@ -3984,7 +4371,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat. In the participant list, there is John Livingston, and an anonymous account using \"Anonymous 212873\" nickname.](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px \"Chat with an anonymous user\")" msgstr "" #. type: Plain text @@ -3994,7 +4381,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Joining chat when not connected](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat. The current user is not logged in, and must choose a nickname before being able to write in the chat.](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px \"Joining chat when not connected\")" msgstr "" #. type: Title #### @@ -4047,7 +4434,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label login_using_external_account %}}\" dialog. There is a field where you can enter a Peertube url.](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px \"External login dialog\")" msgstr "" #. type: Title ## @@ -4089,7 +4476,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Participants list](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with on the right the list of participants.](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px \"Participants list\")" msgstr "" #. type: Plain text @@ -4122,7 +4509,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/intro/_index.md -msgid "![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px \"Fullscreen chat screenshot\")" msgstr "" #. type: Plain text @@ -4180,7 +4567,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share button](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat, with a share button on the top.](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px \"Share button\")" msgstr "" #. type: Plain text @@ -4190,12 +4577,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "Then, choose \"Connect using XMPP\":" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +msgid "Then, choose \"{{% livechat_label connect_using_xmpp %}}\":" msgstr "" #. type: Plain text @@ -4203,10 +4585,16 @@ msgstr "" msgid "Then you just have to click on \"open\" or copy/paste the address of the chat room into your XMPP client (using the \"join a room\" feature)." msgstr "" -#. type: Title # +#. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/_index.md #, no-wrap -msgid "PeerTube plugin livechat" +msgid "Peertube plugin livechat documentation" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/_index.md +#, no-wrap +msgid "Peertube livechat" msgstr "" #. type: Plain text @@ -4283,7 +4671,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/intro/_index.md -msgid "![Livechat installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of Peertube plugins admin page. The search fields contains \"livechat\", and the search results show the livechat plugin.](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px \"Livechat installation\")" msgstr "" #. type: Title ## diff --git a/support/documentation/po/livechat.cs.po b/support/documentation/po/livechat.cs.po index 55c06eb1..50a6cd72 100644 --- a/support/documentation/po/livechat.cs.po +++ b/support/documentation/po/livechat.cs.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: peertube-plugin-livechat-documentation VERSION\n" -"POT-Creation-Date: 2024-07-10 16:54+0200\n" +"POT-Creation-Date: 2024-08-30 16:24+0200\n" "PO-Revision-Date: 2023-07-17 10:52+0000\n" "Last-Translator: Anonymous \n" "Language-Team: Czech \n" @@ -430,7 +430,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md -msgid "The used theme is [hugo-theme-learn](https://learn.netlify.app/). You should read its documentation before starting editing the documentation." +msgid "The minimum required version for Hugo is 0.121.0. It was tested using version 0.132.2." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/contributing/document/_index.md +msgid "The used theme is [hugo-theme-relearn](https://mcshelby.github.io/hugo-theme-relearn). You should read its documentation before starting editing the documentation." msgstr "" #. type: Plain text @@ -1340,7 +1345,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md -msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx`" +msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx`" msgstr "" #. type: Plain text @@ -1704,13 +1709,13 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a \"{{% livechat_label login_using_external_account %}}\" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px \"{{% livechat_label login_using_external_account %}} button\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog with an \"OpenID Connect\" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px \"External login dialog - OpenID Connect\")" msgstr "" #. type: Plain text @@ -1856,6 +1861,98 @@ msgstr "" msgid "Admin documentation" msgstr "" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Advanced firewall rules for the Prosody server" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Prosody mod_firewall" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "This feature comes with the livechat plugin version 11.0.0." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Doing so, Peertube admins will be able to define advanced firewall rules." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "These rules could be used to run arbitrary code on the server. If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. You can still use mod_firewall by editing files directly on the server." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Edit rules" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "First, you must enable the feature in the [plugin settings](/peertube-plugin-livechat/documentation/admin/settings)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Just bellow the settings, you will find a \"Configure mod_firewall\" button. This button will open a configuration page." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "![Screenshot of the \"{{% livechat_label prosody_firewall_configuration %}}\" form.](/peertube-plugin-livechat/images/mod_firewall.png?classes=shadow,border&height=400px \"{{% livechat_label prosody_firewall_configuration %}}\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Here you can add several configuration files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can enable/disable each files." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Files will be loaded in the alphabetical order. You can use a number as prefix to easily choose the order." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can also edit these firewall rules directly on the server, in the `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` directory. File names must only contains alphanumerical characters, underscores and hyphens. The extension must be `.pfw`, or `.pfw.disabled` if you want to disable a file. Please be sure that the peertube system user has write access to these files, else the web editing interface will fail. Once you have edited these files, you must reload prosody. This can be done by saving the plugin settings, or saving the mod_firewall configuration in the web interface, or by restarting Peertube." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "When you save the configuration, the server will automatically reload it, and your rules will apply immediatly. You can check that there is no parsing error in the Prosody error log. To do so, you can read the `plugins/data/peertube-plugin-livechat/prosody/prosody.err` file, or use the [diagnostic tool](/peertube-plugin-livechat/documentation/installation/troubleshooting/) that will show last Prosody errors." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Examples" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Don't hesitate to share your rules. To do so, you can for example edit this [page](/peertube-plugin-livechat/contributing/document/#write-documentation)." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap @@ -1995,7 +2092,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px)" +msgid "![Sepia avatar example](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px \"Sepia\")" msgstr "" #. type: Plain text @@ -2005,7 +2102,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Cats](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px)" +msgid "![Cats avatar example](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px \"Cats\")" msgstr "" #. type: Plain text @@ -2015,7 +2112,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Birds](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px)" +msgid "![Birds avatar example](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px \"Birds\")" msgstr "" #. type: Plain text @@ -2025,7 +2122,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px)" +msgid "![Fenecs avatar example](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px \"Fenecs\")" msgstr "" #. type: Plain text @@ -2035,7 +2132,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Abstracts](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px)" +msgid "![Abstracts avatar example](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px \"Abtracts\")" msgstr "" #. type: Plain text @@ -2045,7 +2142,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Legacy](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px)" +msgid "![Legacy avatar example](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px \"Legacy\")" msgstr "" #. type: Plain text @@ -2070,7 +2167,7 @@ msgstr "" #. type: Bullet: '- ' #: build/documentation/pot_in/documentation/admin/settings.md -msgid "ConverseJS concord theme: this is a theme provided by ConverseJS." +msgid "ConverseJS cyberpunk theme: this is a theme provided by ConverseJS." msgstr "" #. type: Title ## @@ -2125,6 +2222,11 @@ msgstr "" msgid "More informations on Prosody external components [here](https://prosody.im/doc/components)." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "For more information, please check [the documentation](/peertube-plugin-livechat/documentation/admin/mod_firewall/)." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/_index.md #, no-wrap @@ -2313,7 +2415,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Launch diagnostic](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the plugin's settings page, with a \"launch diagnostic\" button.](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px \"Launch diagnostic\")" msgstr "" #. type: Plain text @@ -2323,7 +2425,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Diagnostic result](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the diagnostic result page. This gives a lot of information, with status for different test suites.](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px \"Diagnostic result\")" msgstr "" #. type: Title ## @@ -2451,7 +2553,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px \"Embeding the chat in a live stream\")" msgstr "" #. type: Plain text @@ -2467,7 +2569,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, where the \"{{% livechat_label read_only %}}\" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px \"Share link popup\")" msgstr "" #. type: Plain text @@ -2478,7 +2580,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px \"Embeding the chat in OBS\")" msgstr "" #. type: Plain text @@ -2536,7 +2638,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable.\"](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px \"Share link popup - dock tab\")" msgstr "" #. type: Plain text @@ -2546,12 +2648,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock menu](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Dock menu, with a \"Custom Browser Docks\" entry.](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px \"OBS - Dock menu\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Custom Browser Docks dialog, with a new dock called \"My chat\".](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px \"OBS - Dock dialog\")" msgstr "" #. type: Plain text @@ -2561,7 +2663,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of OBS with a new dock including the chat. The user is logged in with their Peertube account, and can chat directly from OBS.](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px \"OBS - Dock\")" msgstr "" #. type: Plain text @@ -2625,7 +2727,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![New live](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the Peertube new live form.](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px \"New live\")" msgstr "" #. type: Plain text @@ -2635,7 +2737,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Activate the chat](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the form, with a \"{{% livechat_label use_chat %}}\" checkbox.](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px \"Activate the chat\")" msgstr "" #. type: Plain text @@ -2707,7 +2809,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label web %}} tab. There is a url you can copy.](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px \"Share link popup - web tab\")" msgstr "" #. type: Plain text @@ -2717,7 +2819,8 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - xmpp tab](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +#: support/documentation/content/en/documentation/user/xmpp_clients.md +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label connect_using_xmpp %}}\" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px \"{{% livechat_label connect_using_xmpp %}}\")" msgstr "" #. type: Title ## @@ -2765,7 +2868,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/streamers/basics.md #: build/documentation/pot_in/documentation/user/streamers/moderation.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px \"Chat menu\")" msgstr "" #. type: Plain text @@ -2775,7 +2878,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Configure chat room](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat configuration form.](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px \"Configure chat room\")" msgstr "" #. type: Plain text @@ -2823,7 +2926,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md -msgid "![Commands configuration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with some fields to configure the bot commands.](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px \"Commands configuration\")" msgstr "" #. type: Plain text @@ -2845,12 +2948,12 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Forbidden words configuration](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with several fields to configure the forbidden words.](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px \"Forbidden words configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Deleted message](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px)" +msgid "![Screenshot of a chat message that was deleted, with the following reason: \"No url allowed\".](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px \"Deleted message\")" msgstr "" #. type: Plain text @@ -2888,6 +2991,12 @@ msgstr "" msgid "These words are case insensitive." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md +#: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +msgid "You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them." +msgstr "" + #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md msgid "This features is still experimental. There might be some issues with non-latin alphabets. You can [open an issue](https://github.com/JohnXLivingston/peertube-plugin-livechat/issues) to report your problems." @@ -2925,7 +3034,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px \"Channel configuration\")" msgstr "" #. type: Plain text @@ -2962,7 +3071,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/quotes.md -msgid "![Timers configuration](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the channel options page, with some fields to configure a new timer.](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px \"Timers configuration\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -2984,7 +3093,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the chatrooms configuration page. The page list the user's channels.](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px \"Chatrooms menu\")" msgstr "" #. type: Plain text @@ -3057,12 +3166,12 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis configuration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the emoji configuration page. There is a form where you can add new emojis.](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis configuration\")" msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session, with messages containing custom emojis. The emoji picker is open, and shows custom emojis.](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis\")" msgstr "" #. type: Title ### @@ -3129,6 +3238,7 @@ msgstr "" #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #: support/documentation/content/en/documentation/user/streamers/tasks.md #: support/documentation/content/en/intro/_index.md @@ -3164,7 +3274,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel option form, with a field to configure the moderation delay.](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Moderation delay\")" msgstr "" #. type: Plain text @@ -3207,7 +3317,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Moderation delay timer](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border)" +msgid "![Screenshot of a chat message. A timer is displayed next to the message datetime. The timer is in seconds.](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border \"Moderation delay timer\")" msgstr "" #. type: Yaml Front Matter Hash Value: description @@ -3285,7 +3395,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the room configuration form. There is a \"{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}\" checkbox.](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px \"Room configuration / Mute anonymous users\")" msgstr "" #. type: Plain text @@ -3295,7 +3405,7 @@ msgstr "" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. The current user has no message field. There is a message: \"{{% livechat_label muted_anonymous_message %}}\"](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px \"Room configuration / Muted anonymous users\")" msgstr "" #. type: Plain text @@ -3329,6 +3439,81 @@ msgstr "" msgid "You can promote users as moderators, if you need some help." msgstr "" +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "It is possible to anonymize moderation actions, to avoid disclosing who is banning/kicking/… occupants." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the \"configure\" menu. In the form, you will find a \"{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}\" checkbox." +msgstr "" + +#. type: Title ## +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#, no-wrap +msgid "Participant message history search" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "As a room admin or owner, you can search all messages sent by a given participant." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To do so, you have several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "![Screenshot of a chat session. The moderator has open the message menu, and there is a \"{{% livechat_label search_occupant_message %}}\" button.](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px \"Message history search\")" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "To have more space and better readability, open the chat in full-page mode." +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "In the search results, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "if the current nickname is different than the nickname when the participant has sent the message, the original nickname will be shown" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant" +msgstr "" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant" +msgstr "" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "The search result will also include all messages related to participants who had the same nickname. You can differenciate them by comparing [JID](https://xmpp.org/extensions/xep-0029.html) and [occupant-id](https://xmpp.org/extensions/xep-0421.html)." +msgstr "" + #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap @@ -3361,6 +3546,230 @@ msgstr "" msgid "From there, you can also promote yourself as room moderator by using the \"{{% livechat_label promote %}}\" button on the right." msgstr "" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Plugin peertube-plugin-livechat moderation notes" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Moderation notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The livechat plugin includes a Moderator Notes Application: you can write some notes, that could be associated to chat participants. Every room's admins have access to these notes, so they can edit them collaboratively." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can for example use this Application to:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "share some notes between moderators" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "take notes about participants that were kicked or caused troubles" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "..." +msgstr "" + +#. type: Title ## +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Using the Moderator Notes Application" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Opening the Moderator Notes Application" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "To open the Moderator Notes Application, there is a \"{{% livechat_label \"moderator_notes\" %}}\" button in the top chat menu:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_video.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Clicking this button will toggle the Application display:" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/polls.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +#, no-wrap +msgid "Access rights" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Every room's admins have access to this Application (read and write access)." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you promote someone as room admin or owner, they gets instant access to this Application. When you remove admin or owner rights to someone, they instantly lose access to this Application." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Scope" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Notes are only available in the room in which you have created them." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Chatrooms can be releated to video or channel. If you want to keep notes from one video to another, please consider using rooms associated to channels." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Currently the video vs channel rooms is an instance-wide settings. Only Peertube admins can change it, and it applies to all chatrooms. In the future, this choice will be added in your channel's options." +msgstr "" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes" +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Create/Edit Notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can use the plus button on the top to create a new note. You can also edit existing notes using the edit button, or delate any note." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can create a note associated to a participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When a note is associated to a participant, you will see their nickname and avatar on the top of the note." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes filtering" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can filter notes to find all notes related to a given participant in several ways:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button that is available on notes to find all notes related to the same participant" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides participants in the sidebar" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides chat messages" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can remove the filter by clicking on the close button." +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of the note application, with a filter enabled for user \"Mike\". The only notes that are shown are the notes for the Mike user.](/peertube-plugin-livechat/images/moderation_notes_filters.png?classes=shadow,border&height=200px \"Moderator Notes Application - filtering\")" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you filters notes on a participant, there are several informations that are shown at the right of the participant nickname:" +msgstr "" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "if the current nickname is different than the nickname when you created the note, the original nickname will be shown" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The search result will also include all notes related to participants who had the same nickname. So you can also take note for anonymous users (who don't have any consistent JID or occupant-id). You can differenciate them by comparing JID and occupant-id." +msgstr "" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Sorting notes" +msgstr "" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can sort notes simply using drag & drop." +msgstr "" + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/polls.md #, no-wrap @@ -3386,7 +3795,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a \"{{% livechat_label new_poll %}}\" form. The form contains several fields: question, duration, choices, …](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px \"Poll form\")" msgstr "" #. type: Plain text @@ -3440,13 +3849,6 @@ msgstr "" msgid "If there was a previous unfinished poll, it will end and its result will be shown." msgstr "" -#. type: Title ### -#: support/documentation/content/en/documentation/user/streamers/polls.md -#: support/documentation/content/en/documentation/user/streamers/tasks.md -#, no-wrap -msgid "Access rights" -msgstr "" - #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md msgid "Every room's admins can create a new poll." @@ -3485,7 +3887,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. In the chat, there is a message with the poll question, and the different choices. There is also a banner on the top of the chat, where you can see the question, and the number of votes for each answers.](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px \"Poll start\")" msgstr "" #. type: Plain text @@ -3505,7 +3907,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll votes](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with an ongoing poll. The current user has just voted by sending \"!1\".](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px \"Poll votes\")" msgstr "" #. type: Plain text @@ -3530,7 +3932,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with poll that has ended. The banner no more accept new votes. There is a message in the chat with the poll results. For each choice, there is the number of votes, and the percentage of the total it represents.](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px \"Poll end\")" msgstr "" #. type: Plain text @@ -3598,7 +4000,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Channel configuration / Slow Mode](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a slow mode field.](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Slow Mode\")" msgstr "" #. type: Plain text @@ -3625,7 +4027,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. There is a banner on the bottom of the chat, indicating that the slow mode is enabled, and that users can send a message every 2 seconds.](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px \"Slow mode infobox\")" msgstr "" #. type: Plain text @@ -3670,11 +4072,6 @@ msgstr "" msgid "highlight questions from your viewers, so you can come back to them later without forgetting to answer them" msgstr "" -#. type: Bullet: '* ' -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "..." -msgstr "" - #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap @@ -3694,12 +4091,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Task Application\")" msgstr "" #. type: Plain text @@ -3709,17 +4106,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "To have more space and better readability, open the chat in full-page mode." +msgid "![Screenshot of a Peertube chat, fullscreen. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px \"Task Application\")" msgstr "" #. type: Plain text @@ -3755,12 +4147,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task lists](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px)" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgid "![Screenshot of a chat session, with the Task application. There are several task lists.](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px \"Task lists\")" msgstr "" #. type: Title ### @@ -3782,12 +4169,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task form](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, there is a form to create a new task.](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px \"Task form\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, a new task was created.](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Title #### @@ -3808,7 +4195,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under task lists, there are several tasks. Some of them are checked, other not.](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px \"Tasks\")" msgstr "" #. type: Title #### @@ -3824,12 +4211,12 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to sort](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another.](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px \"Drag and drop to sort\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to move to another list](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another task list.](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px \"Drag and drop to move to another list\")" msgstr "" #. type: Title #### @@ -3845,17 +4232,17 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Create task from message](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. The menu besides a message is open, with a button to create a new task.](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px \"Create task from message\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Choose the task list](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog, where you can choose in which task list you want to add the new task.](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px \"Choose the task list\")" msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. A new task was added in the \"chat questions\" task list, with the user's nickname, and the message as content.](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px \"Task created\")" msgstr "" #. type: Plain text @@ -3893,7 +4280,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Channel configuration / Terms](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a field to configure your terms and conditions.](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px \"Channel configuration / Terms\")" msgstr "" #. type: Plain text @@ -3914,7 +4301,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. On the top of the chat, there are terms and conditions for both the server and the channel.](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px \"Terms\")" msgstr "" #. type: Plain text @@ -3963,7 +4350,7 @@ msgstr "" #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/_index.md #: support/documentation/content/en/intro/_index.md -msgid "![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px \"Chat screenshot\")" msgstr "" #. type: Plain text @@ -3984,7 +4371,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat. In the participant list, there is John Livingston, and an anonymous account using \"Anonymous 212873\" nickname.](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px \"Chat with an anonymous user\")" msgstr "" #. type: Plain text @@ -3994,7 +4381,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Joining chat when not connected](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat. The current user is not logged in, and must choose a nickname before being able to write in the chat.](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px \"Joining chat when not connected\")" msgstr "" #. type: Title #### @@ -4047,7 +4434,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label login_using_external_account %}}\" dialog. There is a field where you can enter a Peertube url.](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px \"External login dialog\")" msgstr "" #. type: Title ## @@ -4089,7 +4476,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Participants list](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with on the right the list of participants.](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px \"Participants list\")" msgstr "" #. type: Plain text @@ -4122,7 +4509,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/intro/_index.md -msgid "![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px \"Fullscreen chat screenshot\")" msgstr "" #. type: Plain text @@ -4180,7 +4567,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share button](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat, with a share button on the top.](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px \"Share button\")" msgstr "" #. type: Plain text @@ -4190,12 +4577,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "Then, choose \"Connect using XMPP\":" -msgstr "" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +msgid "Then, choose \"{{% livechat_label connect_using_xmpp %}}\":" msgstr "" #. type: Plain text @@ -4203,10 +4585,16 @@ msgstr "" msgid "Then you just have to click on \"open\" or copy/paste the address of the chat room into your XMPP client (using the \"join a room\" feature)." msgstr "" -#. type: Title # +#. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/_index.md #, no-wrap -msgid "PeerTube plugin livechat" +msgid "Peertube plugin livechat documentation" +msgstr "" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/_index.md +#, no-wrap +msgid "Peertube livechat" msgstr "" #. type: Plain text @@ -4283,7 +4671,7 @@ msgstr "" #. type: Plain text #: support/documentation/content/en/intro/_index.md -msgid "![Livechat installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of Peertube plugins admin page. The search fields contains \"livechat\", and the search results show the livechat plugin.](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px \"Livechat installation\")" msgstr "" #. type: Title ## diff --git a/support/documentation/po/livechat.de.po b/support/documentation/po/livechat.de.po index b14f8ae4..97bd10c3 100644 --- a/support/documentation/po/livechat.de.po +++ b/support/documentation/po/livechat.de.po @@ -7,16 +7,18 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2024-07-10 16:54+0200\n" -"PO-Revision-Date: 2024-07-05 19:12+0000\n" -"Last-Translator: Victor Hampel \n" -"Language-Team: German \n" +"POT-Creation-Date: 2024-08-30 16:24+0200\n" +"PO-Revision-Date: 2024-08-31 17:22+0000\n" +"Last-Translator: Victor Hampel " +"\n" +"Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Weblate 5.6.2\n" +"X-Generator: Weblate 5.7\n" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/contact/_index.md @@ -457,8 +459,18 @@ msgstr "Die Dokumentation wird mit [Hugo](https://gohugo.io/) erstellt. Sie mü #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md -msgid "The used theme is [hugo-theme-learn](https://learn.netlify.app/). You should read its documentation before starting editing the documentation." -msgstr "Das verwendete Thema ist [hugo-theme-learn](https://learn.netlify.app/). Sie sollten dessen Dokumentation lesen, bevor Sie mit der Bearbeitung der Dokumentation beginnen." +msgid "The minimum required version for Hugo is 0.121.0. It was tested using version 0.132.2." +msgstr "" +"Die erforderliche Mindestversion für Hugo ist 0.121.0. Es wurde mit Version " +"0.132.2 getestet." + +#. type: Plain text +#: support/documentation/content/en/contributing/document/_index.md +msgid "The used theme is [hugo-theme-relearn](https://mcshelby.github.io/hugo-theme-relearn). You should read its documentation before starting editing the documentation." +msgstr "" +"Das verwendete Thema ist [hugo-theme-learn](https://mcshelby.github.io/hugo-" +"theme-relearn). Sie sollten dessen Dokumentation lesen, bevor Sie mit der " +"Bearbeitung der Dokumentation beginnen." #. type: Plain text #: support/documentation/content/en/contributing/document/_index.md @@ -1386,8 +1398,8 @@ msgstr "Sie müssen diese Zertifikate dann in einem Ordner ablegen, auf den der #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md -msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx`" -msgstr "Wenn Sie das Programm ProsodyCtl verwenden möchten, um Zertifikate zu importieren, ist es (sobald Peertube gestartet ist) mit folgendem Befehl verfügbar (passen Sie den Pfad zu Ihrem Peertube-Datenordner an und ersetzen Sie \"xxx\" durch die Argumente, die Sie an prosodyctl übergeben wollen): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl xxx`" +msgid "If you want to use the ProsodyCtl utility to import certificates, this utility is available (once Peertube is started) using the following command (adapting the path to your Peertube data folder, and replacing \"xxx\" with the arguments you wish to pass to prosodyctl): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx`" +msgstr "Wenn Sie das Programm ProsodyCtl verwenden möchten, um Zertifikate zu importieren, ist es (sobald Peertube gestartet ist) mit folgendem Befehl verfügbar (passen Sie den Pfad zu Ihrem Peertube-Datenordner an und ersetzen Sie \"xxx\" durch die Argumente, die Sie an prosodyctl übergeben wollen): `sudo -u peertube /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosodyAppImage/squashfs-root/AppRun prosodyctl --config /var/www/peertube/storage/plugins/data/peertube-plugin-livechat/prosody/prosody.cfg.lua xxx`" #. type: Plain text #: support/documentation/content/en/documentation/admin/advanced/xmpp_clients.md @@ -1767,14 +1779,24 @@ msgstr "Dies ermöglicht es den Nutzern auch, dem Chat beizutreten, ohne ein Pee #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" -msgstr "![Externes Anmelden Schaltfläche](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a chat on the right. At the bottom of the chat, there is a \"{{% livechat_label login_using_external_account %}}\" button.](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px \"{{% livechat_label login_using_external_account %}} button\")" +msgstr "" +"![Screenshot einer Peertube-Videoseite, mit einem Chat auf der rechten " +"Seite. Am unteren Ende des Chats befindet sich die Schaltfläche \"{{% " +"livechat_label login_using_external_account %}}\".](/peertube-" +"plugin-livechat/images/" +"external_login_button.png?classes=shadow,border&height=200px \"{{% " +"livechat_label login_using_external_account %}} button\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px)" -msgstr "![Externer Anmeldedialog - OpenID Connect](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog with an \"OpenID Connect\" button.](/peertube-plugin-livechat/images/external_login_dialog_oidc.png?classes=shadow,border&height=200px \"External login dialog - OpenID Connect\")" +msgstr "" +"![Screenshot eines Dialogs mit einer Schaltfläche \"OpenID Connect\"" +".](/peertube-plugin-livechat/images/" +"external_login_dialog_oidc.png?classes=shadow,border&height=200px \"Externer " +"Anmeldedialog - OpenID Connect\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/external_auth.md @@ -1921,6 +1943,102 @@ msgstr "Plugin Peertube Livechat Administation" msgid "Admin documentation" msgstr "Admin Dokumentation" +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Advanced firewall rules for the Prosody server" +msgstr "Erweiterte Firewall-Regeln für den Prosody-Server" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Prosody mod_firewall" +msgstr "Prosody mod_firewall" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "This feature comes with the livechat plugin version 11.0.0." +msgstr "Diese Funktion wird mit dem Livechatplugin Version 11.0.0 verfügbar sein." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "You can enable [mod_firewall](https://modules.prosody.im/mod_firewall) on your Prosody server." +msgstr "Sie können [mod_firewall](https://modules.prosody.im/mod_firewall) auf Ihrem Prosody-Server aktivieren." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Doing so, Peertube admins will be able to define advanced firewall rules." +msgstr "Auf diese Weise können Peertube-Administratoren erweiterte Firewall-Regeln definieren." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "These rules could be used to run arbitrary code on the server. If you are a hosting provider, and you don't want to allow Peertube admins to write such rules, you can disable the online editing by creating a `disable_mod_firewall_editing` file in the plugin directory (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). This is opt-out, as Peertube admins can already run arbitrary code just by installing any plugin. You can still use mod_firewall by editing files directly on the server." +msgstr "Diese Regeln könnten verwendet werden, um beliebigen Code auf dem Server auszuführen. Wenn Sie ein Hosting-Anbieter sind und Peertube-Administratoren nicht erlauben wollen, solche Regeln zu schreiben, können Sie die Online-Bearbeitung deaktivieren, indem Sie eine `disable_mod_firewall_editing`-Datei im Plugin-Verzeichnis erstellen (`plugins/data/peertube-plugin-livechat/disable_mod_firewall_editing`). Dies ist ein Opt-Out, da Peertube-Administratoren bereits beliebigen Code ausführen können, indem sie ein beliebiges Plugin installieren. Sie können mod_firewall immer noch verwenden, indem Sie Dateien direkt auf dem Server bearbeiten." + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Edit rules" +msgstr "Regeln bearbeiten" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "First, you must enable the feature in the [plugin settings](/peertube-plugin-livechat/documentation/admin/settings)." +msgstr "Zuerst müssen Sie die Funktion in den [Plugin-Einstellungen](/peertube-plugin-livechat/de/documentation/admin/settings) aktivieren." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Just bellow the settings, you will find a \"Configure mod_firewall\" button. This button will open a configuration page." +msgstr "Direkt unter den Einstellungen finden Sie die Schaltfläche \"Configure mod_firewall\". Mit dieser Schaltfläche wird eine Konfigurationsseite geöffnet." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "![Screenshot of the \"{{% livechat_label prosody_firewall_configuration %}}\" form.](/peertube-plugin-livechat/images/mod_firewall.png?classes=shadow,border&height=400px \"{{% livechat_label prosody_firewall_configuration %}}\")" +msgstr "" +"![Screenshot des Formulars \"{{% livechat_label " +"prosody_firewall_configuration %}}\".](/peertube-plugin-livechat/images/" +"mod_firewall.png?classes=shadow,border&height=400px \"{{% livechat_label " +"prosody_firewall_configuration %}}\")" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Here you can add several configuration files." +msgstr "Hier können Sie mehrere Konfigurationsdateien hinzufügen." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can enable/disable each files." +msgstr "Sie können jede Datei aktivieren/deaktivieren." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Files will be loaded in the alphabetical order. You can use a number as prefix to easily choose the order." +msgstr "Die Dateien werden in alphabetischer Reihenfolge geladen. Sie können eine Zahl als Präfix verwenden, um die Reihenfolge einfach zu wählen." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "You can also edit these firewall rules directly on the server, in the `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` directory. File names must only contains alphanumerical characters, underscores and hyphens. The extension must be `.pfw`, or `.pfw.disabled` if you want to disable a file. Please be sure that the peertube system user has write access to these files, else the web editing interface will fail. Once you have edited these files, you must reload prosody. This can be done by saving the plugin settings, or saving the mod_firewall configuration in the web interface, or by restarting Peertube." +msgstr "Sie können diese Firewall-Regeln auch direkt auf dem Server im Verzeichnis `plugins/data/peertube-plugin-livechat/prosody/mod_firewall_config/` bearbeiten. Die Dateinamen dürfen nur alphanumerische Zeichen, Unterstriche und Bindestriche enthalten. Die Erweiterung muss `.pfw` sein, oder `.pfw.disabled`, wenn Sie eine Datei deaktivieren wollen. Vergewissern Sie sich, dass der Peertube-Systembenutzer Schreibrechte für diese Dateien hat, sonst schlägt die Bearbeitung über die Webschnittstelle fehl. Nachdem Sie diese Dateien bearbeitet haben, müssen Sie prosody neu laden. Dies kann durch Speichern der Plugin-Einstellungen, durch Speichern der mod_firewall-Konfiguration im Web-Interface oder durch einen Neustart von Peertube geschehen." + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "When you save the configuration, the server will automatically reload it, and your rules will apply immediatly. You can check that there is no parsing error in the Prosody error log. To do so, you can read the `plugins/data/peertube-plugin-livechat/prosody/prosody.err` file, or use the [diagnostic tool](/peertube-plugin-livechat/documentation/installation/troubleshooting/) that will show last Prosody errors." +msgstr "Wenn Sie die Konfiguration speichern, wird der Server sie automatisch neu laden, und Ihre Regeln werden sofort angewendet. Sie können im Prosody-Fehlerprotokoll überprüfen, ob ein Parsing-Fehler aufgetreten ist. Dazu können Sie die Datei `plugins/data/peertube-plugin-livechat/prosody/prosody.err` lesen oder das [Diagnose-Tool](/peertube-plugin-livechat/de/documentation/installation/troubleshooting/) verwenden, das die letzten Prosody-Fehler anzeigt." + +#. type: Title ## +#: support/documentation/content/en/documentation/admin/mod_firewall.md +#, no-wrap +msgid "Examples" +msgstr "Beispiele" + +#. type: Plain text +#: support/documentation/content/en/documentation/admin/mod_firewall.md +msgid "Don't hesitate to share your rules. To do so, you can for example edit this [page](/peertube-plugin-livechat/contributing/document/#write-documentation)." +msgstr "Zögern Sie nicht, Ihre Regeln zu teilen. Um dies zu tun, können Sie zum Beispiel diese [Seite](/peertube-plugin-livechat/contributing/document/#write-documentation) bearbeiten." + #. type: Yaml Front Matter Hash Value: description #: build/documentation/pot_in/documentation/admin/settings.md #, no-wrap @@ -2063,8 +2181,10 @@ msgstr "{{% livechat_label avatar_set_option_sepia %}}: [David Revoy's Peertube #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px)" -msgstr "![Sepia](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px)" +msgid "![Sepia avatar example](/peertube-plugin-livechat/images/avatar_sepia.png?classes=shadow,border&height=40px \"Sepia\")" +msgstr "" +"![Sepia Avatar Beispiel](/peertube-plugin-livechat/images/" +"avatar_sepia.png?classes=shadow,border&height=40px \"Sepia\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2073,8 +2193,10 @@ msgstr "{{% livechat_label avatar_set_option_cat %}}: [David Revoy's Katzen Avat #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Cats](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px)" -msgstr "![Katzen](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px)" +msgid "![Cats avatar example](/peertube-plugin-livechat/images/avatar_cat.png?classes=shadow,border&height=40px \"Cats\")" +msgstr "" +"![Katzen AvatarBeispiel](/peertube-plugin-livechat/images/" +"avatar_cat.png?classes=shadow,border&height=40px \"Katzen\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2083,8 +2205,10 @@ msgstr "{{% livechat_label avatar_set_option_bird %}}: [David Revoy's Vögel Ava #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Birds](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px)" -msgstr "![Vögel](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px)" +msgid "![Birds avatar example](/peertube-plugin-livechat/images/avatar_bird.png?classes=shadow,border&height=40px \"Birds\")" +msgstr "" +"![Vogel Avatar Beispiel](/peertube-plugin-livechat/images/" +"avatar_bird.png?classes=shadow,border&height=40px \"Vögel\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2093,8 +2217,10 @@ msgstr "{{% livechat_label avatar_set_option_fenec %}}: [David Revoy's Fenec/Mob #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px)" -msgstr "![Fenecs](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px)" +msgid "![Fenecs avatar example](/peertube-plugin-livechat/images/avatar_fenec.png?classes=shadow,border&height=40px \"Fenecs\")" +msgstr "" +"![Fenecs Avatar Beispiel](/peertube-plugin-livechat/images/" +"avatar_fenec.png?classes=shadow,border&height=40px \"Fenecs\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2103,8 +2229,10 @@ msgstr "{{% livechat_label avatar_set_option_abstract %}}: [David Revoy's Abstra #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Abstracts](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px)" -msgstr "![Abstrakt](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px)" +msgid "![Abstracts avatar example](/peertube-plugin-livechat/images/avatar_abstract.png?classes=shadow,border&height=40px \"Abtracts\")" +msgstr "" +"![Abstrakt Avatar Beispiel](/peertube-plugin-livechat/images/" +"avatar_abstract.png?classes=shadow,border&height=40px \"Abstrakt\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2113,8 +2241,10 @@ msgstr "{{% livechat_label avatar_set_option_legacy %}}: Basierend auf [David Re #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md -msgid "![Legacy](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px)" -msgstr "![Alte Avatare](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px)" +msgid "![Legacy avatar example](/peertube-plugin-livechat/images/avatar_legacy.jpg?classes=shadow,border&height=40px \"Legacy\")" +msgstr "" +"![Alte Avatare Beispiel](/peertube-plugin-livechat/images/" +"avatar_legacy.jpg?classes=shadow,border&height=40px \"Alte Avatare\")" #. type: Plain text #: build/documentation/pot_in/documentation/admin/settings.md @@ -2138,8 +2268,8 @@ msgstr "Default ConverseJS theme: Dies ist das standard ConverseJS Thema." #. type: Bullet: '- ' #: build/documentation/pot_in/documentation/admin/settings.md -msgid "ConverseJS concord theme: this is a theme provided by ConverseJS." -msgstr "ConverseJS concord theme: Dies ist ein von ConverseJS bereitgestelltes Thema." +msgid "ConverseJS cyberpunk theme: this is a theme provided by ConverseJS." +msgstr "ConverseJS cyberpunk theme: Dies ist ein von ConverseJS bereitgestelltes Thema." #. type: Title ## #: build/documentation/pot_in/documentation/admin/settings.md @@ -2193,6 +2323,11 @@ msgstr "Diese Funktion könnte für die Verbindung von Brücken oder Bots genutz msgid "More informations on Prosody external components [here](https://prosody.im/doc/components)." msgstr "Weitere Informationen zu den externen Komponenten von Prosody finden Sie [hier](https://prosody.im/doc/components)." +#. type: Plain text +#: build/documentation/pot_in/documentation/admin/settings.md +msgid "For more information, please check [the documentation](/peertube-plugin-livechat/documentation/admin/mod_firewall/)." +msgstr "Weitere Informationen finden Sie in [der Dokumentation](/peertube-plugin-livechat/de/documentation/admin/mod_firewall/)." + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/_index.md #, no-wrap @@ -2386,8 +2521,12 @@ msgstr "Öffnen Sie die Plugin-Einstellungen, und klicken Sie auf die Schaltflä #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Launch diagnostic](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px)" -msgstr "![Diagnose starten](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the plugin's settings page, with a \"launch diagnostic\" button.](/peertube-plugin-livechat/images/launch_diagnostic.png?classes=shadow,border&height=200px \"Launch diagnostic\")" +msgstr "" +"![Screenshot der Einstellungsseite des Plugins, mit einer Schaltfläche " +"\"Diagnose starten\".](/peertube-plugin-livechat/images/" +"launch_diagnostic.png?classes=shadow,border&height=200px \"Diagnose starten\"" +")" #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md @@ -2396,8 +2535,12 @@ msgstr "Wenn auf der Diagnoseseite ein Fehler auftritt, können Sie auf dieser S #. type: Plain text #: support/documentation/content/en/documentation/installation/troubleshooting.md -msgid "![Diagnostic result](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px)" -msgstr "![Diagnoseergebnis](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the diagnostic result page. This gives a lot of information, with status for different test suites.](/peertube-plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px \"Diagnostic result\")" +msgstr "" +"![Screenshot der Seite mit den Diagnoseergebnissen. Dies gibt eine Menge " +"Informationen, mit Status für verschiedene Testsuiten.](/peertube-" +"plugin-livechat/images/diagnostic.png?classes=shadow,border&height=200px " +"\"Diagnoseergebnisse\")" #. type: Title ## #: support/documentation/content/en/documentation/installation/troubleshooting.md @@ -2524,8 +2667,12 @@ msgstr "Sie können den Chat ganz einfach in Ihren Videostream integrieren." #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in a live stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px)" -msgstr "![Einbetten des Chats in einen Live-Stream](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube live, replay, with the chat included at the bottom of the video stream.](/peertube-plugin-livechat/images/embed_chat_in_livestream.png?classes=shadow,border&height=200px \"Embeding the chat in a live stream\")" +msgstr "" +"![Screenshot einer Peertube Live-Übertragung, bei der der Chat am unteren " +"Ende des Video-Streams eingebettet ist.](/peertube-plugin-livechat/images/" +"embed_chat_in_livestream.png?classes=shadow,border&height=200px \"Einbettung " +"des Chats in einen Live-Stream\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2540,8 +2687,12 @@ msgstr "Aktivieren Sie das Kontrollkästchen \"{{% livechat_label read_only %}}\ #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" -msgstr "![Link Teilen Popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, where the \"{{% livechat_label read_only %}}\" option is checked.](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px \"Share link popup\")" +msgstr "" +"![Screenshot des \"{{% livechat_label share_chat_link %}}\"-Dialogs, in dem " +"die Option \"{{% livechat_label read_only %}}\" aktiviert ist](/peertube-" +"plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px " +"\"Link teilen popup\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2551,8 +2702,12 @@ msgstr "Verwenden Sie dann diesen Link als \"Webbrowser-Quelle\" in OBS." #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/intro/_index.md -msgid "![Embeding the chat in OBS](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px)" -msgstr "![Den Chat in OBS einbetten](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS software, where the chat was added as web browser source.](/peertube-plugin-livechat/images/embed_chat_in_obs.png?classes=shadow,border&height=200px \"Embeding the chat in OBS\")" +msgstr "" +"![Screenshot der OBS-Software, wo der Chat als Webbrowser-Quelle hinzugefügt " +"wurde.](/peertube-plugin-livechat/images/" +"embed_chat_in_obs.png?classes=shadow,border&height=200px \"Den Chat in OBS " +"einbetten\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2609,8 +2764,13 @@ msgstr "Verwenden Sie dazu einfach die \"{{% livechat_label share_chat_link %}}\ #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" -msgstr "![Link teilen Fenster - Dock Reiter](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label share_chat_dock %}} tab. A token was generated, and is selectionable.\"](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px \"Share link popup - dock tab\")" +msgstr "" +"![Screenshot des \"{{% livechat_label share_chat_link %}}\"-Dialogs auf der " +"Registerkarte \"{{% livechat_label share_chat_dock %}}\". Ein Token wurde " +"generiert und ist auswählbar.\"](/peertube-plugin-livechat/images/" +"share_dock.png?classes=shadow,border&height=200px \"Link teilen popup - Dock " +"Registerkarte\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2619,13 +2779,19 @@ msgstr "Kopieren Sie dann die URL und verwenden Sie das Menü \"Docks / Benutzer #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock menu](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px)" -msgstr "![OBS - Dock Menü](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Dock menu, with a \"Custom Browser Docks\" entry.](/peertube-plugin-livechat/images/obs_dock_menu.png?classes=shadow,border&height=200px \"OBS - Dock menu\")" +msgstr "" +"![Screenshot des OBS Dock Menüs mit dem Eintrag \"Benutzerdefinierte Browser-" +"Docks\"](/peertube-plugin-livechat/images/" +"obs_dock_menu.png?classes=shadow,border&height=200px \"OBS - Dock menu\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px)" -msgstr "![OBS - Dock-Dialog](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the OBS Custom Browser Docks dialog, with a new dock called \"My chat\".](/peertube-plugin-livechat/images/obs_dock_dialog.png?classes=shadow,border&height=200px \"OBS - Dock dialog\")" +msgstr "" +"![Screenshot des OBS Custom Browser Docks Dialogs, mit einem neuen Dock " +"namens \"Mein Chat\"](/peertube-plugin-livechat/images/" +"obs_dock_dialog.png?classes=shadow,border&height=200px \"OBS - Dock Dialog\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2634,8 +2800,12 @@ msgstr "Danach haben Sie ein neues Dock, das mit dem Chat und Ihrem Konto verbun #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md -msgid "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" -msgstr "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of OBS with a new dock including the chat. The user is logged in with their Peertube account, and can chat directly from OBS.](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px \"OBS - Dock\")" +msgstr "" +"![Screenshot von OBS mit einem neuen Dock inklusive Chat. Der Nutzer ist mit " +"seinem Peertube-Account eingeloggt und kann direkt von OBS aus chatten" +".](/peertube-plugin-livechat/images/" +"obs_dock.png?classes=shadow,border&height=200px \"OBS - Dock\")" #. type: Plain text #: support/documentation/content/en/documentation/user/obs.md @@ -2698,8 +2868,11 @@ msgstr "Wenn Sie eine Peertube Live-Stream erstellen oder ändern, gibt es eine #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![New live](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px)" -msgstr "![Neuer Live-Stream](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the Peertube new live form.](/peertube-plugin-livechat/images/new_live.png?classes=shadow,border&height=200px \"New live\")" +msgstr "" +"![Screenshot des Peertube-Formulars für die Live-Übertragung (/peertube-" +"plugin-livechat/images/new_live.png?classes=shadow,border&height=200px " +"\"Neuer Livestream\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2708,8 +2881,12 @@ msgstr "Auf der Registerkarte \"Plugin-Einstellungen\" gibt es ein Kontrollkäst #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Activate the chat](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px)" -msgstr "![Den Chat aktivieren](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the form, with a \"{{% livechat_label use_chat %}}\" checkbox.](/peertube-plugin-livechat/images/new_live_activate_chat.png?classes=shadow,border&height=200px \"Activate the chat\")" +msgstr "" +"![Screenshot des Formulars mit dem Kontrollkästchen \"{{% livechat_label " +"use_chat %}}\"](/peertube-plugin-livechat/images/" +"new_live_activate_chat.png?classes=shadow,border&height=200px \"Chat " +"aktivieren\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2780,8 +2957,13 @@ msgstr "Auf der Registerkarte \"{{% livechat_label web %}}\" öffnet die angegeb #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" -msgstr "![Link teilen Fenster - Web Reiter](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label web %}} tab. There is a url you can copy.](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px \"Share link popup - web tab\")" +msgstr "" +"![Screenshot des \"{{% livechat_label share_chat_link %}}\"-Dialogs auf der " +"Registerkarte \"{{% livechat_label web %}}\". Es gibt eine Url, die Sie " +"kopieren können](/peertube-plugin-livechat/images/" +"share_web.png?classes=shadow,border&height=200px Link teilen popup - Web " +"Registerkarte\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2790,8 +2972,14 @@ msgstr "Das \"{{% livechat_label share_chat_link %}}\" Popup-Fenster kann auch e #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Share link popup - xmpp tab](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" -msgstr "![Link teilen Fenster - xmpp Reiter](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +#: support/documentation/content/en/documentation/user/xmpp_clients.md +msgid "![Screenshot of the \"{{% livechat_label share_chat_link %}}\" dialog, on the \"{{% livechat_label connect_using_xmpp %}}\" tab.](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px \"{{% livechat_label connect_using_xmpp %}}\")" +msgstr "" +"![Screenshot des \"{{% livechat_label share_chat_link %}}\"-Dialogs, auf der " +"Registerkarte \"{{% livechat_label connect_using_xmpp %}}\".](/peertube-" +"plugin-livechat/images/" +"share_xmpp_dialog.png?classes=shadow,border&height=200px \"{{% " +"livechat_label connect_using_xmpp %}}\")" #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2838,8 +3026,11 @@ msgstr "Sie können das Dauerhaftigkeitsverhalten ändern. [Öffnen Sie das Cha #: support/documentation/content/en/documentation/user/streamers/basics.md #: build/documentation/pot_in/documentation/user/streamers/moderation.md #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat menu](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px)" -msgstr "![Chat Menü](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the dropdown menu at the top of the chat. Several entries are available.](/peertube-plugin-livechat/images/top_menu.png?classes=shadow,border&height=200px \"Chat menu\")" +msgstr "" +"![Screenshot des Dropdown-Menüs oben im Chat. Es sind mehrere Einträge " +"verfügbar. ](/peertube-plugin-livechat/images/" +"top_menu.png?classes=shadow,border&height=200px \"Chat Menü\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2848,8 +3039,11 @@ msgstr "Es gibt mehrere Optionen, die geändert werden können." #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md -msgid "![Configure chat room](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px)" -msgstr "![Chatraum konfigurieren](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat configuration form.](/peertube-plugin-livechat/images/configure.png?classes=shadow,border&height=200px \"Configure chat room\")" +msgstr "" +"![Screenshot des Chat-Konfigurationsformulars.](/peertube-plugin-livechat/" +"images/configure.png?classes=shadow,border&height=200px \"Chatraum " +"konfigurieren\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/basics.md @@ -2896,8 +3090,11 @@ msgstr "Befehle" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md -msgid "![Commands configuration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px)" -msgstr "![Befehlskonfiguration](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with some fields to configure the bot commands.](/peertube-plugin-livechat/images/bot_commands.png?classes=shadow,border&height=400px \"Commands configuration\")" +msgstr "" +"![Screenshot der Seite mit den Kanaloptionen, mit einigen Feldern zur " +"Konfiguration der Chatbot-Befehle.](/peertube-plugin-livechat/images/" +"bot_commands.png?classes=shadow,border&height=400px \"Befehlskonfiguration\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/commands.md @@ -2918,13 +3115,21 @@ msgstr "Verbotene Wörter" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Forbidden words configuration](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px)" -msgstr "![Konfiguration der verbotenen Wörter](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options page, with several fields to configure the forbidden words.](/peertube-plugin-livechat/images/bot_forbidden_words.png?classes=shadow,border&height=400px \"Forbidden words configuration\")" +msgstr "" +"![Screenshot der Seite mit den Kanaloptionen, mit mehreren Feldern zur " +"Konfiguration der verbotenen Wörter.](/peertube-plugin-livechat/images/" +"bot_forbidden_words.png?classes=shadow,border&height=400px \"Konfiguration " +"der verbotenen Wörter\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md -msgid "![Deleted message](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px)" -msgstr "![Gelöschte Nachricht](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px)" +msgid "![Screenshot of a chat message that was deleted, with the following reason: \"No url allowed\".](/peertube-plugin-livechat/images/bot_deleted_message.png?classes=shadow,border&height=100px \"Deleted message\")" +msgstr "" +"![Screenshot einer Chat-Nachricht, die mit folgender Begründung gelöscht " +"wurde: \"Keine URL erlaubt\".](/peertube-plugin-livechat/images/" +"bot_deleted_message.png?classes=shadow,border&height=100px \"Gelöschte " +"Nachricht\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md @@ -2961,6 +3166,12 @@ msgstr "Wenn Sie einige nützliche Wörterlisten haben, können Sie die gerne zu msgid "These words are case insensitive." msgstr "Bei diesen Wörtern wird die Groß- und Kleinschreibung nicht berücksichtigt." +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md +#: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +msgid "You can combine a short [moderation delay](/peertube-plugin-livechat/documentation/user/streamers/moderation_delay) (1 second for example) with the [moderation bot](/peertube-plugin-livechat/documentation/user/streamers/bot) to delete messages containing swear words before any non-moderator user will see them." +msgstr "Sie können eine kurze [Moderationsverzögerung](/peertube-plugin-livechat/de/documentation/user/streamers/moderation_delay) (z.B. 1 Sekunde) mit dem [Moderations-Chatbot](/peertube-plugin-livechat/de/documentation/user/streamers/bot) kombinieren, um Nachrichten mit Schimpfwörtern zu löschen, bevor ein Nicht-Moderator sie sieht." + #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/forbidden_words.md msgid "This features is still experimental. There might be some issues with non-latin alphabets. You can [open an issue](https://github.com/JohnXLivingston/peertube-plugin-livechat/issues) to report your problems." @@ -2998,8 +3209,12 @@ msgstr "Sie können einen Chatbot für Ihre Chaträume aktivieren. Die Chatbotk #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Channel configuration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px)" -msgstr "![Kanalkonfiguration](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options. There is a form with multiple fields.](/peertube-plugin-livechat/images/channel_configuration.png?classes=shadow,border&height=400px \"Channel configuration\")" +msgstr "" +"![Screenshot der Kanaloptionen; es gibt ein Formular mit mehreren Feldern" +".](/peertube-plugin-livechat/images/" +"channel_configuration.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/bot/_index.md @@ -3035,8 +3250,11 @@ msgstr "Wenn sich kein Benutzer im Chatraum befindet, sendet der Chatbot keine N #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/bot/quotes.md -msgid "![Timers configuration](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px)" -msgstr "[Konfiguration der Timer](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the channel options page, with some fields to configure a new timer.](/peertube-plugin-livechat/images/bot_quotes.png?classes=shadow,border&height=200px \"Timers configuration\")" +msgstr "" +"![Screenshot der Seite mit den Kanaloptionen, mit einigen Feldern zur " +"Konfiguration eines neuen Timers.](/peertube-plugin-livechat/images/" +"bot_quotes.png?classes=shadow,border&height=200px \"Timer konfiguration\")" #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/channel.md @@ -3057,8 +3275,11 @@ msgstr "Im linken Menü von Peertube gibt es einen Eintrag \"{{% livechat_label #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md -msgid "![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" -msgstr "![Chaträume Menü](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the chatrooms configuration page. The page list the user's channels.](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px \"Chatrooms menu\")" +msgstr "" +"![Screenshot der Konfigurationsseite für Chaträume. Die Seite listet die " +"Kanäle des Benutzers auf.](/peertube-plugin-livechat/images/" +"chatrooms_menu.png?classes=shadow,border&height=400px \"Chaträume Menü\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/channel.md @@ -3130,13 +3351,22 @@ msgstr "Öffnen Sie auf der [Kanal Konfigurationsseite](/peertube-plugin-livecha #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis configuration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px)" -msgstr "![Kanal-Konfiguration / Kanal-Emojis-Konfiguration](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the emoji configuration page. There is a form where you can add new emojis.](/peertube-plugin-livechat/images/channel_custom_emojis_configuration.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis configuration\")" +msgstr "" +"![Screenshot der Emoji-Konfigurationsseite. Es gibt ein Formular, in dem Sie " +"neue Emojis hinzufügen können.](/peertube-plugin-livechat/images/" +"channel_custom_emojis_configuration.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration / Kanal Emojis Konfiguration\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/emojis.md -msgid "![Channel configuration / Channel emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px)" -msgstr "![Kanal-Konfiguration / Kanal-Emojis](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session, with messages containing custom emojis. The emoji picker is open, and shows custom emojis.](/peertube-plugin-livechat/images/channel_custom_emojis.png?classes=shadow,border&height=400px \"Channel configuration / Channel emojis\")" +msgstr "" +"![Screenshot einer Chatsitzung mit Nachrichten, die benutzerdefinierte " +"Emojis enthalten. Die Emoji-Auswahl ist geöffnet und zeigt " +"benutzerdefinierte Emojis.](/peertube-plugin-livechat/images/" +"channel_custom_emojis.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration / Kanal Emojis\")" #. type: Title ### #: build/documentation/pot_in/documentation/user/streamers/emojis.md @@ -3208,6 +3438,7 @@ msgstr "Diese Funktion wird mit dem Livechatplugin Version 10.3.0 verfügbar sei #. type: Yaml Front Matter Hash Value: title #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md #: support/documentation/content/en/documentation/user/streamers/slow_mode.md #: support/documentation/content/en/documentation/user/streamers/tasks.md #: support/documentation/content/en/intro/_index.md @@ -3243,8 +3474,12 @@ msgstr "Auf der [Kanal Konfigurationsseite](/peertube-plugin-livechat/de/documen #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -msgid "![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" -msgstr "![Kanalkonfiguration / Moderationsverzögerung](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel option form, with a field to configure the moderation delay.](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Moderation delay\")" +msgstr "" +"![Screenshot des Formulars für die Kanaloptionen, mit einem Feld zur " +"Konfiguration der Moderationsverzögerung.](/peertube-plugin-livechat/images/" +"moderation_delay_channel_option.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration / Moderationsverzögerung\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md @@ -3271,26 +3506,27 @@ msgstr "Um den Wert für einen bereits bestehenden Raum zu ändern, öffnen Sie #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md msgid "Currently, this feature has one known bug: users that join the chat will get all messages, even messages that are still pending for other participants. However, messages sent after they joined will be delayed correctly." -msgstr "" +msgstr "Derzeit gibt es bei dieser Funktion einen bekannten Fehler: Benutzer, die dem Chat beitreten, erhalten alle Nachrichten, auch solche, die noch für andere Teilnehmer ausstehen. Allerdings werden Nachrichten, die nach dem Beitritt gesendet werden, korrekt verzögert." #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -#, fuzzy, no-wrap -#| msgid "Share the chat" +#, no-wrap msgid "In the chat" -msgstr "Teilen Sie den Chat" +msgstr "Im Chat" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md msgid "As a moderator, you will see the remaining time (in seconds) before the message is broadcasted, just besides the message datetime." -msgstr "" +msgstr "Als Moderator sehen Sie neben dem Datum der Nachricht auch die verbleibende Zeit (in Sekunden), bevor die Nachricht veröffentlicht wird." #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/moderation_delay.md -#, fuzzy -#| msgid "![Channel configuration / Moderation delay](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" -msgid "![Moderation delay timer](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border)" -msgstr "![Kanalkonfiguration / Moderationsverzögerung](/peertube-plugin-livechat/images/moderation_delay_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat message. A timer is displayed next to the message datetime. The timer is in seconds.](/peertube-plugin-livechat/images/moderation_delay_timer.png?classes=shadow,border \"Moderation delay timer\")" +msgstr "" +"![Screenshot einer Chat-Nachricht. Neben dem Datum der Nachricht wird ein " +"Timer angezeigt. Der Timer ist in Sekunden.](/peertube-plugin-livechat/" +"images/moderation_delay_timer.png?classes=shadow,border " +"\"Moderationsverzögerungstimer\")" #. type: Yaml Front Matter Hash Value: description #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3337,8 +3573,6 @@ msgstr "Über das [Chat Dropdown Menü](/peertube-plugin-livechat/de/documentati #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -#, fuzzy -#| msgid "The video owner will be owner of the chat room. This means he can configure the room, delete it, promote other users as admins, ..." msgid "The video owner will be owner of the chat room. This means they can configure the room, delete it, promote other users as admins, ..." msgstr "Der Videobesitzer ist der Besitzer des Chatraums. Das bedeutet, er kann den Raum konfigurieren, löschen, andere Benutzer als Administratoren befördern, ..." @@ -3371,8 +3605,13 @@ msgstr "Um diese Funktion zu aktivieren oder zu deaktivieren, verwenden Sie das #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Mute anonymous users](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" -msgstr "![Raumkonfiguration / Anonyme Benutzer stummschalten](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the room configuration form. There is a \"{{% livechat_label livechat_configuration_channel_mute_anonymous_label %}}\" checkbox.](/peertube-plugin-livechat/images/configure_mute_anonymous.png?classes=shadow,border&height=400px \"Room configuration / Mute anonymous users\")" +msgstr "" +"![Screenshot des Raumkonfigurationsformulars. Es gibt ein \"{{% " +"livechat_label livechat_configuration_channel_mute_anonymous_label %}}\"" +"-Kontrollkästchen.](/peertube-plugin-livechat/images/" +"configure_mute_anonymous.png?classes=shadow,border&height=400px " +"\"Raumkonfiguration / Anonyme Benutzer stummschalten\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3381,8 +3620,13 @@ msgstr "Anonyme Benutzer haben das Nachrichtenfeld nicht und sehen folgende Auff #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md -msgid "![Room configuration / Muted anonymous users](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" -msgstr "![Raumkonfiguration / Stummgeschaltete anonyme Benutzer](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. The current user has no message field. There is a message: \"{{% livechat_label muted_anonymous_message %}}\"](/peertube-plugin-livechat/images/anonymous_muted.png?classes=shadow,border&height=400px \"Room configuration / Muted anonymous users\")" +msgstr "" +"![Screenshot einer Chatsitzung. Der aktuelle Benutzer hat kein " +"Nachrichtenfeld. Es gibt eine Nachricht: \"{{% livechat_label " +"muted_anonymous_message %}}\"](/peertube-plugin-livechat/images/" +"anonymous_muted.png?classes=shadow,border&height=400px \"Raumkonfiguration / " +"Stummgeschaltete anonyme Benutzer\")" #. type: Plain text #: build/documentation/pot_in/documentation/user/streamers/moderation.md @@ -3415,6 +3659,86 @@ msgstr "Es gibt verschiedene Rollen, die Benutzern in Chaträumen zugewiesen wer msgid "You can promote users as moderators, if you need some help." msgstr "Sie können Benutzer zu Moderatoren befördern, wenn Sie Hilfe benötigen." +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "It is possible to anonymize moderation actions, to avoid disclosing who is banning/kicking/… occupants." +msgstr "Es ist möglich, Moderationsaktionen zu anonymisieren, um zu vermeiden, dass bekannt wird, wer Teilnehmer bannt/verweist/..." + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To enable or disable this feature, use the [chat dropdown menu](/peertube-plugin-livechat/documentation/user/viewers), open the \"configure\" menu. In the form, you will find a \"{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}\" checkbox." +msgstr "Um diese Funktion zu aktivieren oder zu deaktivieren, verwenden Sie das [Chat-Dropdown-Menü](/peertube-plugin-livechat/de/documentation/user/viewers), öffnen Sie das Menü \"Konfigurieren\". In dem Formular finden Sie eine Checkbox \"{{% livechat_label livechat_configuration_channel_anonymize_moderation_label %}}\"." + +#. type: Title ## +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#, no-wrap +msgid "Participant message history search" +msgstr "Im Nachrichtenverlauf eines Teilnehmers suchen" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "As a room admin or owner, you can search all messages sent by a given participant." +msgstr "Als Raumadministrator oder -besitzer können Sie alle von einem bestimmten Teilnehmer gesendeten Nachrichten durchsuchen." + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "To do so, you have several ways:" +msgstr "Dazu haben Sie mehrere Möglichkeiten:" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "die Aktion \"{{% livechat_label search_occupant_message %}}\" im Dropdown-Menü neben den Teilnehmern in der Seitenleiste verwenden" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "using the \"{{% livechat_label search_occupant_message %}}\" action in the dropdown menu besides chat messages" +msgstr "die Aktion \"{{% livechat_label search_occupant_message %}}\" im Dropdown-Menü neben den Chat-Nachrichten verwenden" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "![Screenshot of a chat session. The moderator has open the message menu, and there is a \"{{% livechat_label search_occupant_message %}}\" button.](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px \"Message history search\")" +msgstr "" +"![Screenshot einer Chatsitzung. Der Moderator hat das Nachrichtenmenü " +"geöffnet, und es gibt eine Schaltfläche \"{{% livechat_label " +"search_occupant_message %}}\".](/peertube-plugin-livechat/images/" +"message_search.png?classes=shadow,border&height=200px " +"\"Nachrichtenverlaufssuche\")" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "To have more space and better readability, open the chat in full-page mode." +msgstr "Um mehr Platz und eine bessere Lesbarkeit zu erhalten, öffnen Sie den Chat im neuen Fenster." + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "In the search results, there are several informations that are shown at the right of the participant nickname:" +msgstr "In den Suchergebnissen werden rechts neben dem Spitznamen des Teilnehmers verschiedene Informationen angezeigt:" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "if the current nickname is different than the nickname when the participant has sent the message, the original nickname will be shown" +msgstr "wenn der aktuelle Nickname nicht mit dem Nicknamen übereinstimmt, unter dem der Teilnehmer die Nachricht gesendet hat, wird der ursprüngliche Nickname angezeigt" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will see the [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) of the participant" +msgstr "sehen Sie die [JID (Jabber ID)](https://xmpp.org/extensions/xep-0029.html) des Teilnehmers" + +#. type: Bullet: '* ' +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "you will also see the [occupant-id](https://xmpp.org/extensions/xep-0421.html) of the participant" +msgstr "Sie sehen auch die [occupant-id](https://xmpp.org/extensions/xep-0421.html) des Teilnehmers" + +#. type: Plain text +#: build/documentation/pot_in/documentation/user/streamers/moderation.md +msgid "The search result will also include all messages related to participants who had the same nickname. You can differenciate them by comparing [JID](https://xmpp.org/extensions/xep-0029.html) and [occupant-id](https://xmpp.org/extensions/xep-0421.html)." +msgstr "Das Suchergebnis enthält auch alle Nachrichten, die sich auf Teilnehmer beziehen, die denselben Spitznamen hatten. Sie können sie unterscheiden, indem Sie [JID](https://xmpp.org/extensions/xep-0029.html) und [occupant-id](https://xmpp.org/extensions/xep-0421.html) vergleichen." + #. type: Title ## #: build/documentation/pot_in/documentation/user/streamers/moderation.md #, no-wrap @@ -3447,6 +3771,255 @@ msgstr "Sie können alle bestehenden Chaträume auflisten: in den Einstellungen msgid "From there, you can also promote yourself as room moderator by using the \"{{% livechat_label promote %}}\" button on the right." msgstr "Von dort aus kannst du dich auch als Moderator des Raums bewerben, indem du die Schaltfläche \"{{% livechat_label promote %}}\" auf der rechten Seite benutzt." +#. type: Yaml Front Matter Hash Value: description +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Plugin peertube-plugin-livechat moderation notes" +msgstr "Plugin peertube-plugin-livechat Moderationnotizen" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Moderation notes" +msgstr "Moderationsnotizen" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The livechat plugin includes a Moderator Notes Application: you can write some notes, that could be associated to chat participants. Every room's admins have access to these notes, so they can edit them collaboratively." +msgstr "Das Livechat Plugin enthält eine Anwendung für Moderator-Notizen: Sie können einige Notizen schreiben, die den Chat-Teilnehmern zugeordnet werden können. Die Administratoren jedes Raums haben Zugriff auf diese Notizen, so dass sie diese gemeinsam bearbeiten können." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can for example use this Application to:" +msgstr "Sie können diese Anwendung zum Beispiel verwenden, um:" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "share some notes between moderators" +msgstr "einige Notizen zwischen Moderatoren austauschen" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "take notes about participants that were kicked or caused troubles" +msgstr "Notizen über Teilnehmer machen, die aus dem Chat geworfen wurden oder Probleme verursachten" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "..." +msgstr "..." + +#. type: Title ## +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Using the Moderator Notes Application" +msgstr "Verwendung der Anwendung Moderationsnotizen" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Opening the Moderator Notes Application" +msgstr "Öffnen der Anwendung Moderationsnotizen" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "To open the Moderator Notes Application, there is a \"{{% livechat_label \"moderator_notes\" %}}\" button in the top chat menu:" +msgstr "Um die Anwendung Moderationsnotizen zu öffnen, gibt es eine Schaltfläche \"{{% livechat_label \"moderator_notes\" %}}\" im oberen Chatmenü:" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_video.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" +"![Screenshot eines Peertube-Videos, mit dem Chat auf der rechten Seite. Das " +"obere Menü des Chats ist geöffnet, mit einer \"{{% livechat_label " +"\"moderator_notes\" %}}\"-Schaltfläche.](/peertube-plugin-livechat/images/" +"moderation_notes_open_app_video.png?classes=shadow,border&height=200px " +"\"Öffnen des Moderator Notizen Programms\")" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label \"moderator_notes\" %}}\" button.](/peertube-plugin-livechat/images/moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Moderator Notes Application\")" +msgstr "" +"![Screenshot eines Peertube-Chats, Vollbild. Das obere Menü des Chats ist " +"geöffnet, mit einer \"{{% livechat_label \"moderator_notes\" %}}\"" +"-Schaltfläche.](/peertube-plugin-livechat/images/" +"moderation_notes_open_app_fullpage.png?classes=shadow,border&height=200px " +"\"Öffnen des Moderator Notizen Programms\")" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Clicking this button will toggle the Application display:" +msgstr "Wenn Sie auf diese Schaltfläche klicken, wird die Anzeige der Anwendung umgeschaltet:" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube video, with the chat on the right. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" +"![Screenshot eines Peertube-Videos, mit dem Chat auf der rechten Seite. Die " +"Anwendung für Moderationsnotizen ist geöffnet. Es gibt mehrere Notizen, " +"einige davon sind mit Benutzern verknüpft.](/peertube-plugin-livechat/images/" +"moderator_notes_app_video_1.png?classes=shadow,border&height=200px " +"\"Moderator Notizen Programm\")" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of a Peertube chat, fullscreen. The moderation notes application is open. There are several notes, some of them are associated to users.](/peertube-plugin-livechat/images/moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px \"Moderator Notes Application\")" +msgstr "" +"![Screenshot eines Peertube-Chats, Vollbild. Die Anwendung für " +"Moderationsnotizen ist geöffnet. Es gibt mehrere Notizen, einige davon sind " +"mit Benutzern verknüpft.](/peertube-plugin-livechat/images/" +"moderator_notes_app_fullpage_1.png?classes=shadow,border&height=200px " +"\"Moderator Notizen Programm\")" + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/polls.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +#, no-wrap +msgid "Access rights" +msgstr "Zugriffsrechte" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Every room's admins have access to this Application (read and write access)." +msgstr "Die Administratoren jedes Raums haben Zugriff auf diese Anwendung (Lese- und Schreibzugriff)." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you promote someone as room admin or owner, they gets instant access to this Application. When you remove admin or owner rights to someone, they instantly lose access to this Application." +msgstr "Wenn Sie jemanden zum Raumadministrator oder -besitzer befördern, erhält er sofortigen Zugang zu dieser Anwendung. Wenn Sie jemandem die Admin- oder Eigentümerrechte entziehen, verliert er sofort den Zugang zu dieser Anwendung." + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Scope" +msgstr "Umfang" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Notes are only available in the room in which you have created them." +msgstr "Notizen sind nur in dem Raum verfügbar, in dem Sie sie erstellt haben." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Chatrooms can be releated to video or channel. If you want to keep notes from one video to another, please consider using rooms associated to channels." +msgstr "Chaträume können einem Video oder einem Kanal zugeordnet werden. Wenn Sie Notizen von einem Video zum anderen aufbewahren möchten, sollten Sie Räume verwenden, die mit Kanälen verbunden sind." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "Currently the video vs channel rooms is an instance-wide settings. Only Peertube admins can change it, and it applies to all chatrooms. In the future, this choice will be added in your channel's options." +msgstr "Derzeit ist die Einstellung Video vs. Kanalräume eine instanzweite Einstellung. Nur Peertube-Administratoren können diese Einstellung ändern, und sie gilt für alle Chaträume. In der Zukunft wird diese Wahl in den Optionen Ihres Channels hinzugefügt werden." + +#. type: Title ### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes" +msgstr "Hinweise" + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Create/Edit Notes" +msgstr "Notizen erstellen/bearbeiten" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can use the plus button on the top to create a new note. You can also edit existing notes using the edit button, or delate any note." +msgstr "Mit der Plus-Schaltfläche am oberen Rand können Sie eine neue Notiz erstellen. Sie können auch bestehende Notizen mit der Schaltfläche \"Bearbeiten\" bearbeiten oder jede Notiz löschen." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#: support/documentation/content/en/documentation/user/streamers/tasks.md +msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." +msgstr "Alle Änderungen sind sofort in allen Registerkarten Ihres Browsers und für alle Raumadministratoren sichtbar." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can create a note associated to a participant in several ways:" +msgstr "Sie können eine Notiz zu einem Teilnehmer auf verschiedene Weise erstellen:" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides participants in the sidebar" +msgstr "die Aktion \"{{% livechat_label moderator_note_create_for_participant %}}\" im Dropdown-Menü neben den Teilnehmern in der Seitenleiste verwenden" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "using the \"{{% livechat_label moderator_note_create_for_participant %}}\" action in the dropdown menu besides chat messages" +msgstr "Verwendung der Aktion \"{{% livechat_label moderator_note_create_for_participant %}}\" im Dropdown-Menü neben den Chat-Nachrichten" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When a note is associated to a participant, you will see their nickname and avatar on the top of the note." +msgstr "Wenn eine Notiz mit einem Teilnehmer verknüpft ist, sehen Sie dessen Spitznamen und Avatar oben in der Notiz." + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Notes filtering" +msgstr "Notizen filtern" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can filter notes to find all notes related to a given participant in several ways:" +msgstr "Sie können die Notizen filtern, um alle Notizen zu einem bestimmten Teilnehmer zu finden, und haben dabei mehrere Möglichkeiten:" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button that is available on notes to find all notes related to the same participant" +msgstr "Klicken Sie auf die Schaltfläche \"{{% livechat_label moderator_note_search_for_participant %}}\", die auf den Notizen verfügbar ist, um alle Notizen zu finden, die sich auf denselben Teilnehmer beziehen" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides participants in the sidebar" +msgstr "auf die Schaltfläche \"{{% livechat_label moderator_note_search_for_participant %}}\" im Dropdown-Menü neben den Teilnehmern in der Seitenleiste klicken" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "click on the \"{{% livechat_label moderator_note_search_for_participant %}}\" button in the dropdown menu besides chat messages" +msgstr "Klicken Sie auf die Schaltfläche \"{{% livechat_label moderator_note_search_for_participant %}}\" im Dropdown-Menü neben den Chat-Nachrichten" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can remove the filter by clicking on the close button." +msgstr "Sie können den Filter entfernen, indem Sie auf die Schaltfläche \"Schließen\" klicken." + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "![Screenshot of the note application, with a filter enabled for user \"Mike\". The only notes that are shown are the notes for the Mike user.](/peertube-plugin-livechat/images/moderation_notes_filters.png?classes=shadow,border&height=200px \"Moderator Notes Application - filtering\")" +msgstr "" +"![Screenshot der Notizen-Anwendung, mit einem aktivierten Filter für den " +"Benutzer \"Mike\". Es werden nur die Notizen für den Benutzer \"Mike\" " +"angezeigt.](/peertube-plugin-livechat/images/" +"moderation_notes_filters.png?classes=shadow,border&height=200px \"Moderator " +"Notizen Programm - Filtern\")" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "When you filters notes on a participant, there are several informations that are shown at the right of the participant nickname:" +msgstr "Wenn Sie Notizen zu einem Teilnehmer filtern, werden rechts neben dem Spitznamen des Teilnehmers verschiedene Informationen angezeigt:" + +#. type: Bullet: '* ' +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "if the current nickname is different than the nickname when you created the note, the original nickname will be shown" +msgstr "wenn der aktuelle Spitzname nicht mit dem Spitznamen übereinstimmt, unter dem Sie die Notiz erstellt haben, wird der ursprüngliche Spitzname angezeigt" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "The search result will also include all notes related to participants who had the same nickname. So you can also take note for anonymous users (who don't have any consistent JID or occupant-id). You can differenciate them by comparing JID and occupant-id." +msgstr "Das Suchergebnis enthält auch alle Notizen zu Teilnehmern, die denselben Spitznamen hatten. Sie können also auch anonyme Benutzer (die keine einheitliche JID oder occupant-id haben) notieren. Sie können sie durch den Vergleich von JID und occupant-id unterscheiden." + +#. type: Title #### +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +#, no-wrap +msgid "Sorting notes" +msgstr "Notizen sortieren" + +#. type: Plain text +#: support/documentation/content/en/documentation/user/streamers/moderation_notes.md +msgid "You can sort notes simply using drag & drop." +msgstr "Sie können Notizen einfach per Drag & Drop sortieren." + #. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/documentation/user/streamers/polls.md #, no-wrap @@ -3472,8 +4045,12 @@ msgstr "Sie können eine neue Umfrage erstellen, indem Sie die Aktion \"{{% live #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" -msgstr "![Umfrageformular](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a \"{{% livechat_label new_poll %}}\" form. The form contains several fields: question, duration, choices, …](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px \"Poll form\")" +msgstr "" +"![Screenshot eines \"{{% livechat_label new_poll %}}\"-Formulars. Das " +"Formular enthält mehrere Felder: Frage, Dauer, Auswahlmöglichkeiten, " +"...](/peertube-plugin-livechat/images/" +"polls_form.png?classes=shadow,border&height=200px \"Umfrageformular\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md @@ -3526,13 +4103,6 @@ msgstr "Sobald Sie das Formular abschicken, wird die Umfrage sofort gestartet." msgid "If there was a previous unfinished poll, it will end and its result will be shown." msgstr "Wenn es eine vorherige, noch nicht beendete Umfrage gab, wird diese beendet und ihr Ergebnis angezeigt." -#. type: Title ### -#: support/documentation/content/en/documentation/user/streamers/polls.md -#: support/documentation/content/en/documentation/user/streamers/tasks.md -#, no-wrap -msgid "Access rights" -msgstr "Zugriffsrechte" - #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md msgid "Every room's admins can create a new poll." @@ -3571,8 +4141,13 @@ msgstr "Außerdem wird ein Banner erscheinen, das die Umfrage anzeigt und regelm #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" -msgstr "![Umfrage Start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. In the chat, there is a message with the poll question, and the different choices. There is also a banner on the top of the chat, where you can see the question, and the number of votes for each answers.](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px \"Poll start\")" +msgstr "" +"![Screenshot einer Chatsitzung. Im Chat gibt es eine Nachricht mit der Frage " +"und den verschiedenen Wahlmöglichkeiten. Außerdem gibt es ein Banner am " +"oberen Rand des Chats, auf dem die Frage und die Anzahl der Stimmen für die " +"einzelnen Antworten zu sehen sind.](/peertube-plugin-livechat/images/" +"polls_start.png?classes=shadow,border&height=200px \"Umfragestart\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md @@ -3591,8 +4166,12 @@ msgstr "Die Zuschauer können ihre Wahl jederzeit ändern, indem sie einfach ein #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll votes](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px)" -msgstr "![Abstimmungen](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with an ongoing poll. The current user has just voted by sending \"!1\".](/peertube-plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px \"Poll votes\")" +msgstr "" +"![Screenshot einer Chatsitzung mit einer laufenden Umfrage. Der aktuelle " +"Benutzer hat gerade abgestimmt, indem er \"!1\" gesendet hat.](/peertube-" +"plugin-livechat/images/polls_votes.png?classes=shadow,border&height=200px " +"\"Umfrage abstimmen\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md @@ -3616,8 +4195,14 @@ msgstr "Wenn die Umfrage beendet ist, wird im Chat eine neue Nachricht mit den E #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md -msgid "![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" -msgstr "![Umfrageende](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with poll that has ended. The banner no more accept new votes. There is a message in the chat with the poll results. For each choice, there is the number of votes, and the percentage of the total it represents.](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px \"Poll end\")" +msgstr "" +"![Screenshot einer Chatsitzung, mit einer Umfrage, die beendet wurde. Das " +"banner nimmt keine neuen Stimmen mehr an. Im Chat erscheint eine Nachricht " +"mit den Ergebnissen der Umfrage. Für jede Wahl gibt es die Anzahl der " +"Stimmen und den prozentualen Anteil an der Gesamtzahl.](/peertube-" +"plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px " +"\"Umfrageende\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/polls.md @@ -3684,8 +4269,12 @@ msgstr "Auf der [Kanal Konfigurations Seite](/peertube-plugin-livechat/de/docume #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Channel configuration / Slow Mode](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px)" -msgstr "![Kanalkonfiguration / Langsamer Modus](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a slow mode field.](/peertube-plugin-livechat/images/slow_mode_channel_option.png?classes=shadow,border&height=400px \"Channel configuration / Slow Mode\")" +msgstr "" +"![Screenshot des Formulars für die Kanaloptionen, mit einem Feld für den " +"langsamen Modus.](/peertube-plugin-livechat/images/" +"slow_mode_channel_option.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration / Langsamer Modus\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md @@ -3711,8 +4300,13 @@ msgstr "Wenn der langsame Modus aktiviert ist, wird der Benutzer durch eine Nach #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md -msgid "![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" -msgstr "![Infobox Langsamer Modus](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. There is a banner on the bottom of the chat, indicating that the slow mode is enabled, and that users can send a message every 2 seconds.](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px \"Slow mode infobox\")" +msgstr "" +"![Screenshot einer Chatsitzung. Am unteren Rand des Chats befindet sich ein " +"Banner, das darauf hinweist, dass der langsame Modus aktiviert ist und die " +"Benutzer alle 2 Sekunden eine Nachricht senden können.](/peertube-" +"plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px " +"\"Langsamer Modus Infobox\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/slow_mode.md @@ -3756,11 +4350,6 @@ msgstr "eine Liste der Themen vorzubereiten, die Sie während Ihres Livestreams msgid "highlight questions from your viewers, so you can come back to them later without forgetting to answer them" msgstr "Fragen Ihrer Zuschauer markieren, damit Sie später darauf zurückkommen können, ohne zu vergessen, sie zu beantworten" -#. type: Bullet: '* ' -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "..." -msgstr "..." - #. type: Title ## #: support/documentation/content/en/documentation/user/streamers/tasks.md #, no-wrap @@ -3780,13 +4369,23 @@ msgstr "Um die Aufgabenanwendung zu öffnen, gibt es eine Schaltfläche \"{{% li #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px)" -msgstr "[Öffnen der Aufgabenanwendung](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The chat top menu is open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_video.png?classes=shadow,border&height=200px \"Opening the Task Application\")" +msgstr "" +"![Screenshot eines Peertube-Videos, mit dem Chat auf der rechten Seite. Das " +"obere Menü des Chats ist geöffnet, mit einer Schaltfläche \"{{% " +"livechat_label tasks %}}\".](/peertube-plugin-livechat/images/" +"task_open_app_video.png?classes=shadow,border&height=200px \"Öffnen der " +"Aufgabenanwendung\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Opening the Task Application](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px)" -msgstr "[Öffnen der Aufgabenanwendung](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube chat, fullscreen. The chat top menu open, with a \"{{% livechat_label tasks %}}\" button.](/peertube-plugin-livechat/images/task_open_app_fullpage.png?classes=shadow,border&height=200px \"Opening the Task Application\")" +msgstr "" +"![Screenshot eines Peertube-Chats, Vollbild. Das obere Menü des Chats ist " +"geöffnet, mit der Schaltfläche \"{{% livechat_label tasks %}}\".](/peertube-" +"plugin-livechat/images/" +"task_open_app_fullpage.png?classes=shadow,border&height=200px \"Öffnen der " +"Aufgabenanwendung\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3795,18 +4394,23 @@ msgstr "Wenn Sie auf diese Schaltfläche klicken, wird die Anzeige der Aufgabena #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabenanwendung](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video, with the chat on the right. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px \"Task Application\")" +msgstr "" +"![Screenshot eines Peertube-Videos, mit dem Chat auf der rechten Seite. Die " +"Aufgabenanwendung ist geöffnet. Es gibt eine Aufgabenliste und ein Formular, " +"um eine neue Aufgabe zu erstellen.](/peertube-plugin-livechat/images/" +"task_app_video_1.png?classes=shadow,border&height=200px \"Aufgabenanwendung\"" +")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabenanwendung](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "To have more space and better readability, open the chat in full-page mode." -msgstr "Um mehr Platz und eine bessere Lesbarkeit zu erhalten, öffnen Sie den Chat im neuen Fenster." +msgid "![Screenshot of a Peertube chat, fullscreen. The Task application is open. There is a task list, and a form to create a new task.](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px \"Task Application\")" +msgstr "" +"![Screenshot eines Peertube-Chats, Vollbild. Die Aufgabenanwendung ist " +"geöffnet. Es gibt eine Aufgabenliste und ein Formular, um eine neue Aufgabe " +"zu erstellen.](/peertube-plugin-livechat/images/" +"task_app_fullpage_1.png?classes=shadow,border&height=200px " +"\"Aufgabenanwendung\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3841,13 +4445,12 @@ msgstr "Die Aufgabenlisten sind alphabetisch sortiert." #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task lists](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabenlisten](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px)" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "All modification are instantly visible in all your browser tabs, and for all room's admins." -msgstr "Alle Änderungen sind sofort in allen Registerkarten Ihres Browsers und für alle Raumadministratoren sichtbar." +msgid "![Screenshot of a chat session, with the Task application. There are several task lists.](/peertube-plugin-livechat/images/task_app_task_lists.png?classes=shadow,border&height=200px \"Task lists\")" +msgstr "" +"![Screenshot einer Chatsitzung mit der Aufgabenanwendung. Es gibt mehrere " +"Aufgabenlisten.](/peertube-plugin-livechat/images/" +"task_app_task_lists.png?classes=shadow,border&height=200px \"Aufgabenlisten\"" +")" #. type: Title ### #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3868,13 +4471,21 @@ msgstr "Sie können eine Aufgabe über die Schaltfläche rechts neben der Aufgab #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task form](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabenformular](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, there is a form to create a new task.](/peertube-plugin-livechat/images/task_app_task_form.png?classes=shadow,border&height=200px \"Task form\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Unter der ersten Aufgabenliste befindet " +"sich ein Formular zum Erstellen einer neuen Aufgabe.](/peertube-" +"plugin-livechat/images/" +"task_app_task_form.png?classes=shadow,border&height=200px \"Aufgabenformular" +"\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabe erstellt](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under the first task list, a new task was created.](/peertube-plugin-livechat/images/task_app_task_1.png?classes=shadow,border&height=200px \"Task created\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Unter der ersten Aufgabenliste wurde " +"eine neue Aufgabe erstellt.](/peertube-plugin-livechat/images/" +"task_app_task_1.png?classes=shadow,border&height=200px \"Aufgabe erstellt\")" #. type: Title #### #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3894,8 +4505,12 @@ msgstr "Aufgaben können durch direktes Anklicken des Kontrollkästchens in der #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" -msgstr "![Aufgaben](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. Under task lists, there are several tasks. Some of them are checked, other not.](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px \"Tasks\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Unter den Aufgabenlisten gibt es mehrere " +"Aufgaben. Einige von ihnen sind markiert, andere nicht.](/peertube-" +"plugin-livechat/images/" +"task_app_task_2.png?classes=shadow,border&height=200px \"Aufgaben\")" #. type: Title #### #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3910,13 +4525,21 @@ msgstr "Sie können Aufgaben sortieren oder von einer Liste in eine andere versc #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to sort](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px)" -msgstr "![Drag und Drop zum Sortieren](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another.](/peertube-plugin-livechat/images/task_drag_drop.png?classes=shadow,border&height=200px \"Drag and drop to sort\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Eine Aufgabe wird über eine andere " +"gezogen](/peertube-plugin-livechat/images/" +"task_drag_drop.png?classes=shadow,border&height=200px \"Drag und drop zum " +"sortieren\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Drag and drop to move to another list](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px)" -msgstr "![Drag und Drop zum Verschieben zu einer anderen Liste](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. There is a task that is dragged over another task list.](/peertube-plugin-livechat/images/task_drag_drop_task_list.png?classes=shadow,border&height=200px \"Drag and drop to move to another list\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Es gibt eine Aufgabe, die über eine " +"andere Aufgabenliste gezogen wird.](/peertube-plugin-livechat/images/" +"task_drag_drop_task_list.png?classes=shadow,border&height=200px \"Drag und " +"drop zum verschieben zu einer anderen Liste\")" #. type: Title #### #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3931,18 +4554,33 @@ msgstr "Sie können eine Aufgabe aus einer Nachricht in einem Chat erstellen, in #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Create task from message](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabe aus einer Nachricht erstellen](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session. The menu besides a message is open, with a button to create a new task.](/peertube-plugin-livechat/images/task_from_message_1.png?classes=shadow,border&height=200px \"Create task from message\")" +msgstr "" +"![Screenshot einer Chatsitzung. Das Menü neben einer Nachricht ist geöffnet, " +"mit einer Schaltfläche zum Erstellen einer neuen Aufgabe.](/peertube-" +"plugin-livechat/images/" +"task_from_message_1.png?classes=shadow,border&height=200px \"Aufgabe aus " +"einer Nachricht erstellen\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Choose the task list](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabenliste auswählen](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a dialog, where you can choose in which task list you want to add the new task.](/peertube-plugin-livechat/images/task_from_message_2.png?classes=shadow,border&height=200px \"Choose the task list\")" +msgstr "" +"![Screenshot eines Dialogs, in dem Sie auswählen können, in welcher " +"Aufgabenliste Sie die neue Aufgabe hinzufügen möchten.](/peertube-" +"plugin-livechat/images/" +"task_from_message_2.png?classes=shadow,border&height=200px \"Aufgabenliste " +"auswählen\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md -msgid "![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" -msgstr "![Aufgabe erstellt](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the task application. A new task was added in the \"chat questions\" task list, with the user's nickname, and the message as content.](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px \"Task created\")" +msgstr "" +"![Screenshot der Aufgabenanwendung. Eine neue Aufgabe wurde in der " +"Aufgabenliste \"Chat-Fragen\" hinzugefügt, mit dem Spitznamen des Benutzers " +"und der Nachricht als Inhalt.](/peertube-plugin-livechat/images/" +"task_from_message_3.png?classes=shadow,border&height=200px \"Aufgabe " +"erstellt\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/tasks.md @@ -3979,8 +4617,12 @@ msgstr "Um die Nutzungsbedingungen zu konfigurieren, gehen Sie auf die [Kanal-Ko #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Channel configuration / Terms](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px)" -msgstr "![Kanalkonfiguration / Nutzungsbedingungen](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of the channel options form, with a field to configure your terms and conditions.](/peertube-plugin-livechat/images/channel_terms_config.png?classes=shadow,border&height=400px \"Channel configuration / Terms\")" +msgstr "" +"![Screenshot des Formulars für die Kanaloptionen mit einem Feld für die " +"Konfiguration der Nutzungsbedingungen.](/peertube-plugin-livechat/images/" +"channel_terms_config.png?classes=shadow,border&height=400px " +"\"Kanalkonfiguration / Nutzungsbedingungen\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md @@ -4000,8 +4642,11 @@ msgstr "Wenn Sie dem Chat beitreten, sehen die Zuschauer die Bedingungen:" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md -msgid "![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" -msgstr "![Bedingungen](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" +msgid "![Screenshot of a chat session. On the top of the chat, there are terms and conditions for both the server and the channel.](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px \"Terms\")" +msgstr "" +"![Screenshot einer Chatsitzung. Am oberen Rand des Chats stehen die " +"Bedingungen für den Server und den Kanal.](/peertube-plugin-livechat/images/" +"terms.png?classes=shadow,border&height=400px \"Nutzungsbedingungen\")" #. type: Plain text #: support/documentation/content/en/documentation/user/streamers/terms.md @@ -4049,8 +4694,11 @@ msgstr "Wenn Sie ein Peertube-Video ansehen, bei dem der Chat aktiviert ist, seh #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/_index.md #: support/documentation/content/en/intro/_index.md -msgid "![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px)" -msgstr "![Chat screenshot](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a Peertube video page, with a web chat on the right of the video.](/peertube-plugin-livechat/images/chat.png?classes=shadow,border&height=200px \"Chat screenshot\")" +msgstr "" +"![Screenshot einer Peertube-Videoseite mit einem Web-Chat rechts neben dem " +"Video.](/peertube-plugin-livechat/images/" +"chat.png?classes=shadow,border&height=200px \"Chat Bildschirmfoto\")" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4070,8 +4718,13 @@ msgstr "Wenn Sie auf der Peertube-Instanz, auf der Sie das Video ansehen, nicht #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Chat with an anonymous user](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" -msgstr "![Chat mit einem anonymen Benutzer](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat. In the participant list, there is John Livingston, and an anonymous account using \"Anonymous 212873\" nickname.](/peertube-plugin-livechat/images/chat_with_anonymous.png?classes=shadow,border&height=200px \"Chat with an anonymous user\")" +msgstr "" +"![Screenshot eines Chats. In der Teilnehmerliste gibt es John Livingston und " +"einen anonymen Account mit dem Nicknamen \"Anonymous 212873\".](/peertube-" +"plugin-livechat/images/" +"chat_with_anonymous.png?classes=shadow,border&height=200px \"Chat mit einem " +"anonymen Benutzer\")" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4080,8 +4733,12 @@ msgstr "Bevor Sie im Chatraum sprechen können, müssen Sie einen Spitznamen in #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Joining chat when not connected](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px)" -msgstr "![Dem Chat beitreten, wenn nicht verbunden](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat. The current user is not logged in, and must choose a nickname before being able to write in the chat.](/peertube-plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px \"Joining chat when not connected\")" +msgstr "" +"![Screenshot des Chats. Der aktuelle Benutzer ist nicht eingeloggt und muss " +"einen Spitznamen wählen, bevor er im Chat schreiben kann.](/peertube-" +"plugin-livechat/images/chat_anonymous.png?classes=shadow,border&height=200px " +"\"Chat beitreten, wenn nicht verbunden\")" #. type: Title #### #: support/documentation/content/en/documentation/user/viewers.md @@ -4133,8 +4790,13 @@ msgstr "Wenn Sie ein Peertube Konto haben, aber nicht auf der aktuellen Instanz, #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" -msgstr "![Externer Anmeldedialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the \"{{% livechat_label login_using_external_account %}}\" dialog. There is a field where you can enter a Peertube url.](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px \"External login dialog\")" +msgstr "" +"![Screenshot des Dialogs \"{{% livechat_label login_using_external_account " +"%}}\". Es gibt ein Feld, in das Sie eine Peertube-URL eingeben können" +".](/peertube-plugin-livechat/images/" +"external_login_dialog.png?classes=shadow,border&height=200px \"Externer " +"Anmeldedialog\")" #. type: Title ## #: support/documentation/content/en/documentation/user/viewers.md @@ -4175,8 +4837,12 @@ msgstr "Um die Liste der Teilnehmer zu sehen, öffnen Sie einfach das rechte Men #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md -msgid "![Participants list](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px)" -msgstr "![Teilnehmerliste](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat session, with on the right the list of participants.](/peertube-plugin-livechat/images/open_participants_list.png?classes=shadow,border&height=200px \"Participants list\")" +msgstr "" +"![Screenshot einer Chatsitzung, rechts die Liste der Teilnehmer](/peertube-" +"plugin-livechat/images/" +"open_participants_list.png?classes=shadow,border&height=200px " +"\"Teilnehmerliste\")" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4208,8 +4874,11 @@ msgstr "Oben im Chat gibt es eine Schaltfläche, um den Chat im Vollbildmodus zu #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md #: support/documentation/content/en/intro/_index.md -msgid "![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px)" -msgstr "![Fullscreen chat screenshot](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of a chat using the full web page.](/peertube-plugin-livechat/images/fullscreen.png?classes=shadow,border&height=200px \"Fullscreen chat screenshot\")" +msgstr "" +"![Screenshot des Chats über die gesamte Webseite.](/peertube-plugin-livechat/" +"images/fullscreen.png?classes=shadow,border&height=200px \"Vollbild Chat " +"Bildschirmfoto\")" #. type: Plain text #: support/documentation/content/en/documentation/user/viewers.md @@ -4266,8 +4935,11 @@ msgstr "Um die Adresse des Raums, dem Sie beitreten möchten, zu erhalten, könn #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share button](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px)" -msgstr "![Teilen Schaltfläche](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of the chat, with a share button on the top.](/peertube-plugin-livechat/images/share_button.png?classes=shadow,border&height=200px \"Share button\")" +msgstr "" +"![Screenshot des Chats mit einer Schaltfläche zum Teilen am oberen Rand" +".](/peertube-plugin-livechat/images/" +"share_button.png?classes=shadow,border&height=200px \"Teilen Schaltfläche\")" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md @@ -4276,24 +4948,25 @@ msgstr "Standardmäßig ist die Schaltfläche \"Freigeben\" nur für den Eigent #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "Then, choose \"Connect using XMPP\":" -msgstr "Wählen Sie dann \"Verbinden über XMPP\":" - -#. type: Plain text -#: support/documentation/content/en/documentation/user/xmpp_clients.md -msgid "![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" -msgstr "![XMPP Link teilen](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +msgid "Then, choose \"{{% livechat_label connect_using_xmpp %}}\":" +msgstr "Wählen Sie dann \"{{% livechat_label connect_using_xmpp %}}\":" #. type: Plain text #: support/documentation/content/en/documentation/user/xmpp_clients.md msgid "Then you just have to click on \"open\" or copy/paste the address of the chat room into your XMPP client (using the \"join a room\" feature)." msgstr "Dann müssen Sie nur noch auf \"Öffnen\" klicken oder die Adresse des Chatraums in Ihren XMPP-Client kopieren/einfügen (mit der Funktion \"einem Raum beitreten\")." -#. type: Title # +#. type: Yaml Front Matter Hash Value: description #: support/documentation/content/en/_index.md #, no-wrap -msgid "PeerTube plugin livechat" -msgstr "PeerTube plugin livechat" +msgid "Peertube plugin livechat documentation" +msgstr "Peertube Plugin Livechat Dokumentation" + +#. type: Yaml Front Matter Hash Value: title +#: support/documentation/content/en/_index.md +#, no-wrap +msgid "Peertube livechat" +msgstr "Peertube livechat" #. type: Plain text #: support/documentation/content/en/_index.md @@ -4369,8 +5042,13 @@ msgstr "Als Peertube-Administrator können Sie dieses Plugin auf Ihrer Instanz e #. type: Plain text #: support/documentation/content/en/intro/_index.md -msgid "![Livechat installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px)" -msgstr "![Livechat-Installation](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px)" +msgid "![Screenshot of Peertube plugins admin page. The search fields contains \"livechat\", and the search results show the livechat plugin.](/peertube-plugin-livechat/images/installation.png?classes=shadow,border&height=200px \"Livechat installation\")" +msgstr "" +"![Screenshot der Peertube Plugin Administrator Seite. Die Suchfelder " +"enthalten \"livechat\", und die Suchergebnisse zeigen das Livechat-Plugin" +".](/peertube-plugin-livechat/images/" +"installation.png?classes=shadow,border&height=200px \"Livechat installation\"" +")" #. type: Title ## #: support/documentation/content/en/intro/_index.md @@ -4502,6 +5180,63 @@ msgstr "[Meilensteine auf Github](https://github.com/JohnXLivingston/peertube-pl msgid "If you are a webdesigner or a ConverseJS/Prosody/XMPP expert, and want to help improve this plugin, you are welcome." msgstr "Wenn Sie ein Webdesigner oder ein ConverseJS/Prosody/XMPP-Experte sind und helfen wollen, dieses Plugin zu verbessern, sind Sie gerne willkommen." +#~ msgid "![External login button](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" +#~ msgstr "![Externes Anmelden Schaltfläche](/peertube-plugin-livechat/images/external_login_button.png?classes=shadow,border&height=200px)" + +#~ msgid "![Share link popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" +#~ msgstr "![Link Teilen Popup](/peertube-plugin-livechat/images/share_readonly.png?classes=shadow,border&height=200px)" + +#~ msgid "![Share link popup - dock tab](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" +#~ msgstr "![Link teilen Fenster - Dock Reiter](/peertube-plugin-livechat/images/share_dock.png?classes=shadow,border&height=200px)" + +#~ msgid "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" +#~ msgstr "![OBS - Dock](/peertube-plugin-livechat/images/obs_dock.png?classes=shadow,border&height=200px)" + +#~ msgid "![Share link popup - web tab](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" +#~ msgstr "![Link teilen Fenster - Web Reiter](/peertube-plugin-livechat/images/share_web.png?classes=shadow,border&height=200px)" + +#~ msgid "![Chatrooms menu](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" +#~ msgstr "![Chaträume Menü](/peertube-plugin-livechat/images/chatrooms_menu.png?classes=shadow,border&height=400px)" + +#~ msgid "![Message history search](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px)" +#~ msgstr "![Nachrichtenverlaufssuche](/peertube-plugin-livechat/images/message_search.png?classes=shadow,border&height=200px)" + +#~ msgid "![Moderator Notes Application](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px)" +#~ msgstr "![Moderationsnotizen Anwendung](/peertube-plugin-livechat/images/moderator_notes_app_video_1.png?classes=shadow,border&height=200px)" + +#~ msgid "![Poll form](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" +#~ msgstr "![Umfrageformular](/peertube-plugin-livechat/images/polls_form.png?classes=shadow,border&height=200px)" + +#~ msgid "![Poll start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" +#~ msgstr "![Umfrage Start](/peertube-plugin-livechat/images/polls_start.png?classes=shadow,border&height=200px)" + +#~ msgid "![Poll end](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" +#~ msgstr "![Umfrageende](/peertube-plugin-livechat/images/polls_end.png?classes=shadow,border&height=200px)" + +#~ msgid "![Slow mode infobox](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" +#~ msgstr "![Infobox Langsamer Modus](/peertube-plugin-livechat/images/slow_mode.png?classes=shadow,border&height=400px)" + +#~ msgid "![Task Application](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" +#~ msgstr "![Aufgabenanwendung](/peertube-plugin-livechat/images/task_app_video_1.png?classes=shadow,border&height=200px)" + +#~ msgid "![Task Application](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" +#~ msgstr "![Aufgabenanwendung](/peertube-plugin-livechat/images/task_app_fullpage_1.png?classes=shadow,border&height=200px)" + +#~ msgid "![Tasks](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" +#~ msgstr "![Aufgaben](/peertube-plugin-livechat/images/task_app_task_2.png?classes=shadow,border&height=200px)" + +#~ msgid "![Task created](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" +#~ msgstr "![Aufgabe erstellt](/peertube-plugin-livechat/images/task_from_message_3.png?classes=shadow,border&height=200px)" + +#~ msgid "![Terms](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" +#~ msgstr "![Bedingungen](/peertube-plugin-livechat/images/terms.png?classes=shadow,border&height=400px)" + +#~ msgid "![External login dialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" +#~ msgstr "![Externer Anmeldedialog](/peertube-plugin-livechat/images/external_login_dialog.png?classes=shadow,border&height=200px)" + +#~ msgid "![Share XMPP](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" +#~ msgstr "![XMPP Link teilen](/peertube-plugin-livechat/images/share_xmpp_dialog.png?classes=shadow,border&height=200px)" + #, fuzzy, no-wrap #~ msgid "" #~ "